mirror of https://github.com/helix-editor/helix
reorganize top level to use precedence for multiple plugins
parent
3060bccc0c
commit
2d4bc31d54
|
@ -16,45 +16,68 @@ mod components;
|
||||||
#[cfg(feature = "steel")]
|
#[cfg(feature = "steel")]
|
||||||
pub mod scheme;
|
pub mod scheme;
|
||||||
|
|
||||||
// For now, we will allow _one_ embedded scripting engine to live inside of helix.
|
pub enum PluginSystemKind {
|
||||||
// In theory, we could allow multiple, with some kind of hierarchy where we check
|
None,
|
||||||
// each one, with some kind of precedence.
|
#[cfg(feature = "steel")]
|
||||||
struct PluginEngine<T: PluginSystem>(T);
|
Steel,
|
||||||
|
}
|
||||||
|
|
||||||
// This is where we can configure our system to use the correct one
|
pub enum PluginSystemTypes {
|
||||||
#[cfg(feature = "steel")]
|
None(NoEngine),
|
||||||
static PLUGIN_SYSTEM: PluginEngine<scheme::SteelScriptingEngine> =
|
#[cfg(feature = "steel")]
|
||||||
PluginEngine(scheme::SteelScriptingEngine);
|
Steel(scheme::SteelScriptingEngine),
|
||||||
|
}
|
||||||
#[cfg(not(feature = "steel"))]
|
|
||||||
/// The default plugin system used ends up with no ops for all of the behavior.
|
|
||||||
static PLUGIN_SYSTEM: PluginEngine<NoEngine> = PluginEngine(NoEngine);
|
|
||||||
|
|
||||||
// enum PluginSystemTypes {
|
|
||||||
// None,
|
|
||||||
// Steel,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// The order in which the plugins will be evaluated against - if we wanted to include, lets say `rhai`,
|
// The order in which the plugins will be evaluated against - if we wanted to include, lets say `rhai`,
|
||||||
// we would have to order the precedence for searching for exported commands, or somehow merge them?
|
// we would have to order the precedence for searching for exported commands, or somehow merge them?
|
||||||
// static PLUGIN_PRECEDENCE: &[PluginSystemTypes] = &[PluginSystemTypes::Steel];
|
const PLUGIN_PRECEDENCE: &[PluginSystemTypes] = &[
|
||||||
|
#[cfg(feature = "steel")]
|
||||||
|
PluginSystemTypes::Steel(scheme::SteelScriptingEngine),
|
||||||
|
PluginSystemTypes::None(NoEngine),
|
||||||
|
];
|
||||||
|
|
||||||
pub struct NoEngine;
|
pub struct NoEngine;
|
||||||
|
|
||||||
// This will be the boundary layer between the editor and the engine.
|
// This will be the boundary layer between the editor and the engine.
|
||||||
pub struct ScriptingEngine;
|
pub struct ScriptingEngine;
|
||||||
|
|
||||||
|
macro_rules! manual_dispatch {
|
||||||
|
($kind:expr, $raw:tt ($($args:expr),* $(,)?) ) => {
|
||||||
|
match $kind {
|
||||||
|
PluginSystemTypes::None(n) => n.$raw($($args),*),
|
||||||
|
#[cfg(feature = "steel")]
|
||||||
|
PluginSystemTypes::Steel(s) => s.$raw($($args),*),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
impl ScriptingEngine {
|
impl ScriptingEngine {
|
||||||
pub fn initialize() {
|
pub fn initialize() {
|
||||||
PLUGIN_SYSTEM.0.initialize();
|
for kind in PLUGIN_PRECEDENCE {
|
||||||
|
manual_dispatch!(kind, initialize())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_initialization_script(cx: &mut Context) {
|
pub fn run_initialization_script(cx: &mut Context) {
|
||||||
PLUGIN_SYSTEM.0.run_initialization_script(cx);
|
for kind in PLUGIN_PRECEDENCE {
|
||||||
|
manual_dispatch!(kind, run_initialization_script(cx))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_keybindings() -> Option<HashMap<Mode, KeyTrie>> {
|
pub fn get_keybindings() -> Option<HashMap<Mode, KeyTrie>> {
|
||||||
PLUGIN_SYSTEM.0.get_keybindings()
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
|
for kind in PLUGIN_PRECEDENCE {
|
||||||
|
if let Some(keybindings) = manual_dispatch!(kind, get_keybindings()) {
|
||||||
|
map.extend(keybindings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if map.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(map)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_keymap_event(
|
pub fn handle_keymap_event(
|
||||||
|
@ -62,10 +85,16 @@ impl ScriptingEngine {
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
cxt: &mut Context,
|
cxt: &mut Context,
|
||||||
event: KeyEvent,
|
event: KeyEvent,
|
||||||
) -> KeymapResult {
|
) -> Option<KeymapResult> {
|
||||||
PLUGIN_SYSTEM
|
for kind in PLUGIN_PRECEDENCE {
|
||||||
.0
|
let res = manual_dispatch!(kind, handle_keymap_event(editor, mode, cxt, event));
|
||||||
.handle_keymap_event(editor, mode, cxt, event)
|
|
||||||
|
if res.is_some() {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn call_function_if_global_exists(
|
pub fn call_function_if_global_exists(
|
||||||
|
@ -73,9 +102,13 @@ impl ScriptingEngine {
|
||||||
name: &str,
|
name: &str,
|
||||||
args: Vec<Cow<str>>,
|
args: Vec<Cow<str>>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
PLUGIN_SYSTEM
|
for kind in PLUGIN_PRECEDENCE {
|
||||||
.0
|
if manual_dispatch!(kind, call_function_if_global_exists(cx, name, &args)) {
|
||||||
.call_function_if_global_exists(cx, name, args)
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn call_typed_command_if_global_exists<'a>(
|
pub fn call_typed_command_if_global_exists<'a>(
|
||||||
|
@ -84,24 +117,46 @@ impl ScriptingEngine {
|
||||||
parts: &'a [&'a str],
|
parts: &'a [&'a str],
|
||||||
event: PromptEvent,
|
event: PromptEvent,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
PLUGIN_SYSTEM
|
for kind in PLUGIN_PRECEDENCE {
|
||||||
.0
|
if manual_dispatch!(
|
||||||
.call_typed_command_if_global_exists(cx, input, parts, event)
|
kind,
|
||||||
|
call_typed_command_if_global_exists(cx, input, parts, event)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_doc_for_identifier(ident: &str) -> Option<String> {
|
pub fn get_doc_for_identifier(ident: &str) -> Option<String> {
|
||||||
PLUGIN_SYSTEM.0.get_doc_for_identifier(ident)
|
for kind in PLUGIN_PRECEDENCE {
|
||||||
|
let doc = manual_dispatch!(kind, get_doc_for_identifier(ident));
|
||||||
|
|
||||||
|
if doc.is_some() {
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fuzzy_match<'a>(
|
pub fn fuzzy_match<'a>(
|
||||||
fuzzy_matcher: &'a fuzzy_matcher::skim::SkimMatcherV2,
|
fuzzy_matcher: &'a fuzzy_matcher::skim::SkimMatcherV2,
|
||||||
input: &'a str,
|
input: &'a str,
|
||||||
) -> Vec<(String, i64)> {
|
) -> Vec<(String, i64)> {
|
||||||
PLUGIN_SYSTEM.0.fuzzy_match(fuzzy_matcher, input)
|
PLUGIN_PRECEDENCE
|
||||||
|
.iter()
|
||||||
|
.flat_map(|kind| manual_dispatch!(kind, fuzzy_match(fuzzy_matcher, input)))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PluginSystem for NoEngine {}
|
impl PluginSystem for NoEngine {
|
||||||
|
fn engine_name(&self) -> PluginSystemKind {
|
||||||
|
PluginSystemKind::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// These methods are the main entry point for interaction with the rest of
|
/// These methods are the main entry point for interaction with the rest of
|
||||||
/// the editor system.
|
/// the editor system.
|
||||||
|
@ -110,6 +165,8 @@ pub trait PluginSystem {
|
||||||
/// this is done here. This is run before the context is available.
|
/// this is done here. This is run before the context is available.
|
||||||
fn initialize(&self) {}
|
fn initialize(&self) {}
|
||||||
|
|
||||||
|
fn engine_name(&self) -> PluginSystemKind;
|
||||||
|
|
||||||
/// Post initialization, once the context is available. This means you should be able to
|
/// Post initialization, once the context is available. This means you should be able to
|
||||||
/// run anything here that could modify the context before the main editor is available.
|
/// run anything here that could modify the context before the main editor is available.
|
||||||
fn run_initialization_script(&self, _cx: &mut Context) {}
|
fn run_initialization_script(&self, _cx: &mut Context) {}
|
||||||
|
@ -129,8 +186,8 @@ pub trait PluginSystem {
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
_cxt: &mut Context,
|
_cxt: &mut Context,
|
||||||
event: KeyEvent,
|
event: KeyEvent,
|
||||||
) -> KeymapResult {
|
) -> Option<KeymapResult> {
|
||||||
editor.keymaps.get(mode, event)
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This attempts to call a function in the engine with the name `name` using the args `args`. The context
|
/// This attempts to call a function in the engine with the name `name` using the args `args`. The context
|
||||||
|
@ -139,7 +196,7 @@ pub trait PluginSystem {
|
||||||
&self,
|
&self,
|
||||||
_cx: &mut Context,
|
_cx: &mut Context,
|
||||||
_name: &str,
|
_name: &str,
|
||||||
_args: Vec<Cow<str>>,
|
_args: &[Cow<str>],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,6 +191,10 @@ impl super::PluginSystem for SteelScriptingEngine {
|
||||||
initialize_engine();
|
initialize_engine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn engine_name(&self) -> super::PluginSystemKind {
|
||||||
|
super::PluginSystemKind::Steel
|
||||||
|
}
|
||||||
|
|
||||||
fn run_initialization_script(&self, cx: &mut Context) {
|
fn run_initialization_script(&self, cx: &mut Context) {
|
||||||
run_initialization_script(cx);
|
run_initialization_script(cx);
|
||||||
}
|
}
|
||||||
|
@ -205,9 +209,8 @@ impl super::PluginSystem for SteelScriptingEngine {
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
cxt: &mut Context,
|
cxt: &mut Context,
|
||||||
event: KeyEvent,
|
event: KeyEvent,
|
||||||
) -> KeymapResult {
|
) -> Option<KeymapResult> {
|
||||||
SteelScriptingEngine::get_keymap_for_extension(cxt)
|
SteelScriptingEngine::get_keymap_for_extension(cxt).and_then(|map| {
|
||||||
.and_then(|map| {
|
|
||||||
if let steel::SteelVal::Custom(inner) = map {
|
if let steel::SteelVal::Custom(inner) = map {
|
||||||
if let Some(underlying) =
|
if let Some(underlying) =
|
||||||
steel::rvals::as_underlying_type::<EmbeddedKeyMap>(inner.borrow().as_ref())
|
steel::rvals::as_underlying_type::<EmbeddedKeyMap>(inner.borrow().as_ref())
|
||||||
|
@ -218,14 +221,13 @@ impl super::PluginSystem for SteelScriptingEngine {
|
||||||
|
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| editor.keymaps.get(mode, event))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call_function_if_global_exists(
|
fn call_function_if_global_exists(
|
||||||
&self,
|
&self,
|
||||||
cx: &mut Context,
|
cx: &mut Context,
|
||||||
name: &str,
|
name: &str,
|
||||||
args: Vec<Cow<str>>,
|
args: &[Cow<str>],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if ENGINE.with(|x| x.borrow().global_exists(name)) {
|
if ENGINE.with(|x| x.borrow().global_exists(name)) {
|
||||||
let args = steel::List::from(
|
let args = steel::List::from(
|
||||||
|
@ -882,11 +884,28 @@ impl Component for BoxDynComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make this be prompt event validate?
|
||||||
|
fn call_function_in_external_engine(
|
||||||
|
ctx: &mut Context,
|
||||||
|
name: String,
|
||||||
|
args: Vec<String>,
|
||||||
|
engine_type: String,
|
||||||
|
) {
|
||||||
|
// Wire up entire plugins separately?
|
||||||
|
match engine_type.as_str() {
|
||||||
|
"rhai" => todo!(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn configure_engine() -> std::rc::Rc<std::cell::RefCell<steel::steel_vm::engine::Engine>> {
|
fn configure_engine() -> std::rc::Rc<std::cell::RefCell<steel::steel_vm::engine::Engine>> {
|
||||||
let mut engine = steel::steel_vm::engine::Engine::new();
|
let mut engine = steel::steel_vm::engine::Engine::new();
|
||||||
|
|
||||||
log::info!("Loading engine!");
|
log::info!("Loading engine!");
|
||||||
|
|
||||||
|
// Include this?
|
||||||
|
engine.register_fn("call-function-in-context", call_function_in_external_engine);
|
||||||
|
|
||||||
engine.register_value("*context*", SteelVal::Void);
|
engine.register_value("*context*", SteelVal::Void);
|
||||||
|
|
||||||
engine.register_fn("hx.context?", |_: &mut Context| true);
|
engine.register_fn("hx.context?", |_: &mut Context| true);
|
||||||
|
|
|
@ -803,7 +803,8 @@ impl EditorView {
|
||||||
self.pseudo_pending.extend(self.keymaps.pending());
|
self.pseudo_pending.extend(self.keymaps.pending());
|
||||||
|
|
||||||
// Check the engine for any buffer specific keybindings first
|
// Check the engine for any buffer specific keybindings first
|
||||||
let key_result = ScriptingEngine::handle_keymap_event(self, mode, cxt, event);
|
let key_result = ScriptingEngine::handle_keymap_event(self, mode, cxt, event)
|
||||||
|
.unwrap_or_else(|| 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());
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue