diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index 97993bf75..2948181d0 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -791,7 +791,6 @@ fn call_indent_hook( let mut cursor = line_before; let mut depth = 0; - // for line in text_up_to_cursor.lines().reversed() { loop { let line = text_up_to_cursor.line(cursor); @@ -864,7 +863,12 @@ fn call_indent_hook( // return Some(" ".repeat(offset + 1)); // } - if line.slice(offset..offset + end).as_str().map(|x| LISP_WORDS.contains(x)).unwrap_or_default() { + if line + .slice(offset..offset + end) + .as_str() + .map(|x| LISP_WORDS.contains(x)) + .unwrap_or_default() + { return Some(" ".repeat(offset + 1)); } @@ -1021,16 +1025,21 @@ pub fn custom_indent_for_newline( } // TODO: Don't unwrap here, we don't want that - if LISP_WORDS.contains( - line.slice(offset..offset + end).as_str().unwrap(), - ) { - return Some(" ".repeat(offset + 1)); - } - - // if line.slice(offset..offset + end).as_str().map(|x| LISP_WORDS.contains(x)).unwrap_or_default() { + // if LISP_WORDS.contains( + // line.slice(offset..offset + end).as_str().unwrap(), + // ) { // return Some(" ".repeat(offset + 1)); // } + if line + .slice(offset..offset + end) + .as_str() + .map(|x| LISP_WORDS.contains(x)) + .unwrap_or_default() + { + return Some(" ".repeat(offset + 1)); + } + for _ in char_iter_from_paren .take_while(|(_, x)| x.is_whitespace()) { diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 62ddf7667..a92406067 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -343,6 +343,10 @@ impl Application { self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); self.render().await; } + Some(callback) = self.jobs.local_futures.next() => { + self.jobs.handle_local_callback(&mut self.editor, &mut self.compositor, callback); + self.render().await; + } event = self.editor.wait_event() => { let _idle_handled = self.handle_editor_event(event).await; diff --git a/helix-term/src/commands/engine.rs b/helix-term/src/commands/engine.rs index e74f52c4a..07c68570f 100644 --- a/helix-term/src/commands/engine.rs +++ b/helix-term/src/commands/engine.rs @@ -1,10 +1,12 @@ use fuzzy_matcher::FuzzyMatcher; -use helix_core::{graphemes, Tendril, Selection}; -use helix_view::{document::Mode, Document, DocumentId, Editor, editor::Action, extension::document_id_to_usize}; +use helix_core::{graphemes, Selection, Tendril}; +use helix_view::{ + document::Mode, editor::Action, extension::document_id_to_usize, Document, DocumentId, Editor, +}; use once_cell::sync::Lazy; use steel::{ gc::unsafe_erased_pointers::CustomReference, - rvals::{FromSteelVal, IntoSteelVal, SteelString}, + rvals::{AsRefMutSteelValFromRef, FromSteelVal, IntoSteelVal}, steel_vm::{engine::Engine, register_fn::RegisterFn}, SteelErr, SteelVal, }; @@ -25,7 +27,7 @@ use crate::{ compositor::{self, Component, Compositor}, job::{self, Callback}, keymap::{merge_keys, Keymap}, - ui::{self, menu::Item, overlay::overlaid, Popup, PromptEvent}, + ui::{self, menu::Item, overlay::overlaid, Popup, Prompt, PromptEvent}, }; use self::components::SteelDynamicComponent; @@ -418,6 +420,11 @@ fn configure_engine() -> std::rc::Rc std::rc::Rc WrappedDynComponent { + let prompt = Prompt::new( + prompt.into(), + None, + |_, _| Vec::new(), + move |cx, input, prompt_event| { + log::info!("Calling dynamic prompt callback"); + + if prompt_event != PromptEvent::Validate { + return; + } + + let mut ctx = Context { + register: None, + count: None, + editor: cx.editor, + callback: None, + on_next_key_callback: None, + jobs: cx.jobs, + }; + + let cloned_func = callback_fn.clone(); + + ENGINE + .with(|x| { + x.borrow_mut() + .with_mut_reference::(&mut ctx) + .consume(move |engine, mut args| { + // Add the string as an argument to the callback + args.push(input.into_steelval().unwrap()); + + engine.call_function_with_args(cloned_func.clone(), args) + }) + }) + .unwrap(); + }, + ); + + WrappedDynComponent { + inner: Some(Box::new(prompt)), + } + }, + ); + engine.register_fn("Picker::new", |values: Vec| todo!()); // engine.register_fn( @@ -562,7 +615,7 @@ fn configure_engine() -> std::rc::Rcget-document", get_document); // TODO: These are some horrendous type annotations, however... they do work? @@ -583,6 +636,8 @@ fn configure_engine() -> std::rc::Rc("DocumentId?"); // RegisterFn::< // _, @@ -618,6 +673,10 @@ fn configure_engine() -> std::rc::Rc std::rc::Rc std::rc::Rc usize { let (view, doc) = current_ref!(cx.editor); helix_core::coords_at_pos( - doc.text().slice(..), - doc - .selection(view.id) + doc.text().slice(..), + doc.selection(view.id) .primary() .cursor(doc.text().slice(..)), - ).row + ) + .row } fn get_selection(cx: &mut Context) -> String { @@ -970,15 +1028,17 @@ fn get_document_id(editor: &mut Editor, view_id: helix_view::ViewId) -> Document editor.tree.get(view_id).doc } - - // Get the document from the document id - TODO: Add result type here fn get_document(editor: &mut Editor, doc_id: DocumentId) -> &Document { editor.documents.get(&doc_id).unwrap() } fn is_document_in_view(editor: &mut Editor, doc_id: DocumentId) -> Option { - editor.tree.traverse().find(|(_, v)| v.doc == doc_id).map(|(id, _)| id) + editor + .tree + .traverse() + .find(|(_, v)| v.doc == doc_id) + .map(|(id, _)| id) } fn document_exists(editor: &mut Editor, doc_id: DocumentId) -> bool { @@ -1035,45 +1095,59 @@ fn run_shell_command_text( } } -// Pin the value to _this_ thread? +fn is_context(value: SteelVal) -> bool { + Context::as_mut_ref_from_ref(&value).is_ok() +} // Overlay the dynamic component, see what happens? // Probably need to pin the values to this thread - wrap it in a shim which pins the value // to this thread? - call methods on the thread local value? fn push_component(cx: &mut Context, component: &mut WrappedDynComponent) { - // let component = crate::ui::Text::new("Hello world!".to_string()); - log::info!("Pushing dynamic component!"); - // todo!(); - - // let callback = async move { - // let call: job::Callback = Callback::EditorCompositor(Box::new( - // move |_editor: &mut Editor, compositor: &mut Compositor| { - // compositor.push(Box::new(component)); - // }, - // )); - - // Ok(call) - // }; - - // cx.jobs.callback(callback); + let inner = component.inner.take().unwrap(); - // Why does this not work? - UPDATE: This does work when called in a static command context, but - // in a typed command context, we do not have access to the real compositor. Thus, we need a callback - // that then requires the values to be moved over threads. We'll need some message passing scheme - // to call values from the typed command context. - cx.push_layer(component.inner.take().unwrap()); - - // TODO: This _needs_ to go through a callback. Otherwise the new layer is just dropped. - // Set up some sort of callback queue for dynamic components that we can pull from instead, so that - // things stay thread local? + let callback = async move { + let call: Box = Box::new( + move |_editor: &mut Editor, compositor: &mut Compositor, _| compositor.push(inner), + ); + Ok(call) + }; + cx.jobs.local_callback(callback); +} - // let root = helix_core::find_workspace().0; - // let picker = ui::file_picker(root, &cx.editor.config()); - // cx.push_layer(Box::new(overlaid(picker))); +fn enqueue_command(cx: &mut Context, callback_fn: SteelVal) { + let callback = async move { + let call: Box = Box::new( + move |editor: &mut Editor, _compositor: &mut Compositor, jobs: &mut job::Jobs| { + let mut ctx = Context { + register: None, + count: None, + editor, + callback: None, + on_next_key_callback: None, + jobs, + }; + + let cloned_func = callback_fn.clone(); + + ENGINE + .with(|x| { + x.borrow_mut() + .with_mut_reference::(&mut ctx) + .consume(move |engine, args| { + engine.call_function_with_args(cloned_func.clone(), args) + }) + }) + .unwrap(); + }, + ); + Ok(call) + }; + cx.jobs.local_callback(callback); } -// fn push_component_raw(cx: &mut Context, component: Box) { -// cx.push_layer(component); +// fn enqueue_callback(cx: &mut Context, thunk: SteelVal) { +// log::info!("Enqueueing callback!"); + // } diff --git a/helix-term/src/commands/engine/components.rs b/helix-term/src/commands/engine/components.rs index 6b46f1d2f..5a010b08a 100644 --- a/helix-term/src/commands/engine/components.rs +++ b/helix-term/src/commands/engine/components.rs @@ -10,7 +10,7 @@ use steel::{ use crate::{ commands::{engine::ENGINE, Context}, compositor::{self, Component}, - ui::Popup, + ui::{Popup, Prompt, PromptEvent}, }; pub fn helix_component_module() -> BuiltInModule { @@ -72,6 +72,61 @@ pub fn helix_component_module() -> BuiltInModule { } }, ); + // prompt: Cow<'static, str>, + // history_register: Option, + // completion_fn: impl FnMut(&Editor, &str) -> Vec + 'static, + // callback_fn: impl FnMut(&mut Context, &str, PromptEvent) + 'static, + module.register_fn( + "Prompt::new", + |prompt: String, callback_fn: SteelVal| -> WrappedDynComponent { + let prompt = Prompt::new( + prompt.into(), + None, + |_, _| Vec::new(), + move |cx, input, prompt_event| { + if prompt_event != PromptEvent::Validate { + return; + } + + let mut ctx = Context { + register: None, + count: None, + editor: cx.editor, + callback: None, + on_next_key_callback: None, + jobs: cx.jobs, + }; + + let cloned_func = callback_fn.clone(); + // let thunk = move |engine: &mut Engine, cx, input| { + // engine.call_function_with_args( + // cloned_func, + // vec![cx, input], + + // ) + + // }; + + ENGINE + .with(|x| { + x.borrow_mut() + .with_mut_reference::(&mut ctx) + .consume(move |engine, mut args| { + // Add the string as an argument to the callback + args.push(input.into_steelval().unwrap()); + + engine.call_function_with_args(cloned_func.clone(), args) + }) + }) + .unwrap(); + }, + ); + + WrappedDynComponent { + inner: Some(Box::new(prompt)), + } + }, + ); module.register_fn("Picker::new", |values: Vec| todo!()); diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index dcccf037f..d336f953e 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -2933,17 +2933,18 @@ pub(super) fn command_mode(cx: &mut Context) { ); let mut guard = x.borrow_mut(); + // let mut maybe_callback = None; + + let res = { + let mut cx = Context { + register: None, + count: std::num::NonZeroUsize::new(1), + editor: cx.editor, + callback: None, + on_next_key_callback: None, + jobs: cx.jobs, + }; - let mut cx = Context { - register: None, - count: std::num::NonZeroUsize::new(1), - editor: cx.editor, - callback: None, - on_next_key_callback: None, - jobs: cx.jobs, - }; - - { guard .register_value("_helix_args", steel::rvals::SteelVal::ListV(args)); @@ -2955,8 +2956,22 @@ pub(super) fn command_mode(cx: &mut Context) { guard.register_value("_helix_args", steel::rvals::SteelVal::Void); + // if let Some(callback) = cx.callback.take() { + // panic!("Found a callback!"); + // maybe_callback = Some(callback); + // } + res - } + }; + + // TODO: Recursively (or otherwise) keep retrying until we're back + // into the engine context, executing a function. We might need to set up + // some sort of fuel or something + // if let Some(callback) = maybe_callback { + // (callback)(_, cx); + // } + + res }) { compositor_present_error(cx, e) }; diff --git a/helix-term/src/job.rs b/helix-term/src/job.rs index 19f2521a5..7032ad061 100644 --- a/helix-term/src/job.rs +++ b/helix-term/src/job.rs @@ -2,12 +2,15 @@ use helix_view::Editor; use crate::compositor::Compositor; -use futures_util::future::{BoxFuture, Future, FutureExt}; -use futures_util::stream::{FuturesUnordered, StreamExt}; +use futures_util::future::{BoxFuture, Future, FutureExt, LocalBoxFuture}; +use futures_util::stream::{FuturesOrdered, FuturesUnordered, StreamExt}; pub type EditorCompositorCallback = Box; pub type EditorCallback = Box; +pub type ThreadLocalEditorCompositorCallback = + Box; + pub enum Callback { EditorCompositor(EditorCompositorCallback), Editor(EditorCallback), @@ -21,11 +24,16 @@ pub struct Job { pub wait: bool, } +pub type ThreadLocalJob = + LocalBoxFuture<'static, anyhow::Result>>; + #[derive(Default)] pub struct Jobs { pub futures: FuturesUnordered, /// These are the ones that need to complete before we exit. pub wait_futures: FuturesUnordered, + + pub local_futures: FuturesUnordered, } impl Job { @@ -67,6 +75,16 @@ impl Jobs { self.add(Job::with_callback(f)); } + pub fn local_callback< + F: Future> + 'static, + >( + &mut self, + f: F, + ) { + self.local_futures + .push(f.map(|r| r.map(Some)).boxed_local()); + } + pub fn handle_callback( &self, editor: &mut Editor, @@ -85,6 +103,21 @@ impl Jobs { } } + pub fn handle_local_callback( + &mut self, + editor: &mut Editor, + compositor: &mut Compositor, + call: anyhow::Result>, + ) { + match call { + Ok(None) => {} + Ok(Some(call)) => call(editor, compositor, self), + Err(e) => { + editor.set_error(format!("Sync job failed: {}", e)); + } + } + } + pub async fn next_job(&mut self) -> Option>> { tokio::select! { event = self.futures.next() => { event } diff --git a/helix-tui/src/extension.rs b/helix-tui/src/extension.rs index 77af36ae2..63ab393b7 100644 --- a/helix-tui/src/extension.rs +++ b/helix-tui/src/extension.rs @@ -4,4 +4,4 @@ use steel::{gc::unsafe_erased_pointers::CustomReference, rvals::Custom}; impl CustomReference for Buffer {} -steel::custom_reference!(Buffer); \ No newline at end of file +steel::custom_reference!(Buffer); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 4f583d386..8ae320d49 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -692,7 +692,7 @@ pub struct WhitespaceCharacters { impl Default for WhitespaceCharacters { fn default() -> Self { Self { - space: '·', // U+00B7 + space: '·', // U+00B7 nbsp: '⍽', // U+237D tab: '→', // U+2192 newline: '⏎', // U+23CE diff --git a/helix-view/src/extension.rs b/helix-view/src/extension.rs index 16b7773f9..d5bc994a5 100644 --- a/helix-view/src/extension.rs +++ b/helix-view/src/extension.rs @@ -12,4 +12,4 @@ impl CustomReference for Document {} pub fn document_id_to_usize(doc_id: &DocumentId) -> usize { doc_id.0.into() -} \ No newline at end of file +}