more reworking

pull/8675/merge^2
mattwparas 1 year ago
parent 15886dec3f
commit 4213328ebd

@ -1 +1,23 @@
impl steel::rvals::Custom for crate::Position {} 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()
}
}

@ -915,18 +915,22 @@ fn call_indent_hook(
None 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. /// 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. /// Problem is - the issues with the `Any` type and using things with type id.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn custom_indent_for_newline( pub fn custom_indent_for_newline(
language_config: Option<&LanguageConfiguration>, language_config: Option<&LanguageConfiguration>,
syntax: Option<&Syntax>, _syntax: Option<&Syntax>,
indent_style: &IndentStyle, _indent_style: &IndentStyle,
tab_width: usize, _tab_width: usize,
text: RopeSlice, text: RopeSlice,
line_before: usize, line_before: usize,
line_before_end_pos: usize, line_before_end_pos: usize,
current_line: usize, _current_line: usize,
) -> Option<String> { ) -> Option<String> {
if let Some(config) = language_config { if let Some(config) = language_config {
// TODO: If possible, this would be very cool to be implemented in steel itself. If not, // TODO: If possible, this would be very cool to be implemented in steel itself. If not,

@ -193,6 +193,7 @@ impl MappableCommand {
// TODO: @Matt - Add delegating to the engine to run scripts here // TODO: @Matt - Add delegating to the engine to run scripts here
Self::Typable { name, args, doc: _ } => { Self::Typable { name, args, doc: _ } => {
let args: Vec<Cow<str>> = args.iter().map(Cow::from).collect(); let args: Vec<Cow<str>> = 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()) { if let Some(command) = typed::TYPABLE_COMMAND_MAP.get(name.as_str()) {
let mut cx = compositor::Context { let mut cx = compositor::Context {
editor: cx.editor, editor: cx.editor,

@ -1,12 +1,12 @@
use fuzzy_matcher::FuzzyMatcher; use fuzzy_matcher::FuzzyMatcher;
use helix_core::{graphemes, Tendril}; 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 once_cell::sync::Lazy;
use steel::{ use steel::{
gc::unsafe_erased_pointers::CustomReference, gc::unsafe_erased_pointers::CustomReference,
rvals::{FromSteelVal, IntoSteelVal, SteelString}, rvals::{FromSteelVal, IntoSteelVal, SteelString},
steel_vm::{engine::Engine, register_fn::RegisterFn}, steel_vm::{engine::Engine, register_fn::RegisterFn},
SteelVal, SteelErr, SteelVal,
}; };
use std::{ use std::{
@ -28,6 +28,8 @@ use crate::{
ui::{self, menu::Item, overlay::overlaid, Popup, PromptEvent}, ui::{self, menu::Item, overlay::overlaid, Popup, PromptEvent},
}; };
use self::components::SteelDynamicComponent;
use super::{ use super::{
insert::{insert_char, insert_string}, insert::{insert_char, insert_string},
plugin::{DylibContainers, ExternalModule}, plugin::{DylibContainers, ExternalModule},
@ -43,6 +45,8 @@ pub struct ExternalContainersAndModules {
modules: Vec<ExternalModule>, modules: Vec<ExternalModule>,
} }
mod components;
// External modules that can load via rust dylib. These can then be consumed from // External modules that can load via rust dylib. These can then be consumed from
// steel as needed, via the standard FFI for plugin functions. // steel as needed, via the standard FFI for plugin functions.
pub(crate) static EXTERNAL_DYLIBS: Lazy<Arc<RwLock<ExternalContainersAndModules>>> = pub(crate) static EXTERNAL_DYLIBS: Lazy<Arc<RwLock<ExternalContainersAndModules>>> =
@ -76,6 +80,58 @@ pub fn initialize_engine() {
ENGINE.with(|x| x.borrow().globals().first().copied()); 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` /// 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 /// 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) { 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(); 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) { if let Ok(contents) = std::fs::read_to_string(&helix_module_path) {
ENGINE.with(|x| { let res = ENGINE.with(|x| {
x.borrow_mut() x.borrow_mut()
.run_with_reference::<Context, Context>(cx, "*helix.cx*", &contents) .run_with_reference::<Context, Context>(cx, "*helix.cx*", &contents)
.unwrap()
}); });
match res {
Ok(_) => {}
Err(e) => present_error(cx, e),
}
log::info!("Finished loading init.scm!") log::info!("Finished loading init.scm!")
} else { } else {
log::info!("No init.scm found, skipping loading.") log::info!("No init.scm found, skipping loading.")
@ -99,6 +160,8 @@ pub fn run_initialization_script(cx: &mut Context) {
// configure_background_thread() // configure_background_thread()
} }
// pub static MINOR_MODES: Lazy<Arc<RwLock<HashMap<String,
pub static KEYBINDING_QUEUE: Lazy<SharedKeyBindingsEventQueue> = pub static KEYBINDING_QUEUE: Lazy<SharedKeyBindingsEventQueue> =
Lazy::new(|| SharedKeyBindingsEventQueue::new()); Lazy::new(|| SharedKeyBindingsEventQueue::new());
@ -221,78 +284,7 @@ fn get_themes(cx: &mut Context) -> Vec<String> {
// } // }
/// A dynamic component, used for rendering thing /// 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<SteelVal>,
should_update: Option<SteelVal>,
render: SteelVal,
cursor: Option<SteelVal>,
required_size: Option<SteelVal>,
}
impl SteelDynamicComponent {
fn new(name: String, state: SteelVal, render: SteelVal, h: HashMap<String, SteelVal>) -> 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<String, SteelVal>,
) -> 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<SteelVal> {
self.handle_event.clone()
}
fn get_should_update(&self) -> Option<SteelVal> {
self.should_update.clone()
}
fn get_cursor(&self) -> Option<SteelVal> {
self.cursor.clone()
}
fn get_required_size(&self) -> Option<SteelVal> {
self.required_size.clone()
}
}
impl Custom for SteelDynamicComponent {}
impl Custom for compositor::EventResult {} impl Custom for compositor::EventResult {}
impl FromSteelVal for compositor::EventResult { impl FromSteelVal for compositor::EventResult {
fn from_steelval(val: &SteelVal) -> steel::rvals::Result<Self> { fn from_steelval(val: &SteelVal) -> steel::rvals::Result<Self> {
@ -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 // 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 // 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::<tui::buffer::Buffer, tui::buffer::Buffer>(frame)
.with_mut_reference::<Context, Context>(&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::<tui::buffer::Buffer, tui::buffer::Buffer, Context, Context>(
// 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::<Context, Context>(&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_core::Position>,
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_core::Position>,
helix_view::graphics::CursorKind,
)>::from_steelval(&ENGINE.with(|x| {
x.borrow_mut()
.run_thunk_with_ro_reference::<Editor, Editor>(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::<Self>()
}
fn id(&self) -> Option<&'static str> {
None
}
}
// Does this work? // Does this work?
impl Custom for Box<dyn Component> {}
struct WrappedDynComponent { struct WrappedDynComponent {
inner: Option<Box<dyn Component>>, inner: Option<Box<dyn Component>>,
@ -714,6 +525,29 @@ fn configure_engine() -> std::rc::Rc<std::cell::RefCell<steel::steel_vm::engine:
helix_view::Editor, helix_view::Editor,
>::register_fn(&mut engine, "cx-editor!", get_editor); >::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::< // RegisterFn::<
// _, // _,
// steel::steel_vm::register_fn::MarkerWrapper7<( // steel::steel_vm::register_fn::MarkerWrapper7<(
@ -838,6 +672,8 @@ fn configure_engine() -> std::rc::Rc<std::cell::RefCell<steel::steel_vm::engine:
module.register_fn("get-init-scm-path", get_init_scm_path); module.register_fn("get-init-scm-path", get_init_scm_path);
module.register_fn("block-on-shell-command", run_shell_command_text); module.register_fn("block-on-shell-command", run_shell_command_text);
module.register_fn("cx->current-file", current_path);
engine.register_module(module); engine.register_module(module);
engine.register_fn("push-component!", push_component); engine.register_fn("push-component!", push_component);
@ -1031,6 +867,44 @@ fn get_init_scm_path() -> String {
.to_string() .to_string()
} }
/// Get the current path! See if this can be done _without_ this function?
// TODO:
fn current_path(cx: &mut Context) -> Option<String> {
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<String> {
doc.path().and_then(|x| x.to_str()).map(|x| x.to_string())
}
// cx->editor
//
fn run_shell_command_text( fn run_shell_command_text(
cx: &mut Context, cx: &mut Context,
args: &[Cow<str>], args: &[Cow<str>],

@ -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::<BoxDynComponent>() {
return wrapped.inner.as_any().is::<SteelDynamicComponent>();
} 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::<BoxDynComponent>::new("popup", BoxDynComponent::new(inner))
.position(Some(position)),
)),
}
},
);
module.register_fn("Picker::new", |values: Vec<String>| 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<SteelVal>,
should_update: Option<SteelVal>,
render: SteelVal,
cursor: Option<SteelVal>,
required_size: Option<SteelVal>,
}
impl SteelDynamicComponent {
pub fn new(
name: String,
state: SteelVal,
render: SteelVal,
h: HashMap<String, SteelVal>,
) -> 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<String, SteelVal>,
) -> 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<SteelVal> {
self.handle_event.clone()
}
pub fn get_should_update(&self) -> Option<SteelVal> {
self.should_update.clone()
}
pub fn get_cursor(&self) -> Option<SteelVal> {
self.cursor.clone()
}
pub fn get_required_size(&self) -> Option<SteelVal> {
self.required_size.clone()
}
}
impl Custom for SteelDynamicComponent {}
impl Custom for Box<dyn Component> {}
pub struct WrappedDynComponent {
inner: Option<Box<dyn Component>>,
}
impl Custom for WrappedDynComponent {}
struct BoxDynComponent {
inner: Box<dyn Component>,
}
impl BoxDynComponent {
pub fn new(inner: Box<dyn Component>) -> 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_core::Position>,
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::<Self>()
}
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::<tui::buffer::Buffer, tui::buffer::Buffer>(frame)
.with_mut_reference::<Context, Context>(&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::<tui::buffer::Buffer, tui::buffer::Buffer, Context, Context>(
// 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::<Context, Context>(&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_core::Position>,
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_core::Position>,
helix_view::graphics::CursorKind,
)>::from_steelval(&ENGINE.with(|x| {
x.borrow_mut()
.run_thunk_with_ro_reference::<Editor, Editor>(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::<Self>()
}
fn id(&self) -> Option<&'static str> {
None
}
}

@ -2,6 +2,7 @@ use std::ops::Deref;
use crate::job::Job; use crate::job::Job;
use super::engine::{compositor_present_error, present_error};
use super::*; use super::*;
use helix_core::{encoding, shellwords::Shellwords}; 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 // We're finalizing the event - we actually want to call the function
if event == PromptEvent::Validate { 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, // For what its worth, also explore a more elegant API for calling apply with some arguments,
// this does work, but its a little opaque. // this does work, but its a little opaque.
if let Err(e) = ENGINE.with(|x| { if let Err(e) = ENGINE.with(|x| {
@ -2957,7 +2958,7 @@ pub(super) fn command_mode(cx: &mut Context) {
res res
} }
}) { }) {
cx.editor.set_error(format!("{}", e)); compositor_present_error(cx, e)
}; };
} }
} else if event == PromptEvent::Validate { } else if event == PromptEvent::Validate {

@ -127,24 +127,12 @@ impl Config {
Ok(res) Ok(res)
} }
// TODO: @Matt -> Add key binding here by reading value from steel engine
pub fn load_default() -> Result<Config, ConfigLoadError> { pub fn load_default() -> Result<Config, ConfigLoadError> {
let global_config = let global_config =
fs::read_to_string(helix_loader::config_file()).map_err(ConfigLoadError::Error); fs::read_to_string(helix_loader::config_file()).map_err(ConfigLoadError::Error);
let local_config = fs::read_to_string(helix_loader::workspace_config_file()) let local_config = fs::read_to_string(helix_loader::workspace_config_file())
.map_err(ConfigLoadError::Error); .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<Mode, Keymap> = serde_json::from_str(&keybindings_as_str).unwrap();
let bindings = crate::commands::engine::SharedKeyBindingsEventQueue::get(); let bindings = crate::commands::engine::SharedKeyBindingsEventQueue::get();
Config::load(global_config, local_config, bindings) Config::load(global_config, local_config, bindings)

@ -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)] #[derive(Debug, Clone, PartialEq)]
pub enum KeyTrie { pub enum KeyTrie {
Leaf(MappableCommand), Leaf(MappableCommand),

@ -796,6 +796,18 @@ impl EditorView {
let key_result = self.keymaps.get(mode, event); let key_result = self.keymaps.get(mode, event);
cxt.editor.autoinfo = self.keymaps.sticky().map(|node| node.infobox()); 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| { let mut execute_command = |command: &commands::MappableCommand| {
command.execute(cxt); command.execute(cxt);
let current_mode = cxt.editor.mode(); let current_mode = cxt.editor.mode();
@ -832,6 +844,20 @@ impl EditorView {
match &key_result { match &key_result {
KeymapResult::Matched(command) => { 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); execute_command(command);
} }
KeymapResult::Pending(node) => cxt.editor.autoinfo = Some(node.infobox()), KeymapResult::Pending(node) => cxt.editor.autoinfo = Some(node.infobox()),

@ -1190,6 +1190,8 @@ impl Editor {
pub fn switch(&mut self, id: DocumentId, action: Action) { pub fn switch(&mut self, id: DocumentId, action: Action) {
use crate::tree::Layout; use crate::tree::Layout;
log::info!("Switching view: {:?}", id);
if !self.documents.contains_key(&id) { if !self.documents.contains_key(&id) {
log::error!("cannot switch to document that does not exist (anymore)"); log::error!("cannot switch to document that does not exist (anymore)");
return; return;
@ -1472,6 +1474,8 @@ impl Editor {
// if leaving the view: mode should reset and the cursor should be // if leaving the view: mode should reset and the cursor should be
// within view // within view
if prev_id != view_id { if prev_id != view_id {
log::info!("Changing focus: {:?}", view_id);
self.enter_normal_mode(); self.enter_normal_mode();
self.ensure_cursor_in_view(view_id); self.ensure_cursor_in_view(view_id);

@ -1,7 +1,11 @@
use steel::{gc::unsafe_erased_pointers::CustomReference, rvals::Custom}; 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 CustomReference for Event {}
impl Custom for Rect {} impl Custom for Rect {}
impl Custom for crate::graphics::CursorKind {} impl Custom for crate::graphics::CursorKind {}
impl Custom for DocumentId {}
impl Custom for ViewId {}
impl CustomReference for Document {}

Loading…
Cancel
Save