mirror of https://github.com/helix-editor/helix
more reworking
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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…
Reference in New Issue