diff --git a/helix-core/src/extensions.rs b/helix-core/src/extensions.rs index cb16e77de..bcf6359b2 100644 --- a/helix-core/src/extensions.rs +++ b/helix-core/src/extensions.rs @@ -1 +1,23 @@ impl steel::rvals::Custom for crate::Position {} + +struct SRopeSlice<'a>(crate::RopeSlice<'a>); + +impl<'a> SRopeSlice<'a> { + pub fn char_to_byte(&self, pos: usize) -> usize { + self.0.char_to_byte(pos) + } + + pub fn byte_slice(&'a self, lower: usize, upper: usize) -> SRopeSlice<'a> { + SRopeSlice(self.0.byte_slice(lower..upper)) + } + + pub fn line(&'a self, cursor: usize) -> SRopeSlice<'a> { + SRopeSlice(self.0.line(cursor)) + } + + // Reference types are really sus. Not sure how this is going to work, but it might? Hopefully it cleans + // itself up as we go... + pub fn as_str(&'a self) -> Option<&'a str> { + self.0.as_str() + } +} diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index 2fa389cc0..2ee154402 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -915,18 +915,22 @@ fn call_indent_hook( None } +// TODO: Do this to allow for custom indent operations. Unfortunately, we'll have to wrap +// all of the lifetimes up into references. +// impl<'a> steel::gc::unsafe_erased_pointers::CustomReference for RopeSlice<'a> {} + /// TODO: Come up with some elegant enough FFI for this, so that Steel can expose an API for this. /// Problem is - the issues with the `Any` type and using things with type id. #[allow(clippy::too_many_arguments)] pub fn custom_indent_for_newline( language_config: Option<&LanguageConfiguration>, - syntax: Option<&Syntax>, - indent_style: &IndentStyle, - tab_width: usize, + _syntax: Option<&Syntax>, + _indent_style: &IndentStyle, + _tab_width: usize, text: RopeSlice, line_before: usize, line_before_end_pos: usize, - current_line: usize, + _current_line: usize, ) -> Option { if let Some(config) = language_config { // TODO: If possible, this would be very cool to be implemented in steel itself. If not, diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 09268aa41..ec9b7cd13 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -193,6 +193,7 @@ impl MappableCommand { // TODO: @Matt - Add delegating to the engine to run scripts here Self::Typable { name, args, doc: _ } => { let args: Vec> = args.iter().map(Cow::from).collect(); + // TODO: Swap the order to allow overriding the existing commands? if let Some(command) = typed::TYPABLE_COMMAND_MAP.get(name.as_str()) { let mut cx = compositor::Context { editor: cx.editor, diff --git a/helix-term/src/commands/engine.rs b/helix-term/src/commands/engine.rs index 34dc9ecd9..233375ff6 100644 --- a/helix-term/src/commands/engine.rs +++ b/helix-term/src/commands/engine.rs @@ -1,12 +1,12 @@ use fuzzy_matcher::FuzzyMatcher; use helix_core::{graphemes, Tendril}; -use helix_view::{document::Mode, Editor}; +use helix_view::{document::Mode, Document, DocumentId, Editor}; use once_cell::sync::Lazy; use steel::{ gc::unsafe_erased_pointers::CustomReference, rvals::{FromSteelVal, IntoSteelVal, SteelString}, steel_vm::{engine::Engine, register_fn::RegisterFn}, - SteelVal, + SteelErr, SteelVal, }; use std::{ @@ -28,6 +28,8 @@ use crate::{ ui::{self, menu::Item, overlay::overlaid, Popup, PromptEvent}, }; +use self::components::SteelDynamicComponent; + use super::{ insert::{insert_char, insert_string}, plugin::{DylibContainers, ExternalModule}, @@ -43,6 +45,8 @@ pub struct ExternalContainersAndModules { modules: Vec, } +mod components; + // External modules that can load via rust dylib. These can then be consumed from // steel as needed, via the standard FFI for plugin functions. pub(crate) static EXTERNAL_DYLIBS: Lazy>> = @@ -76,6 +80,58 @@ pub fn initialize_engine() { ENGINE.with(|x| x.borrow().globals().first().copied()); } +pub fn compositor_present_error(cx: &mut compositor::Context, e: SteelErr) { + cx.editor.set_error(format!("{}", e)); + + let backtrace = ENGINE.with(|x| x.borrow_mut().raise_error_to_string(e)); + + let callback = async move { + let call: job::Callback = Callback::EditorCompositor(Box::new( + move |editor: &mut Editor, compositor: &mut Compositor| { + if let Some(backtrace) = backtrace { + let contents = ui::Markdown::new( + format!("```\n{}\n```", backtrace), + editor.syn_loader.clone(), + ); + ui::Text::new(format!("```\n{}\n```", backtrace)); + let popup = Popup::new("engine", contents).position(Some( + helix_core::Position::new(editor.cursor().0.unwrap_or_default().row, 2), + )); + compositor.replace_or_push("engine", popup); + } + }, + )); + Ok(call) + }; + cx.jobs.callback(callback); +} + +pub fn present_error(cx: &mut Context, e: SteelErr) { + cx.editor.set_error(format!("{}", e)); + + let backtrace = ENGINE.with(|x| x.borrow_mut().raise_error_to_string(e)); + + let callback = async move { + let call: job::Callback = Callback::EditorCompositor(Box::new( + move |editor: &mut Editor, compositor: &mut Compositor| { + if let Some(backtrace) = backtrace { + let contents = ui::Markdown::new( + format!("```\n{}\n```", backtrace), + editor.syn_loader.clone(), + ); + ui::Text::new(format!("```\n{}\n```", backtrace)); + let popup = Popup::new("engine", contents).position(Some( + helix_core::Position::new(editor.cursor().0.unwrap_or_default().row, 2), + )); + compositor.replace_or_push("engine", popup); + } + }, + )); + Ok(call) + }; + cx.jobs.callback(callback); +} + /// Run the initialization script located at `$helix_config/init.scm` /// This runs the script in the global environment, and does _not_ load it as a module directly pub fn run_initialization_script(cx: &mut Context) { @@ -83,13 +139,18 @@ pub fn run_initialization_script(cx: &mut Context) { let helix_module_path = helix_loader::steel_init_file(); + // These contents need to be registered with the path? if let Ok(contents) = std::fs::read_to_string(&helix_module_path) { - ENGINE.with(|x| { + let res = ENGINE.with(|x| { x.borrow_mut() .run_with_reference::(cx, "*helix.cx*", &contents) - .unwrap() }); + match res { + Ok(_) => {} + Err(e) => present_error(cx, e), + } + log::info!("Finished loading init.scm!") } else { log::info!("No init.scm found, skipping loading.") @@ -99,6 +160,8 @@ pub fn run_initialization_script(cx: &mut Context) { // configure_background_thread() } +// pub static MINOR_MODES: Lazy = Lazy::new(|| SharedKeyBindingsEventQueue::new()); @@ -221,78 +284,7 @@ fn get_themes(cx: &mut Context) -> Vec { // } /// A dynamic component, used for rendering thing -#[derive(Clone)] -// TODO: Implement `trace` method for objects that hold steel vals -struct SteelDynamicComponent { - name: String, - // This _should_ be a struct, but in theory can be whatever you want. It will be the first argument - // passed to the functions in the remainder of the struct. - state: SteelVal, - handle_event: Option, - should_update: Option, - render: SteelVal, - cursor: Option, - required_size: Option, -} - -impl SteelDynamicComponent { - fn new(name: String, state: SteelVal, render: SteelVal, h: HashMap) -> Self { - // if let SteelVal::HashMapV(h) = functions { - - Self { - name, - state, - render, - handle_event: h.get("handle_event").cloned(), - should_update: h.get("should_update").cloned(), - cursor: h.get("cursor").cloned(), - required_size: h.get("required_size").cloned(), - } - - // } else { - // panic!("Implement better error handling") - // } - } - - fn new_dyn( - name: String, - state: SteelVal, - render: SteelVal, - h: HashMap, - ) -> WrappedDynComponent { - let s = Self::new(name, state, render, h); - - WrappedDynComponent { - inner: Some(Box::new(s)), - } - } - - fn get_state(&self) -> SteelVal { - self.state.clone() - } - fn get_render(&self) -> SteelVal { - self.render.clone() - } - - fn get_handle_event(&self) -> Option { - self.handle_event.clone() - } - - fn get_should_update(&self) -> Option { - self.should_update.clone() - } - - fn get_cursor(&self) -> Option { - self.cursor.clone() - } - - fn get_required_size(&self) -> Option { - self.required_size.clone() - } -} - -impl Custom for SteelDynamicComponent {} impl Custom for compositor::EventResult {} impl FromSteelVal for compositor::EventResult { fn from_steelval(val: &SteelVal) -> steel::rvals::Result { @@ -315,189 +307,8 @@ impl FromSteelVal for compositor::EventResult { // TODO: Call the function inside the component, using the global engine. Consider running in its own engine // but leaving it all in the same one is kinda nice -impl Component for SteelDynamicComponent { - fn render( - &mut self, - area: helix_view::graphics::Rect, - frame: &mut tui::buffer::Buffer, - ctx: &mut compositor::Context, - ) { - let mut ctx = Context { - register: None, - count: None, - editor: ctx.editor, - callback: None, - on_next_key_callback: None, - jobs: ctx.jobs, - }; - - // Pass the `state` object through - this can be used for storing the state of whatever plugin thing we're - // attempting to render - let thunk = |engine: &mut Engine, f, c| { - engine.call_function_with_args( - self.render.clone(), - vec![self.state.clone(), area.into_steelval().unwrap(), f, c], - ) - }; - - ENGINE - .with(|x| { - x.borrow_mut() - .with_mut_reference::(frame) - .with_mut_reference::(&mut ctx) - .consume(|engine, args| { - let mut arg_iter = args.into_iter(); - - (thunk)(engine, arg_iter.next().unwrap(), arg_iter.next().unwrap()) - }) - - // .run_with_references::( - // frame, &mut ctx, thunk, - // ) - }) - .unwrap(); - - log::info!("Calling dynamic render!"); - } - - // TODO: Pass in event as well? Need to have immutable reference type - // Otherwise, we're gonna be in a bad spot. For now - just clone the object and pass it through. - // Clong is _not_ ideal, but it might be all we can do for now. - fn handle_event( - &mut self, - event: &helix_view::input::Event, - ctx: &mut compositor::Context, - ) -> compositor::EventResult { - if let Some(handle_event) = &mut self.handle_event { - let mut ctx = Context { - register: None, - count: None, - editor: ctx.editor, - callback: None, - on_next_key_callback: None, - jobs: ctx.jobs, - }; - - // Pass the `state` object through - this can be used for storing the state of whatever plugin thing we're - // attempting to render - let thunk = |engine: &mut Engine, c| { - engine.call_function_with_args( - handle_event.clone(), - vec![ - self.state.clone(), - // TODO: We do _not_ want to clone here, we would need to create a bunch of methods on the engine for various - // combinations of reference passing to do this safely. Right now its limited to mutable references, but we should - // expose more - investigate macros on how to do that with recursively crunching the list to generate the combinations. - // Experimentation needed. - event.clone().into_steelval().unwrap(), - c, - ], - ) - }; - - match ENGINE.with(|x| { - x.borrow_mut() - .run_thunk_with_reference::(&mut ctx, thunk) - }) { - Ok(v) => compositor::EventResult::from_steelval(&v) - .unwrap_or_else(|_| compositor::EventResult::Ignored(None)), - Err(_) => compositor::EventResult::Ignored(None), - } - } else { - compositor::EventResult::Ignored(None) - } - } - - fn should_update(&self) -> bool { - if let Some(should_update) = &self.should_update { - match ENGINE.with(|x| { - x.borrow_mut() - .call_function_with_args(should_update.clone(), vec![self.state.clone()]) - }) { - Ok(v) => bool::from_steelval(&v).unwrap_or(true), - Err(_) => true, - } - } else { - true - } - } - - // TODO: Implement immutable references. Right now I'm only supporting mutable references. - fn cursor( - &self, - area: helix_view::graphics::Rect, - ctx: &Editor, - ) -> ( - Option, - helix_view::graphics::CursorKind, - ) { - if let Some(cursor) = &self.cursor { - // Pass the `state` object through - this can be used for storing the state of whatever plugin thing we're - // attempting to render - let thunk = |engine: &mut Engine, e| { - engine.call_function_with_args( - cursor.clone(), - vec![self.state.clone(), area.into_steelval().unwrap(), e], - ) - }; - - <( - Option, - helix_view::graphics::CursorKind, - )>::from_steelval(&ENGINE.with(|x| { - x.borrow_mut() - .run_thunk_with_ro_reference::(ctx, thunk) - .unwrap() - })) - .unwrap() - } else { - (None, helix_view::graphics::CursorKind::Hidden) - } - } - - fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { - let name = self.type_name(); - - if let Some(required_size) = &mut self.required_size { - log::info!("Calling required-size inside: {}", name); - - // TODO: Create some token that we can grab to enqueue function calls internally. Referencing - // the external API would cause problems - we just need to include a handle to the interpreter - // instance. Something like: - // ENGINE.call_function_or_enqueue? OR - this is the externally facing render function. Internal - // render calls do _not_ go through this interface. Instead, they are just called directly. - // - // If we go through this interface, we're going to get an already borrowed mut error, since it is - // re-entrant attempting to grab the ENGINE instead mutably, since we have to break the recursion - // somehow. By putting it at the edge, we then say - hey for these functions on this interface, - // call the engine instance. Otherwise, all computation happens inside the engine. - let res = ENGINE - .with(|x| { - x.borrow_mut().call_function_with_args( - required_size.clone(), - vec![self.state.clone(), viewport.into_steelval().unwrap()], - ) - }) - .and_then(|x| Option::<(u16, u16)>::from_steelval(&x)) - .unwrap(); - - res - } else { - None - } - } - - fn type_name(&self) -> &'static str { - std::any::type_name::() - } - - fn id(&self) -> Option<&'static str> { - None - } -} // Does this work? -impl Custom for Box {} struct WrappedDynComponent { inner: Option>, @@ -714,6 +525,29 @@ fn configure_engine() -> std::rc::Rc::register_fn(&mut engine, "cx-editor!", get_editor); + engine.register_fn("editor-focus", current_focus); + engine.register_fn("editor->doc-id", get_document_id); + // engine.register_fn("editor->get-document", get_document); + + // TODO: These are some horrendous type annotations, however... they do work? + // If the type annotations are a bit more ergonomic, we might be able to get away with this + // (i.e. if they're sensible enough) + RegisterFn::< + _, + steel::steel_vm::register_fn::MarkerWrapper8<( + helix_view::Editor, + DocumentId, + Document, + Document, + helix_view::Editor, + )>, + Document, + >::register_fn(&mut engine, "editor->get-document", get_document); + + // Check if the doc exists first + engine.register_fn("editor-doc-exists?", document_exists); + engine.register_fn("Document-path", document_path); + // RegisterFn::< // _, // steel::steel_vm::register_fn::MarkerWrapper7<( @@ -838,6 +672,8 @@ fn configure_engine() -> std::rc::Rccurrent-file", current_path); + engine.register_module(module); engine.register_fn("push-component!", push_component); @@ -1031,6 +867,44 @@ fn get_init_scm_path() -> String { .to_string() } +/// Get the current path! See if this can be done _without_ this function? +// TODO: +fn current_path(cx: &mut Context) -> Option { + let current_focus = cx.editor.tree.focus; + let view = cx.editor.tree.get(current_focus); + let doc = &view.doc; + // Lifetime of this needs to be tied to the existing document + let current_doc = cx.editor.documents.get(doc); + current_doc.and_then(|x| x.path().and_then(|x| x.to_str().map(|x| x.to_string()))) +} + +// TODO: Expose the below in a separate module, make things a bit more clear! + +fn current_focus(editor: &mut Editor) -> helix_view::ViewId { + editor.tree.focus +} + +// Get the document id +fn get_document_id(editor: &mut Editor, view_id: helix_view::ViewId) -> DocumentId { + 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 document_exists(editor: &mut Editor, doc_id: DocumentId) -> bool { + editor.documents.get(&doc_id).is_some() +} + +fn document_path(doc: &Document) -> Option { + doc.path().and_then(|x| x.to_str()).map(|x| x.to_string()) +} + +// cx->editor +// + fn run_shell_command_text( cx: &mut Context, args: &[Cow], diff --git a/helix-term/src/commands/engine/components.rs b/helix-term/src/commands/engine/components.rs new file mode 100644 index 000000000..6b46f1d2f --- /dev/null +++ b/helix-term/src/commands/engine/components.rs @@ -0,0 +1,463 @@ +use std::collections::HashMap; + +use helix_view::Editor; +use steel::{ + rvals::{Custom, FromSteelVal, IntoSteelVal}, + steel_vm::{builtin::BuiltInModule, engine::Engine, register_fn::RegisterFn}, + SteelVal, +}; + +use crate::{ + commands::{engine::ENGINE, Context}, + compositor::{self, Component}, + ui::Popup, +}; + +pub fn helix_component_module() -> BuiltInModule { + let mut module = BuiltInModule::new("helix/components".to_string()); + + module.register_fn("new-component!", SteelDynamicComponent::new_dyn); + + module.register_fn("SteelDynamicComponent?", |object: SteelVal| { + if let SteelVal::Custom(v) = object { + if let Some(wrapped) = v.borrow().as_any_ref().downcast_ref::() { + return wrapped.inner.as_any().is::(); + } else { + false + } + } else { + false + } + }); + + module.register_fn( + "SteelDynamicComponent-state", + SteelDynamicComponent::get_state, + ); + module.register_fn( + "SteelDynamicComponent-render", + SteelDynamicComponent::get_render, + ); + module.register_fn( + "SteelDynamicComponent-handle-event", + SteelDynamicComponent::get_handle_event, + ); + module.register_fn( + "SteelDynamicComponent-should-update", + SteelDynamicComponent::should_update, + ); + module.register_fn( + "SteelDynamicComponent-cursor", + SteelDynamicComponent::cursor, + ); + module.register_fn( + "SteelDynamicComponent-required-size", + SteelDynamicComponent::get_required_size, + ); + + // engine.register_fn("WrappedComponent", WrappedDynComponent::new) + + module.register_fn( + "Popup::new", + |contents: &mut WrappedDynComponent, + position: helix_core::Position| + -> WrappedDynComponent { + let inner = contents.inner.take().unwrap(); // Panic, for now + + WrappedDynComponent { + inner: Some(Box::new( + Popup::::new("popup", BoxDynComponent::new(inner)) + .position(Some(position)), + )), + } + }, + ); + + module.register_fn("Picker::new", |values: Vec| todo!()); + + // engine.register_fn( + // "Picker::new", + // |contents: &mut Wrapped + // ) + + module.register_fn("Component::Text", |contents: String| WrappedDynComponent { + inner: Some(Box::new(crate::ui::Text::new(contents))), + }); + + // Separate this out into its own component module - This just lets us call the underlying + // component, not sure if we can go from trait object -> trait object easily but we'll see! + module.register_fn( + "Component::render", + |t: &mut WrappedDynComponent, + area: helix_view::graphics::Rect, + frame: &mut tui::buffer::Buffer, + ctx: &mut Context| { + t.inner.as_mut().unwrap().render( + area, + frame, + &mut compositor::Context { + jobs: ctx.jobs, + editor: ctx.editor, + scroll: None, + }, + ) + }, + ); + + module.register_fn( + "Component::handle-event", + |s: &mut WrappedDynComponent, event: &helix_view::input::Event, ctx: &mut Context| { + s.inner.as_mut().unwrap().handle_event( + event, + &mut compositor::Context { + jobs: ctx.jobs, + editor: ctx.editor, + scroll: None, + }, + ) + }, + ); + + module.register_fn("Component::should-update", |s: &mut WrappedDynComponent| { + s.inner.as_mut().unwrap().should_update() + }); + + module.register_fn( + "Component::cursor", + |s: &WrappedDynComponent, area: helix_view::graphics::Rect, ctx: &Editor| { + s.inner.as_ref().unwrap().cursor(area, ctx) + }, + ); + + module.register_fn( + "Component::required-size", + |s: &mut WrappedDynComponent, viewport: (u16, u16)| { + s.inner.as_mut().unwrap().required_size(viewport) + }, + ); + + module +} + +/// A dynamic component, used for rendering thing +#[derive(Clone)] +// TODO: Implement `trace` method for objects that hold steel vals +pub struct SteelDynamicComponent { + name: String, + // This _should_ be a struct, but in theory can be whatever you want. It will be the first argument + // passed to the functions in the remainder of the struct. + state: SteelVal, + handle_event: Option, + should_update: Option, + render: SteelVal, + cursor: Option, + required_size: Option, +} + +impl SteelDynamicComponent { + pub fn new( + name: String, + state: SteelVal, + render: SteelVal, + h: HashMap, + ) -> Self { + // if let SteelVal::HashMapV(h) = functions { + + Self { + name, + state, + render, + handle_event: h.get("handle_event").cloned(), + should_update: h.get("should_update").cloned(), + cursor: h.get("cursor").cloned(), + required_size: h.get("required_size").cloned(), + } + + // } else { + // panic!("Implement better error handling") + // } + } + + pub fn new_dyn( + name: String, + state: SteelVal, + render: SteelVal, + h: HashMap, + ) -> WrappedDynComponent { + let s = Self::new(name, state, render, h); + + WrappedDynComponent { + inner: Some(Box::new(s)), + } + } + + pub fn get_state(&self) -> SteelVal { + self.state.clone() + } + + pub fn get_render(&self) -> SteelVal { + self.render.clone() + } + + pub fn get_handle_event(&self) -> Option { + self.handle_event.clone() + } + + pub fn get_should_update(&self) -> Option { + self.should_update.clone() + } + + pub fn get_cursor(&self) -> Option { + self.cursor.clone() + } + + pub fn get_required_size(&self) -> Option { + self.required_size.clone() + } +} + +impl Custom for SteelDynamicComponent {} + +impl Custom for Box {} + +pub struct WrappedDynComponent { + inner: Option>, +} + +impl Custom for WrappedDynComponent {} + +struct BoxDynComponent { + inner: Box, +} + +impl BoxDynComponent { + pub fn new(inner: Box) -> Self { + Self { inner } + } +} + +impl Component for BoxDynComponent { + fn handle_event( + &mut self, + _event: &helix_view::input::Event, + _ctx: &mut compositor::Context, + ) -> compositor::EventResult { + self.inner.handle_event(_event, _ctx) + } + + fn should_update(&self) -> bool { + self.inner.should_update() + } + + fn cursor( + &self, + _area: helix_view::graphics::Rect, + _ctx: &Editor, + ) -> ( + Option, + helix_view::graphics::CursorKind, + ) { + self.inner.cursor(_area, _ctx) + } + + fn required_size(&mut self, _viewport: (u16, u16)) -> Option<(u16, u16)> { + self.inner.required_size(_viewport) + } + + fn type_name(&self) -> &'static str { + std::any::type_name::() + } + + fn id(&self) -> Option<&'static str> { + None + } + + fn render( + &mut self, + area: helix_view::graphics::Rect, + frame: &mut tui::buffer::Buffer, + ctx: &mut compositor::Context, + ) { + self.inner.render(area, frame, ctx) + } +} +impl Component for SteelDynamicComponent { + fn render( + &mut self, + area: helix_view::graphics::Rect, + frame: &mut tui::buffer::Buffer, + ctx: &mut compositor::Context, + ) { + let mut ctx = Context { + register: None, + count: None, + editor: ctx.editor, + callback: None, + on_next_key_callback: None, + jobs: ctx.jobs, + }; + + // Pass the `state` object through - this can be used for storing the state of whatever plugin thing we're + // attempting to render + let thunk = |engine: &mut Engine, f, c| { + engine.call_function_with_args( + self.render.clone(), + vec![self.state.clone(), area.into_steelval().unwrap(), f, c], + ) + }; + + ENGINE + .with(|x| { + x.borrow_mut() + .with_mut_reference::(frame) + .with_mut_reference::(&mut ctx) + .consume(|engine, args| { + let mut arg_iter = args.into_iter(); + + (thunk)(engine, arg_iter.next().unwrap(), arg_iter.next().unwrap()) + }) + + // .run_with_references::( + // frame, &mut ctx, thunk, + // ) + }) + .unwrap(); + + log::info!("Calling dynamic render!"); + } + + // TODO: Pass in event as well? Need to have immutable reference type + // Otherwise, we're gonna be in a bad spot. For now - just clone the object and pass it through. + // Clong is _not_ ideal, but it might be all we can do for now. + fn handle_event( + &mut self, + event: &helix_view::input::Event, + ctx: &mut compositor::Context, + ) -> compositor::EventResult { + if let Some(handle_event) = &mut self.handle_event { + let mut ctx = Context { + register: None, + count: None, + editor: ctx.editor, + callback: None, + on_next_key_callback: None, + jobs: ctx.jobs, + }; + + // Pass the `state` object through - this can be used for storing the state of whatever plugin thing we're + // attempting to render + let thunk = |engine: &mut Engine, c| { + engine.call_function_with_args( + handle_event.clone(), + vec![ + self.state.clone(), + // TODO: We do _not_ want to clone here, we would need to create a bunch of methods on the engine for various + // combinations of reference passing to do this safely. Right now its limited to mutable references, but we should + // expose more - investigate macros on how to do that with recursively crunching the list to generate the combinations. + // Experimentation needed. + event.clone().into_steelval().unwrap(), + c, + ], + ) + }; + + match ENGINE.with(|x| { + x.borrow_mut() + .run_thunk_with_reference::(&mut ctx, thunk) + }) { + Ok(v) => compositor::EventResult::from_steelval(&v) + .unwrap_or_else(|_| compositor::EventResult::Ignored(None)), + Err(_) => compositor::EventResult::Ignored(None), + } + } else { + compositor::EventResult::Ignored(None) + } + } + + fn should_update(&self) -> bool { + if let Some(should_update) = &self.should_update { + match ENGINE.with(|x| { + x.borrow_mut() + .call_function_with_args(should_update.clone(), vec![self.state.clone()]) + }) { + Ok(v) => bool::from_steelval(&v).unwrap_or(true), + Err(_) => true, + } + } else { + true + } + } + + // TODO: Implement immutable references. Right now I'm only supporting mutable references. + fn cursor( + &self, + area: helix_view::graphics::Rect, + ctx: &Editor, + ) -> ( + Option, + helix_view::graphics::CursorKind, + ) { + if let Some(cursor) = &self.cursor { + // Pass the `state` object through - this can be used for storing the state of whatever plugin thing we're + // attempting to render + let thunk = |engine: &mut Engine, e| { + engine.call_function_with_args( + cursor.clone(), + vec![self.state.clone(), area.into_steelval().unwrap(), e], + ) + }; + + <( + Option, + helix_view::graphics::CursorKind, + )>::from_steelval(&ENGINE.with(|x| { + x.borrow_mut() + .run_thunk_with_ro_reference::(ctx, thunk) + .unwrap() + })) + .unwrap() + } else { + (None, helix_view::graphics::CursorKind::Hidden) + } + } + + fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { + let name = self.type_name(); + + if let Some(required_size) = &mut self.required_size { + log::info!("Calling required-size inside: {}", name); + + // TODO: Create some token that we can grab to enqueue function calls internally. Referencing + // the external API would cause problems - we just need to include a handle to the interpreter + // instance. Something like: + // ENGINE.call_function_or_enqueue? OR - this is the externally facing render function. Internal + // render calls do _not_ go through this interface. Instead, they are just called directly. + // + // If we go through this interface, we're going to get an already borrowed mut error, since it is + // re-entrant attempting to grab the ENGINE instead mutably, since we have to break the recursion + // somehow. By putting it at the edge, we then say - hey for these functions on this interface, + // call the engine instance. Otherwise, all computation happens inside the engine. + let res = ENGINE + .with(|x| { + x.borrow_mut().call_function_with_args( + required_size.clone(), + vec![self.state.clone(), viewport.into_steelval().unwrap()], + ) + }) + .and_then(|x| Option::<(u16, u16)>::from_steelval(&x)) + .unwrap(); + + res + } else { + None + } + } + + fn type_name(&self) -> &'static str { + std::any::type_name::() + } + + fn id(&self) -> Option<&'static str> { + None + } +} diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 6638b532f..dcccf037f 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -2,6 +2,7 @@ use std::ops::Deref; use crate::job::Job; +use super::engine::{compositor_present_error, present_error}; use super::*; use helix_core::{encoding, shellwords::Shellwords}; @@ -2920,7 +2921,7 @@ pub(super) fn command_mode(cx: &mut Context) { // We're finalizing the event - we actually want to call the function if event == PromptEvent::Validate { - // TODO: @Matt - extract this whole API cal here to just be inside the engine module + // TODO: @Matt - extract this whole API call here to just be inside the engine module // For what its worth, also explore a more elegant API for calling apply with some arguments, // this does work, but its a little opaque. if let Err(e) = ENGINE.with(|x| { @@ -2957,7 +2958,7 @@ pub(super) fn command_mode(cx: &mut Context) { res } }) { - cx.editor.set_error(format!("{}", e)); + compositor_present_error(cx, e) }; } } else if event == PromptEvent::Validate { diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index d06be8898..ee15b1c77 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -127,24 +127,12 @@ impl Config { Ok(res) } - // TODO: @Matt -> Add key binding here by reading value from steel engine pub fn load_default() -> Result { let global_config = fs::read_to_string(helix_loader::config_file()).map_err(ConfigLoadError::Error); let local_config = fs::read_to_string(helix_loader::workspace_config_file()) .map_err(ConfigLoadError::Error); - // let binding = crate::commands::ENGINE.with(|x| { - // x.borrow_mut() - // .run("(value->jsexpr-string *KEYBINDINGS*)") - // .unwrap() - // }); - // let keybindings_as_str = binding[0] - // .string_or_else(|| panic!("Should always be a string")) - // .unwrap(); - - // let bindings: HashMap = serde_json::from_str(&keybindings_as_str).unwrap(); - let bindings = crate::commands::engine::SharedKeyBindingsEventQueue::get(); Config::load(global_config, local_config, bindings) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index b644316a6..3033c6a48 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -143,9 +143,6 @@ impl DerefMut for KeyTrieNode { } } -// TODO: impl FromSteelVal and IntoSteelVal for this - or expose methods -// that allow Steel to integrate with the keybindings on the editor - #[derive(Debug, Clone, PartialEq)] pub enum KeyTrie { Leaf(MappableCommand), diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index fd8e8fb21..4093827af 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -796,6 +796,18 @@ impl EditorView { let key_result = self.keymaps.get(mode, event); cxt.editor.autoinfo = self.keymaps.sticky().map(|node| node.infobox()); + // Get the currently activated minor modes + // let extension = { + // let current_focus = cxt.editor.tree.focus; + // let view = cxt.editor.tree.get(current_focus); + // let doc = &view.doc; + // let current_doc = cxt.editor.documents.get(doc); + + // current_doc + // .and_then(|x| x.path()) + // .and_then(|x| x.extension()); + // }; + let mut execute_command = |command: &commands::MappableCommand| { command.execute(cxt); let current_mode = cxt.editor.mode(); @@ -832,6 +844,20 @@ impl EditorView { match &key_result { KeymapResult::Matched(command) => { + // TODO: @Matt - check minor modes here. + // Check current path: + + // let current_focus = cxt.editor.tree.focus; + // let view = cxt.editor.tree.get(current_focus); + // let doc = &view.doc; + // let current_doc = cxt.editor.documents.get(doc); + + // let extension = + + // current_doc.and_then(|x| x.path().and_then(|x| x.to_str().map(|x| x.to_string()))) + + // cxt.editor. + execute_command(command); } KeymapResult::Pending(node) => cxt.editor.autoinfo = Some(node.infobox()), diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 000f9bb7b..526b9958d 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1190,6 +1190,8 @@ impl Editor { pub fn switch(&mut self, id: DocumentId, action: Action) { use crate::tree::Layout; + log::info!("Switching view: {:?}", id); + if !self.documents.contains_key(&id) { log::error!("cannot switch to document that does not exist (anymore)"); return; @@ -1472,6 +1474,8 @@ impl Editor { // if leaving the view: mode should reset and the cursor should be // within view if prev_id != view_id { + log::info!("Changing focus: {:?}", view_id); + self.enter_normal_mode(); self.ensure_cursor_in_view(view_id); diff --git a/helix-view/src/extension.rs b/helix-view/src/extension.rs index 39bcb3471..6aaa290e4 100644 --- a/helix-view/src/extension.rs +++ b/helix-view/src/extension.rs @@ -1,7 +1,11 @@ use steel::{gc::unsafe_erased_pointers::CustomReference, rvals::Custom}; -use crate::{graphics::Rect, input::Event}; +use crate::{graphics::Rect, input::Event, Document, DocumentId, ViewId}; +// Reference types along with value types - This should allow for having users impl CustomReference for Event {} impl Custom for Rect {} impl Custom for crate::graphics::CursorKind {} +impl Custom for DocumentId {} +impl Custom for ViewId {} +impl CustomReference for Document {}