2023-05-08 12:53:19 +08:00
|
|
|
use fuzzy_matcher::FuzzyMatcher;
|
2023-07-12 12:23:03 +08:00
|
|
|
use helix_core::{graphemes, shellwords::Shellwords, Selection, Tendril};
|
2023-07-03 03:57:44 +08:00
|
|
|
use helix_view::{
|
2023-07-13 12:39:27 +08:00
|
|
|
document::Mode,
|
|
|
|
editor::{Action, ConfigEvent},
|
|
|
|
extension::document_id_to_usize,
|
|
|
|
input::KeyEvent,
|
|
|
|
Document, DocumentId, Editor,
|
2023-07-03 03:57:44 +08:00
|
|
|
};
|
2023-05-08 12:53:19 +08:00
|
|
|
use once_cell::sync::Lazy;
|
2023-07-13 12:39:27 +08:00
|
|
|
use serde_json::Value;
|
2023-05-08 12:53:19 +08:00
|
|
|
use steel::{
|
|
|
|
gc::unsafe_erased_pointers::CustomReference,
|
2023-08-11 10:24:32 +08:00
|
|
|
rvals::{
|
|
|
|
as_underlying_type, AsRefMutSteelValFromRef, AsRefSteelVal, FromSteelVal, IntoSteelVal,
|
|
|
|
},
|
2023-05-30 12:41:13 +08:00
|
|
|
steel_vm::{engine::Engine, register_fn::RegisterFn},
|
2023-06-06 12:09:27 +08:00
|
|
|
SteelErr, SteelVal,
|
2023-05-08 12:53:19 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
use std::{
|
|
|
|
borrow::Cow,
|
|
|
|
collections::{HashMap, VecDeque},
|
2023-07-12 12:23:03 +08:00
|
|
|
marker::PhantomData,
|
2023-07-13 12:39:27 +08:00
|
|
|
ops::Deref,
|
2023-07-12 12:23:03 +08:00
|
|
|
path::PathBuf,
|
2023-05-08 12:53:19 +08:00
|
|
|
sync::Mutex,
|
|
|
|
};
|
|
|
|
use std::{
|
|
|
|
collections::HashSet,
|
|
|
|
sync::{Arc, RwLock},
|
|
|
|
};
|
|
|
|
|
|
|
|
use steel::{rvals::Custom, steel_vm::builtin::BuiltInModule};
|
|
|
|
|
|
|
|
use crate::{
|
2023-05-30 12:41:13 +08:00
|
|
|
compositor::{self, Component, Compositor},
|
2023-07-12 12:23:03 +08:00
|
|
|
config::Config,
|
2023-05-08 12:53:19 +08:00
|
|
|
job::{self, Callback},
|
2023-07-12 12:23:03 +08:00
|
|
|
keymap::{self, merge_keys, KeyTrie, KeymapResult, Keymaps},
|
2023-07-03 03:57:44 +08:00
|
|
|
ui::{self, menu::Item, overlay::overlaid, Popup, Prompt, PromptEvent},
|
2023-05-08 12:53:19 +08:00
|
|
|
};
|
|
|
|
|
2023-06-06 12:09:27 +08:00
|
|
|
use self::components::SteelDynamicComponent;
|
|
|
|
|
2023-05-08 12:53:19 +08:00
|
|
|
use super::{
|
|
|
|
insert::{insert_char, insert_string},
|
2023-05-30 12:41:13 +08:00
|
|
|
plugin::{DylibContainers, ExternalModule},
|
2023-05-15 09:01:52 +08:00
|
|
|
shell_impl, Context, MappableCommand, TYPABLE_COMMAND_LIST,
|
2023-05-08 12:53:19 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
thread_local! {
|
|
|
|
pub static ENGINE: std::rc::Rc<std::cell::RefCell<steel::steel_vm::engine::Engine>> = configure_engine();
|
|
|
|
}
|
|
|
|
|
2023-05-30 12:41:13 +08:00
|
|
|
pub struct ExternalContainersAndModules {
|
|
|
|
containers: DylibContainers,
|
|
|
|
modules: Vec<ExternalModule>,
|
|
|
|
}
|
|
|
|
|
2023-06-06 12:09:27 +08:00
|
|
|
mod components;
|
|
|
|
|
2023-07-12 12:23:03 +08:00
|
|
|
// pub struct PluginEngine<T: PluginSystem>(PhantomData<T>);
|
|
|
|
|
|
|
|
pub struct ScriptingEngine;
|
|
|
|
|
|
|
|
pub trait PluginSystem {
|
|
|
|
fn initialize();
|
|
|
|
fn run_initialization_script(cx: &mut Context);
|
|
|
|
|
|
|
|
fn call_function_if_global_exists(cx: &mut Context, name: &str, args: Vec<Cow<str>>);
|
|
|
|
fn call_typed_command_if_global_exists<'a>(
|
|
|
|
cx: &mut compositor::Context,
|
|
|
|
input: &'a str,
|
|
|
|
parts: &'a [&'a str],
|
|
|
|
event: PromptEvent,
|
|
|
|
) -> bool;
|
|
|
|
|
|
|
|
fn get_doc_for_identifier(ident: &str) -> Option<String>;
|
|
|
|
}
|
|
|
|
|
2023-08-10 13:24:29 +08:00
|
|
|
// APIs / Modules that need to be accepted by the plugin system
|
|
|
|
// Without these, the core functionality cannot operate
|
|
|
|
pub struct DocumentApi;
|
|
|
|
pub struct EditorApi;
|
|
|
|
pub struct ComponentApi;
|
|
|
|
pub struct TypedCommandsApi;
|
|
|
|
pub struct StaticCommandsApi;
|
|
|
|
|
|
|
|
pub struct KeyMapApi {
|
|
|
|
get_keymap: fn() -> EmbeddedKeyMap,
|
|
|
|
default_keymap: fn() -> EmbeddedKeyMap,
|
|
|
|
empty_keymap: fn() -> EmbeddedKeyMap,
|
|
|
|
string_to_embedded_keymap: fn(String) -> EmbeddedKeyMap,
|
|
|
|
merge_keybindings: fn(&mut EmbeddedKeyMap, EmbeddedKeyMap),
|
2023-08-11 10:24:32 +08:00
|
|
|
is_keymap: fn(SteelVal) -> bool,
|
2023-08-10 13:24:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl KeyMapApi {
|
|
|
|
fn new() -> Self {
|
|
|
|
KeyMapApi {
|
|
|
|
get_keymap,
|
|
|
|
default_keymap,
|
|
|
|
empty_keymap,
|
|
|
|
string_to_embedded_keymap,
|
|
|
|
merge_keybindings,
|
2023-08-11 10:24:32 +08:00
|
|
|
is_keymap,
|
2023-08-10 13:24:29 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
thread_local! {
|
|
|
|
pub static BUFFER_OR_EXTENSION_KEYBINDING_MAP: SteelVal =
|
|
|
|
SteelVal::boxed(SteelVal::empty_hashmap());
|
|
|
|
|
|
|
|
pub static REVERSE_BUFFER_MAP: SteelVal =
|
|
|
|
SteelVal::boxed(SteelVal::empty_hashmap());
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_keymap_api(engine: &mut Engine, api: KeyMapApi) {
|
|
|
|
let mut module = BuiltInModule::new("helix/core/keymaps");
|
|
|
|
|
|
|
|
module.register_fn("helix-current-keymap", api.get_keymap);
|
|
|
|
module.register_fn("helix-empty-keymap", api.empty_keymap);
|
|
|
|
module.register_fn("helix-default-keymap", api.default_keymap);
|
|
|
|
module.register_fn("helix-merge-keybindings", api.merge_keybindings);
|
|
|
|
module.register_fn("helix-string->keymap", api.string_to_embedded_keymap);
|
2023-08-11 10:24:32 +08:00
|
|
|
module.register_fn("keymap?", api.is_keymap);
|
2023-08-10 13:24:29 +08:00
|
|
|
|
|
|
|
// This should be associated with a corresponding scheme module to wrap this up
|
|
|
|
module.register_value(
|
|
|
|
"*buffer-or-extension-keybindings*",
|
|
|
|
BUFFER_OR_EXTENSION_KEYBINDING_MAP.with(|x| x.clone()),
|
|
|
|
);
|
|
|
|
module.register_value(
|
|
|
|
"*reverse-buffer-map*",
|
|
|
|
REVERSE_BUFFER_MAP.with(|x| x.clone()),
|
|
|
|
);
|
|
|
|
|
|
|
|
engine.register_module(module);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_static_commands(engine: &mut Engine) {
|
|
|
|
let mut module = BuiltInModule::new("helix/core/static");
|
|
|
|
|
|
|
|
for command in TYPABLE_COMMAND_LIST {
|
|
|
|
let func = |cx: &mut Context| {
|
|
|
|
let mut cx = compositor::Context {
|
|
|
|
editor: cx.editor,
|
|
|
|
scroll: None,
|
|
|
|
jobs: cx.jobs,
|
|
|
|
};
|
|
|
|
|
|
|
|
(command.fun)(&mut cx, &[], PromptEvent::Validate)
|
|
|
|
};
|
|
|
|
|
|
|
|
module.register_fn(command.name, func);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register everything in the static command list as well
|
|
|
|
// These just accept the context, no arguments
|
|
|
|
for command in MappableCommand::STATIC_COMMAND_LIST {
|
|
|
|
if let MappableCommand::Static { name, fun, .. } = command {
|
|
|
|
module.register_fn(name, fun);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
engine.register_module(module);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_typed_commands(engine: &mut Engine) {
|
|
|
|
let mut module = BuiltInModule::new("helix/core/typable".to_string());
|
|
|
|
|
|
|
|
// Register everything in the typable command list. Now these are all available
|
|
|
|
for command in TYPABLE_COMMAND_LIST {
|
|
|
|
let func = |cx: &mut Context, args: &[Cow<str>], event: PromptEvent| {
|
|
|
|
let mut cx = compositor::Context {
|
|
|
|
editor: cx.editor,
|
|
|
|
scroll: None,
|
|
|
|
jobs: cx.jobs,
|
|
|
|
};
|
|
|
|
|
|
|
|
(command.fun)(&mut cx, args, event)
|
|
|
|
};
|
|
|
|
|
|
|
|
module.register_fn(command.name, func);
|
|
|
|
}
|
|
|
|
|
|
|
|
engine.register_module(module);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_editor_api(engine: &mut Engine, api: EditorApi) {
|
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_document_api(engine: &mut Engine, api: DocumentApi) {
|
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_component_api(engine: &mut Engine, api: ComponentApi) {
|
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
|
2023-07-12 12:23:03 +08:00
|
|
|
impl ScriptingEngine {
|
|
|
|
pub fn initialize() {
|
|
|
|
initialize_engine();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn run_initialization_script(cx: &mut Context) {
|
|
|
|
run_initialization_script(cx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attempt to fetch the keymap for the extension
|
|
|
|
pub fn get_keymap_for_extension<'a>(cx: &'a mut Context) -> Option<SteelVal> {
|
|
|
|
// Get the currently activated extension, also need to check the
|
|
|
|
// buffer type.
|
|
|
|
let extension = {
|
|
|
|
let current_focus = cx.editor.tree.focus;
|
|
|
|
let view = cx.editor.tree.get(current_focus);
|
|
|
|
let doc = &view.doc;
|
|
|
|
let current_doc = cx.editor.documents.get(doc);
|
|
|
|
|
|
|
|
current_doc
|
|
|
|
.and_then(|x| x.path())
|
|
|
|
.and_then(|x| x.extension())
|
|
|
|
.and_then(|x| x.to_str())
|
|
|
|
};
|
|
|
|
|
2023-07-13 12:39:27 +08:00
|
|
|
let doc_id = {
|
|
|
|
let current_focus = cx.editor.tree.focus;
|
|
|
|
let view = cx.editor.tree.get(current_focus);
|
|
|
|
let doc = &view.doc;
|
|
|
|
|
|
|
|
doc
|
|
|
|
};
|
|
|
|
|
2023-07-12 12:23:03 +08:00
|
|
|
if let Some(extension) = extension {
|
2023-08-10 13:24:29 +08:00
|
|
|
if let SteelVal::HashMapV(map) = BUFFER_OR_EXTENSION_KEYBINDING_MAP.with(|x| x.clone())
|
|
|
|
{
|
2023-07-12 12:23:03 +08:00
|
|
|
if let Some(value) = map.get(&SteelVal::StringV(extension.into())) {
|
|
|
|
if let SteelVal::Custom(inner) = value {
|
|
|
|
if let Some(_) = steel::rvals::as_underlying_type::<EmbeddedKeyMap>(
|
|
|
|
inner.borrow().as_ref(),
|
|
|
|
) {
|
|
|
|
return Some(value.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-10 13:24:29 +08:00
|
|
|
// TODO: Remove these clones
|
|
|
|
if let SteelVal::HashMapV(map) = REVERSE_BUFFER_MAP.with(|x| x.clone()) {
|
2023-07-13 12:39:27 +08:00
|
|
|
if let Some(label) = map.get(&SteelVal::IntV(document_id_to_usize(doc_id) as isize)) {
|
2023-08-10 13:24:29 +08:00
|
|
|
if let SteelVal::HashMapV(map) =
|
|
|
|
BUFFER_OR_EXTENSION_KEYBINDING_MAP.with(|x| x.clone())
|
|
|
|
{
|
2023-07-13 12:39:27 +08:00
|
|
|
if let Some(value) = map.get(label) {
|
|
|
|
if let SteelVal::Custom(inner) = value {
|
|
|
|
if let Some(_) = steel::rvals::as_underlying_type::<EmbeddedKeyMap>(
|
|
|
|
inner.borrow().as_ref(),
|
|
|
|
) {
|
|
|
|
return Some(value.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-12 12:23:03 +08:00
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2023-08-10 13:24:29 +08:00
|
|
|
pub fn call_function_if_global_exists(
|
|
|
|
cx: &mut Context,
|
|
|
|
name: &str,
|
|
|
|
args: Vec<Cow<str>>,
|
|
|
|
) -> bool {
|
2023-07-12 12:23:03 +08:00
|
|
|
if ENGINE.with(|x| x.borrow().global_exists(name)) {
|
|
|
|
let args = steel::List::from(
|
|
|
|
args.iter()
|
|
|
|
.map(|x| x.clone().into_steelval().unwrap())
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
|
|
|
|
if let Err(e) = ENGINE.with(|x| {
|
|
|
|
let mut guard = x.borrow_mut();
|
|
|
|
|
|
|
|
{
|
|
|
|
guard.register_value("_helix_args", steel::rvals::SteelVal::ListV(args));
|
|
|
|
|
|
|
|
let res = guard.run_with_reference::<Context, Context>(
|
|
|
|
cx,
|
|
|
|
"*context*",
|
|
|
|
&format!("(apply {} (cons *context* _helix_args))", name),
|
|
|
|
);
|
|
|
|
|
|
|
|
guard.register_value("_helix_args", steel::rvals::SteelVal::Void);
|
|
|
|
|
|
|
|
res
|
|
|
|
}
|
|
|
|
}) {
|
|
|
|
cx.editor.set_error(format!("{}", e));
|
|
|
|
}
|
2023-08-10 13:24:29 +08:00
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
2023-07-12 12:23:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn call_typed_command_if_global_exists<'a>(
|
|
|
|
cx: &mut compositor::Context,
|
|
|
|
input: &'a str,
|
|
|
|
parts: &'a [&'a str],
|
|
|
|
event: PromptEvent,
|
|
|
|
) -> bool {
|
|
|
|
if ENGINE.with(|x| x.borrow().global_exists(parts[0])) {
|
|
|
|
let shellwords = Shellwords::from(input);
|
|
|
|
let args = shellwords.words();
|
|
|
|
|
|
|
|
// We're finalizing the event - we actually want to call the function
|
|
|
|
if event == PromptEvent::Validate {
|
|
|
|
// 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| {
|
|
|
|
let args = steel::List::from(
|
|
|
|
args[1..]
|
|
|
|
.iter()
|
|
|
|
.map(|x| x.clone().into_steelval().unwrap())
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
|
|
|
|
let mut guard = x.borrow_mut();
|
|
|
|
// let mut maybe_callback = None;
|
|
|
|
|
|
|
|
let res = {
|
|
|
|
let mut cx = Context {
|
|
|
|
register: None,
|
|
|
|
count: std::num::NonZeroUsize::new(1),
|
|
|
|
editor: cx.editor,
|
|
|
|
callback: None,
|
|
|
|
on_next_key_callback: None,
|
|
|
|
jobs: cx.jobs,
|
|
|
|
};
|
|
|
|
|
|
|
|
guard.register_value("_helix_args", steel::rvals::SteelVal::ListV(args));
|
|
|
|
|
|
|
|
let res = guard.run_with_reference::<Context, Context>(
|
|
|
|
&mut cx,
|
|
|
|
"*context*",
|
|
|
|
&format!("(apply {} (cons *context* _helix_args))", parts[0]),
|
|
|
|
);
|
|
|
|
|
|
|
|
guard.register_value("_helix_args", steel::rvals::SteelVal::Void);
|
|
|
|
|
|
|
|
res
|
|
|
|
};
|
|
|
|
|
|
|
|
res
|
|
|
|
}) {
|
|
|
|
compositor_present_error(cx, e);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Global exists
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
// Global does not exist
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_doc_for_identifier(ident: &str) -> Option<String> {
|
|
|
|
if ENGINE.with(|x| x.borrow().global_exists(ident)) {
|
|
|
|
if let Some(v) = super::engine::ExportedIdentifiers::engine_get_doc(ident) {
|
|
|
|
return Some(v.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
return Some("Run this plugin command!".into());
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
2023-07-12 12:37:40 +08:00
|
|
|
|
|
|
|
pub(crate) fn fuzzy_match<'a>(
|
|
|
|
fuzzy_matcher: &'a fuzzy_matcher::skim::SkimMatcherV2,
|
|
|
|
input: &'a str,
|
|
|
|
) -> Vec<(String, i64)> {
|
|
|
|
EXPORTED_IDENTIFIERS
|
|
|
|
.identifiers
|
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.iter()
|
|
|
|
.filter_map(|name| {
|
|
|
|
fuzzy_matcher
|
|
|
|
.fuzzy_match(name, input)
|
|
|
|
.map(|score| (name, score))
|
|
|
|
})
|
|
|
|
.map(|x| (x.0.to_string(), x.1))
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn is_exported(ident: &str) -> bool {
|
|
|
|
EXPORTED_IDENTIFIERS
|
|
|
|
.identifiers
|
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.contains(ident)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn engine_get_doc(ident: &str) -> Option<String> {
|
|
|
|
EXPORTED_IDENTIFIERS
|
|
|
|
.docs
|
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.get(ident)
|
|
|
|
.cloned()
|
|
|
|
}
|
|
|
|
|
|
|
|
// fn get_doc(&self, ident: &str) -> Option<String> {
|
|
|
|
// self.docs.read().unwrap().get(ident).cloned()
|
|
|
|
// }
|
2023-07-12 12:23:03 +08:00
|
|
|
}
|
|
|
|
|
2023-05-30 12:41:13 +08:00
|
|
|
// 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<Arc<RwLock<ExternalContainersAndModules>>> =
|
|
|
|
Lazy::new(|| {
|
|
|
|
let mut containers = DylibContainers::new();
|
|
|
|
|
|
|
|
// Load the plugins with respect to the extensions directory.
|
|
|
|
// containers.load_modules_from_directory(Some(
|
|
|
|
// helix_loader::config_dir()
|
|
|
|
// .join("extensions")
|
|
|
|
// .to_str()
|
|
|
|
// .unwrap()
|
|
|
|
// .to_string(),
|
|
|
|
// ));
|
|
|
|
|
|
|
|
println!("Found dylibs: {}", containers.containers.len());
|
|
|
|
|
|
|
|
let modules = containers.create_commands();
|
|
|
|
|
|
|
|
println!("Modules length: {}", modules.len());
|
|
|
|
|
|
|
|
Arc::new(RwLock::new(ExternalContainersAndModules {
|
|
|
|
containers,
|
|
|
|
modules,
|
|
|
|
}))
|
|
|
|
|
|
|
|
// Arc::new(RwLock::new(containers))
|
|
|
|
});
|
|
|
|
|
2023-05-08 12:53:19 +08:00
|
|
|
pub fn initialize_engine() {
|
|
|
|
ENGINE.with(|x| x.borrow().globals().first().copied());
|
|
|
|
}
|
|
|
|
|
2023-06-06 12:09:27 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-07-12 12:23:03 +08:00
|
|
|
// Key maps
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct EmbeddedKeyMap(pub HashMap<Mode, KeyTrie>);
|
|
|
|
impl Custom for EmbeddedKeyMap {}
|
|
|
|
|
|
|
|
pub fn get_keymap() -> EmbeddedKeyMap {
|
|
|
|
// Snapsnot current configuration for use in forking the keymap
|
|
|
|
let keymap = Config::load_default().unwrap();
|
|
|
|
|
|
|
|
// These are the actual mappings that we want
|
|
|
|
let map = keymap.keys;
|
|
|
|
|
|
|
|
EmbeddedKeyMap(map)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Base level - no configuration
|
|
|
|
pub fn default_keymap() -> EmbeddedKeyMap {
|
|
|
|
EmbeddedKeyMap(keymap::default())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Completely empty, allow for overriding
|
|
|
|
pub fn empty_keymap() -> EmbeddedKeyMap {
|
|
|
|
EmbeddedKeyMap(HashMap::default())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn string_to_embedded_keymap(value: String) -> EmbeddedKeyMap {
|
|
|
|
EmbeddedKeyMap(serde_json::from_str(&value).unwrap())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn merge_keybindings(left: &mut EmbeddedKeyMap, right: EmbeddedKeyMap) {
|
|
|
|
merge_keys(&mut left.0, right.0)
|
|
|
|
}
|
|
|
|
|
2023-08-11 10:24:32 +08:00
|
|
|
pub fn is_keymap(keymap: SteelVal) -> bool {
|
|
|
|
if let SteelVal::Custom(underlying) = keymap {
|
|
|
|
as_underlying_type::<EmbeddedKeyMap>(underlying.borrow().as_ref()).is_some()
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-10 22:48:02 +08:00
|
|
|
/// 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
|
2023-08-10 13:24:29 +08:00
|
|
|
fn run_initialization_script(cx: &mut Context) {
|
2023-05-10 22:48:02 +08:00
|
|
|
log::info!("Loading init.scm...");
|
|
|
|
|
|
|
|
let helix_module_path = helix_loader::steel_init_file();
|
|
|
|
|
2023-06-06 12:09:27 +08:00
|
|
|
// These contents need to be registered with the path?
|
2023-05-10 22:48:02 +08:00
|
|
|
if let Ok(contents) = std::fs::read_to_string(&helix_module_path) {
|
2023-06-06 12:09:27 +08:00
|
|
|
let res = ENGINE.with(|x| {
|
2023-05-10 22:48:02 +08:00
|
|
|
x.borrow_mut()
|
2023-08-10 13:24:29 +08:00
|
|
|
.run_with_reference_from_path::<Context, Context>(
|
|
|
|
cx,
|
|
|
|
"*helix.cx*",
|
|
|
|
&contents,
|
|
|
|
helix_module_path,
|
|
|
|
)
|
2023-05-10 22:48:02 +08:00
|
|
|
});
|
|
|
|
|
2023-06-06 12:09:27 +08:00
|
|
|
match res {
|
|
|
|
Ok(_) => {}
|
|
|
|
Err(e) => present_error(cx, e),
|
|
|
|
}
|
|
|
|
|
2023-05-10 22:48:02 +08:00
|
|
|
log::info!("Finished loading init.scm!")
|
|
|
|
} else {
|
|
|
|
log::info!("No init.scm found, skipping loading.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-08 12:53:19 +08:00
|
|
|
pub static KEYBINDING_QUEUE: Lazy<SharedKeyBindingsEventQueue> =
|
|
|
|
Lazy::new(|| SharedKeyBindingsEventQueue::new());
|
|
|
|
|
2023-06-25 13:24:52 +08:00
|
|
|
pub static CALLBACK_QUEUE: Lazy<CallbackQueue> = Lazy::new(|| CallbackQueue::new());
|
|
|
|
|
2023-05-08 12:53:19 +08:00
|
|
|
pub static EXPORTED_IDENTIFIERS: Lazy<ExportedIdentifiers> =
|
|
|
|
Lazy::new(|| ExportedIdentifiers::default());
|
|
|
|
|
2023-05-10 22:48:02 +08:00
|
|
|
pub static STATUS_LINE_MESSAGE: Lazy<StatusLineMessage> = Lazy::new(|| StatusLineMessage::new());
|
|
|
|
|
|
|
|
pub struct StatusLineMessage {
|
|
|
|
message: Arc<RwLock<Option<String>>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl StatusLineMessage {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
message: std::sync::Arc::new(std::sync::RwLock::new(None)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set(message: String) {
|
|
|
|
*STATUS_LINE_MESSAGE.message.write().unwrap() = Some(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get() -> Option<String> {
|
|
|
|
STATUS_LINE_MESSAGE.message.read().unwrap().clone()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-31 21:24:31 +08:00
|
|
|
impl Item for SteelVal {
|
|
|
|
type Data = ();
|
|
|
|
|
|
|
|
// TODO: This shouldn't copy the data every time
|
|
|
|
fn format(&self, _data: &Self::Data) -> tui::widgets::Row {
|
|
|
|
let formatted = self.to_string();
|
|
|
|
|
|
|
|
formatted
|
|
|
|
.strip_prefix("\"")
|
|
|
|
.unwrap_or(&formatted)
|
|
|
|
.strip_suffix("\"")
|
|
|
|
.unwrap_or(&formatted)
|
|
|
|
.to_owned()
|
|
|
|
.into()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-25 13:24:52 +08:00
|
|
|
pub struct CallbackQueue {
|
|
|
|
queue: Arc<Mutex<VecDeque<String>>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CallbackQueue {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
queue: Arc::new(Mutex::new(VecDeque::new())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn enqueue(value: String) {
|
|
|
|
CALLBACK_QUEUE.queue.lock().unwrap().push_back(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dequeue should probably be a R/W lock?
|
|
|
|
pub fn dequeue() -> Option<String> {
|
|
|
|
CALLBACK_QUEUE.queue.lock().unwrap().pop_front()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-08 12:53:19 +08:00
|
|
|
/// In order to send events from the engine back to the configuration, we can created a shared
|
|
|
|
/// queue that the engine and the config push and pull from. Alternatively, we could use a channel
|
|
|
|
/// directly, however this was easy enough to set up.
|
|
|
|
pub struct SharedKeyBindingsEventQueue {
|
2023-07-12 12:23:03 +08:00
|
|
|
raw_bindings: Arc<Mutex<Vec<String>>>,
|
2023-05-08 12:53:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl SharedKeyBindingsEventQueue {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
2023-07-12 12:23:03 +08:00
|
|
|
raw_bindings: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
|
2023-05-08 12:53:19 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn merge(other_as_json: String) {
|
|
|
|
KEYBINDING_QUEUE
|
|
|
|
.raw_bindings
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
2023-07-12 12:23:03 +08:00
|
|
|
.push(other_as_json);
|
2023-05-08 12:53:19 +08:00
|
|
|
}
|
|
|
|
|
2023-07-03 09:00:47 +08:00
|
|
|
pub fn get() -> Option<HashMap<Mode, KeyTrie>> {
|
2023-07-12 12:23:03 +08:00
|
|
|
let guard = KEYBINDING_QUEUE.raw_bindings.lock().unwrap();
|
2023-05-08 12:53:19 +08:00
|
|
|
|
2023-07-12 12:23:03 +08:00
|
|
|
if let Some(first) = guard.get(0).clone() {
|
|
|
|
let mut initial = serde_json::from_str(first).unwrap();
|
2023-05-08 12:53:19 +08:00
|
|
|
|
2023-07-12 12:23:03 +08:00
|
|
|
// while let Some(remaining_event) = guard.pop_front() {
|
|
|
|
for remaining_event in guard.iter() {
|
|
|
|
let bindings = serde_json::from_str(remaining_event).unwrap();
|
2023-05-08 12:53:19 +08:00
|
|
|
|
|
|
|
merge_keys(&mut initial, bindings);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Some(initial);
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Custom for PromptEvent {}
|
|
|
|
|
|
|
|
impl<'a> CustomReference for Context<'a> {}
|
|
|
|
|
2023-06-25 13:24:52 +08:00
|
|
|
steel::custom_reference!(Context<'a>);
|
|
|
|
|
2023-05-09 13:20:02 +08:00
|
|
|
fn get_editor<'a>(cx: &'a mut Context<'a>) -> &'a mut Editor {
|
|
|
|
cx.editor
|
|
|
|
}
|
|
|
|
|
2023-05-30 12:41:13 +08:00
|
|
|
fn get_ro_editor<'a>(cx: &'a mut Context<'a>) -> &'a Editor {
|
|
|
|
&cx.editor
|
|
|
|
}
|
|
|
|
|
2023-05-10 22:48:02 +08:00
|
|
|
fn get_themes(cx: &mut Context) -> Vec<String> {
|
|
|
|
ui::completers::theme(cx.editor, "")
|
|
|
|
.into_iter()
|
|
|
|
.map(|x| x.1.to_string())
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2023-05-30 12:41:13 +08:00
|
|
|
// TODO: This is not necessary anymore. We can get away with native threads in steel, and otherwise run background tasks
|
|
|
|
// that may or may not live the duration of the editor time in there.
|
|
|
|
// fn configure_background_thread() {
|
|
|
|
// std::thread::spawn(move || {
|
|
|
|
// let mut engine = steel::steel_vm::engine::Engine::new();
|
|
|
|
|
|
|
|
// engine.register_fn("set-status-line!", StatusLineMessage::set);
|
|
|
|
|
|
|
|
// let helix_module_path = helix_loader::config_dir().join("background.scm");
|
|
|
|
|
|
|
|
// if let Ok(contents) = std::fs::read_to_string(&helix_module_path) {
|
|
|
|
// engine.run(&contents).ok();
|
|
|
|
// }
|
|
|
|
// });
|
|
|
|
// }
|
|
|
|
|
|
|
|
/// A dynamic component, used for rendering thing
|
|
|
|
|
|
|
|
impl Custom for compositor::EventResult {}
|
|
|
|
impl FromSteelVal for compositor::EventResult {
|
|
|
|
fn from_steelval(val: &SteelVal) -> steel::rvals::Result<Self> {
|
|
|
|
match val {
|
|
|
|
SteelVal::SymbolV(v) if v.as_str() == "EventResult::Ignored" => {
|
|
|
|
Ok(compositor::EventResult::Ignored(None))
|
|
|
|
}
|
|
|
|
SteelVal::SymbolV(v) if v.as_str() == "EventResult::Consumed" => {
|
|
|
|
Ok(compositor::EventResult::Consumed(None))
|
|
|
|
}
|
|
|
|
_ => Err(steel::SteelErr::new(
|
|
|
|
steel::rerrs::ErrorKind::TypeMismatch,
|
|
|
|
"Unable to convert value to event result".to_string(),
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// impl CustomReference for tui::buffer::Buffer {}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// Does this work?
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
2023-05-10 22:48:02 +08:00
|
|
|
}
|
|
|
|
|
2023-05-08 12:53:19 +08:00
|
|
|
fn configure_engine() -> std::rc::Rc<std::cell::RefCell<steel::steel_vm::engine::Engine>> {
|
|
|
|
let mut engine = steel::steel_vm::engine::Engine::new();
|
|
|
|
|
2023-05-31 21:24:31 +08:00
|
|
|
log::info!("Loading engine!");
|
2023-05-30 12:41:13 +08:00
|
|
|
|
2023-08-11 10:24:32 +08:00
|
|
|
engine.register_fn("hx.context?", |_: &mut Context| true);
|
|
|
|
|
2023-05-30 12:41:13 +08:00
|
|
|
// Load native modules from the directory. Another idea - have a separate dlopen loading system
|
|
|
|
// in place that does not use the type id, and instead we generate the module after the dylib
|
|
|
|
// is added. That way functions _must_ have a specific signature, and then we add the integration
|
|
|
|
// later.
|
|
|
|
// engine.load_modules_from_directory(
|
|
|
|
// helix_loader::config_dir()
|
|
|
|
// .join("extensions")
|
|
|
|
// .to_str()
|
|
|
|
// .unwrap()
|
|
|
|
// .to_string(),
|
|
|
|
// );
|
|
|
|
|
2023-06-25 13:24:52 +08:00
|
|
|
engine.register_fn("enqueue-callback!", CallbackQueue::enqueue);
|
2023-08-10 13:24:29 +08:00
|
|
|
|
|
|
|
load_keymap_api(&mut engine, KeyMapApi::new());
|
|
|
|
|
2023-08-11 10:24:32 +08:00
|
|
|
// engine.register_fn("helix-current-keymap", get_keymap);
|
|
|
|
// engine.register_fn("helix-empty-keymap", empty_keymap);
|
|
|
|
// engine.register_fn("helix-default-keymap", default_keymap);
|
|
|
|
// engine.register_fn("helix-merge-keybindings", merge_keybindings);
|
|
|
|
// engine.register_fn("helix-string->keymap", string_to_embedded_keymap);
|
2023-07-12 12:23:03 +08:00
|
|
|
|
|
|
|
// Use this to get at buffer specific keybindings
|
2023-08-11 10:24:32 +08:00
|
|
|
// engine.register_value(
|
|
|
|
// "*buffer-or-extension-keybindings*",
|
|
|
|
// SteelVal::empty_hashmap(),
|
|
|
|
// );
|
|
|
|
// engine.register_value("*reverse-buffer-map*", SteelVal::empty_hashmap());
|
2023-06-25 13:24:52 +08:00
|
|
|
|
2023-07-03 03:57:44 +08:00
|
|
|
// Find the workspace
|
|
|
|
engine.register_fn("helix-find-workspace", || {
|
|
|
|
helix_core::find_workspace().0.to_str().unwrap().to_string()
|
|
|
|
});
|
|
|
|
|
2023-05-15 09:01:52 +08:00
|
|
|
// Get the current OS
|
|
|
|
engine.register_fn("current-os!", || std::env::consts::OS);
|
2023-05-30 12:41:13 +08:00
|
|
|
engine.register_fn("new-component!", SteelDynamicComponent::new_dyn);
|
|
|
|
|
|
|
|
engine.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
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
engine.register_fn(
|
|
|
|
"SteelDynamicComponent-state",
|
|
|
|
SteelDynamicComponent::get_state,
|
|
|
|
);
|
|
|
|
engine.register_fn(
|
|
|
|
"SteelDynamicComponent-render",
|
|
|
|
SteelDynamicComponent::get_render,
|
|
|
|
);
|
|
|
|
engine.register_fn(
|
|
|
|
"SteelDynamicComponent-handle-event",
|
|
|
|
SteelDynamicComponent::get_handle_event,
|
|
|
|
);
|
|
|
|
engine.register_fn(
|
|
|
|
"SteelDynamicComponent-should-update",
|
|
|
|
SteelDynamicComponent::should_update,
|
|
|
|
);
|
|
|
|
engine.register_fn(
|
|
|
|
"SteelDynamicComponent-cursor",
|
|
|
|
SteelDynamicComponent::cursor,
|
|
|
|
);
|
|
|
|
engine.register_fn(
|
|
|
|
"SteelDynamicComponent-required-size",
|
|
|
|
SteelDynamicComponent::get_required_size,
|
|
|
|
);
|
|
|
|
|
|
|
|
// engine.register_fn("WrappedComponent", WrappedDynComponent::new)
|
|
|
|
|
2023-08-10 13:24:29 +08:00
|
|
|
// engine.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)),
|
|
|
|
// )),
|
|
|
|
// }
|
|
|
|
// },
|
|
|
|
// );
|
2023-05-30 12:41:13 +08:00
|
|
|
|
2023-07-03 03:57:44 +08:00
|
|
|
engine.register_fn(
|
|
|
|
"Prompt::new",
|
|
|
|
|prompt: String, callback_fn: SteelVal| -> WrappedDynComponent {
|
|
|
|
let prompt = Prompt::new(
|
|
|
|
prompt.into(),
|
|
|
|
None,
|
|
|
|
|_, _| Vec::new(),
|
|
|
|
move |cx, input, prompt_event| {
|
|
|
|
log::info!("Calling dynamic prompt callback");
|
|
|
|
|
|
|
|
if prompt_event != PromptEvent::Validate {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut ctx = Context {
|
|
|
|
register: None,
|
|
|
|
count: None,
|
|
|
|
editor: cx.editor,
|
|
|
|
callback: None,
|
|
|
|
on_next_key_callback: None,
|
|
|
|
jobs: cx.jobs,
|
|
|
|
};
|
|
|
|
|
|
|
|
let cloned_func = callback_fn.clone();
|
|
|
|
|
2023-07-12 12:23:03 +08:00
|
|
|
if let Err(e) = ENGINE.with(|x| {
|
|
|
|
x.borrow_mut()
|
|
|
|
.with_mut_reference::<Context, Context>(&mut ctx)
|
|
|
|
.consume(move |engine, mut args| {
|
|
|
|
// Add the string as an argument to the callback
|
|
|
|
args.push(input.into_steelval().unwrap());
|
2023-07-03 03:57:44 +08:00
|
|
|
|
2023-07-12 12:23:03 +08:00
|
|
|
engine.call_function_with_args(cloned_func.clone(), args)
|
|
|
|
})
|
|
|
|
}) {
|
|
|
|
present_error(&mut ctx, e);
|
|
|
|
}
|
2023-07-03 03:57:44 +08:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
WrappedDynComponent {
|
|
|
|
inner: Some(Box::new(prompt)),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-05-31 21:24:31 +08:00
|
|
|
engine.register_fn("Picker::new", |values: Vec<String>| todo!());
|
|
|
|
|
2023-05-30 12:41:13 +08:00
|
|
|
// engine.register_fn(
|
|
|
|
// "Picker::new",
|
|
|
|
// |contents: &mut Wrapped
|
|
|
|
// )
|
|
|
|
|
|
|
|
engine.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!
|
|
|
|
engine.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,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
engine.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,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
engine.register_fn("Component::should-update", |s: &mut WrappedDynComponent| {
|
|
|
|
s.inner.as_mut().unwrap().should_update()
|
|
|
|
});
|
|
|
|
|
|
|
|
engine.register_fn(
|
|
|
|
"Component::cursor",
|
|
|
|
|s: &WrappedDynComponent, area: helix_view::graphics::Rect, ctx: &Editor| {
|
|
|
|
s.inner.as_ref().unwrap().cursor(area, ctx)
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
engine.register_fn(
|
|
|
|
"Component::required-size",
|
|
|
|
|s: &mut WrappedDynComponent, viewport: (u16, u16)| {
|
|
|
|
s.inner.as_mut().unwrap().required_size(viewport)
|
|
|
|
},
|
|
|
|
);
|
2023-05-15 09:01:52 +08:00
|
|
|
|
2023-05-08 12:53:19 +08:00
|
|
|
let mut module = BuiltInModule::new("helix/core/keybindings".to_string());
|
|
|
|
module.register_fn("set-keybindings!", SharedKeyBindingsEventQueue::merge);
|
|
|
|
|
2023-05-09 13:20:02 +08:00
|
|
|
RegisterFn::<
|
|
|
|
_,
|
|
|
|
steel::steel_vm::register_fn::MarkerWrapper7<(
|
|
|
|
Context<'_>,
|
|
|
|
helix_view::Editor,
|
|
|
|
helix_view::Editor,
|
|
|
|
Context<'static>,
|
|
|
|
)>,
|
|
|
|
helix_view::Editor,
|
|
|
|
>::register_fn(&mut engine, "cx-editor!", get_editor);
|
|
|
|
|
2023-08-10 13:24:29 +08:00
|
|
|
engine.register_fn("set-scratch-buffer-name!", set_scratch_buffer_name);
|
|
|
|
|
2023-06-06 12:09:27 +08:00
|
|
|
engine.register_fn("editor-focus", current_focus);
|
|
|
|
engine.register_fn("editor->doc-id", get_document_id);
|
2023-06-30 13:04:02 +08:00
|
|
|
engine.register_fn("doc-id->usize", document_id_to_usize);
|
2023-06-30 07:08:50 +08:00
|
|
|
engine.register_fn("editor-switch!", switch);
|
|
|
|
engine.register_fn("editor-set-focus!", Editor::focus);
|
|
|
|
engine.register_fn("editor-mode", editor_get_mode);
|
|
|
|
engine.register_fn("editor-set-mode!", editor_set_mode);
|
|
|
|
engine.register_fn("editor-doc-in-view?", is_document_in_view);
|
2023-07-03 03:57:44 +08:00
|
|
|
|
2023-06-06 12:09:27 +08:00
|
|
|
// 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);
|
2023-07-03 03:57:44 +08:00
|
|
|
engine.register_fn("helix.context?", is_context);
|
|
|
|
engine.register_type::<DocumentId>("DocumentId?");
|
2023-06-06 12:09:27 +08:00
|
|
|
|
2023-05-30 12:41:13 +08:00
|
|
|
// RegisterFn::<
|
|
|
|
// _,
|
|
|
|
// steel::steel_vm::register_fn::MarkerWrapper7<(
|
|
|
|
// Context<'_>,
|
|
|
|
// helix_view::Editor,
|
|
|
|
// helix_view::Editor,
|
|
|
|
// Context<'static>,
|
|
|
|
// )>,
|
|
|
|
// helix_view::Editor,
|
|
|
|
// >::register_fn(&mut engine, "cx-editor-ro!", get_ro_editor);
|
|
|
|
|
|
|
|
engine.register_fn("editor-cursor", Editor::cursor);
|
|
|
|
|
|
|
|
engine.register_fn("cx->cursor", |cx: &mut Context| cx.editor.cursor());
|
|
|
|
|
|
|
|
// TODO:
|
|
|
|
// Position related functions. These probably should be defined alongside the actual impl for Custom in the core crate
|
|
|
|
engine.register_fn("Position::new", helix_core::Position::new);
|
|
|
|
engine.register_fn("Position::default", helix_core::Position::default);
|
|
|
|
engine.register_fn("Position-row", |position: helix_core::Position| {
|
|
|
|
position.row
|
|
|
|
});
|
|
|
|
|
2023-05-10 22:48:02 +08:00
|
|
|
engine.register_fn("cx->themes", get_themes);
|
|
|
|
engine.register_fn("set-status-line!", StatusLineMessage::set);
|
|
|
|
|
2023-05-08 12:53:19 +08:00
|
|
|
engine.register_module(module);
|
|
|
|
|
|
|
|
let mut module = BuiltInModule::new("helix/core/typable".to_string());
|
|
|
|
|
2023-07-13 12:39:27 +08:00
|
|
|
{
|
|
|
|
let func = |cx: &mut Context, args: &[Cow<str>], event: PromptEvent| {
|
|
|
|
let mut cx = compositor::Context {
|
|
|
|
editor: cx.editor,
|
|
|
|
scroll: None,
|
|
|
|
jobs: cx.jobs,
|
|
|
|
};
|
|
|
|
|
|
|
|
set_options(&mut cx, args, event)
|
|
|
|
};
|
|
|
|
|
|
|
|
module.register_fn("set-options", func);
|
|
|
|
}
|
|
|
|
|
2023-05-08 12:53:19 +08:00
|
|
|
module.register_value(
|
|
|
|
"PromptEvent::Validate",
|
|
|
|
PromptEvent::Validate.into_steelval().unwrap(),
|
|
|
|
);
|
2023-07-03 03:57:44 +08:00
|
|
|
module.register_value(
|
|
|
|
"PromptEvent::Update",
|
|
|
|
PromptEvent::Update.into_steelval().unwrap(),
|
|
|
|
);
|
2023-05-08 12:53:19 +08:00
|
|
|
|
|
|
|
// Register everything in the typable command list. Now these are all available
|
|
|
|
for command in TYPABLE_COMMAND_LIST {
|
|
|
|
let func = |cx: &mut Context, args: &[Cow<str>], event: PromptEvent| {
|
|
|
|
let mut cx = compositor::Context {
|
|
|
|
editor: cx.editor,
|
|
|
|
scroll: None,
|
|
|
|
jobs: cx.jobs,
|
|
|
|
};
|
|
|
|
|
|
|
|
(command.fun)(&mut cx, args, event)
|
|
|
|
};
|
|
|
|
|
|
|
|
module.register_fn(command.name, func);
|
|
|
|
}
|
|
|
|
|
|
|
|
engine.register_module(module);
|
|
|
|
|
|
|
|
let mut module = BuiltInModule::new("helix/core/static".to_string());
|
|
|
|
|
2023-06-25 13:24:52 +08:00
|
|
|
for command in TYPABLE_COMMAND_LIST {
|
|
|
|
let func = |cx: &mut Context| {
|
|
|
|
let mut cx = compositor::Context {
|
|
|
|
editor: cx.editor,
|
|
|
|
scroll: None,
|
|
|
|
jobs: cx.jobs,
|
|
|
|
};
|
|
|
|
|
|
|
|
(command.fun)(&mut cx, &[], PromptEvent::Validate)
|
|
|
|
};
|
|
|
|
|
|
|
|
module.register_fn(command.name, func);
|
|
|
|
}
|
|
|
|
|
2023-05-08 12:53:19 +08:00
|
|
|
// Register everything in the static command list as well
|
|
|
|
// These just accept the context, no arguments
|
|
|
|
for command in MappableCommand::STATIC_COMMAND_LIST {
|
|
|
|
if let MappableCommand::Static { name, fun, .. } = command {
|
|
|
|
module.register_fn(name, fun);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.register_fn("insert_char", insert_char);
|
|
|
|
module.register_fn("insert_string", insert_string);
|
|
|
|
module.register_fn("current_selection", get_selection);
|
|
|
|
module.register_fn("current-highlighted-text!", get_highlighted_text);
|
2023-06-30 07:08:50 +08:00
|
|
|
module.register_fn("get-current-line-number", current_line_number);
|
|
|
|
|
|
|
|
module.register_fn("current-selection-object", current_selection);
|
|
|
|
module.register_fn("set-current-selection-object!", set_selection);
|
|
|
|
|
2023-05-08 12:53:19 +08:00
|
|
|
module.register_fn("run-in-engine!", run_in_engine);
|
|
|
|
module.register_fn("get-helix-scm-path", get_helix_scm_path);
|
2023-05-10 22:48:02 +08:00
|
|
|
module.register_fn("get-init-scm-path", get_init_scm_path);
|
2023-05-15 09:01:52 +08:00
|
|
|
module.register_fn("block-on-shell-command", run_shell_command_text);
|
2023-05-08 12:53:19 +08:00
|
|
|
|
2023-06-06 12:09:27 +08:00
|
|
|
module.register_fn("cx->current-file", current_path);
|
|
|
|
|
2023-05-08 12:53:19 +08:00
|
|
|
engine.register_module(module);
|
|
|
|
|
2023-05-30 12:41:13 +08:00
|
|
|
engine.register_fn("push-component!", push_component);
|
2023-07-03 03:57:44 +08:00
|
|
|
engine.register_fn("enqueue-thread-local-callback", enqueue_command);
|
2023-08-10 13:24:29 +08:00
|
|
|
engine.register_fn(
|
|
|
|
"enqueue-thread-local-callback-with-delay",
|
|
|
|
enqueue_command_with_delay,
|
|
|
|
);
|
|
|
|
|
|
|
|
engine.register_fn("helix-await-callback", await_value);
|
2023-05-30 12:41:13 +08:00
|
|
|
|
2023-07-12 12:23:03 +08:00
|
|
|
// Create directory since we can't do that in the current state
|
|
|
|
engine.register_fn("hx.create-directory", create_directory);
|
|
|
|
|
2023-05-08 12:53:19 +08:00
|
|
|
let helix_module_path = helix_loader::helix_module_file();
|
|
|
|
|
|
|
|
engine
|
|
|
|
.run(&format!(
|
|
|
|
r#"(require "{}")"#,
|
|
|
|
helix_module_path.to_str().unwrap()
|
|
|
|
))
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// __module-mangler/home/matt/Documents/steel/cogs/logging/log.scm
|
|
|
|
|
|
|
|
// TODO: Use the helix.scm file located in the configuration directory instead
|
|
|
|
// let mut working_directory = std::env::current_dir().unwrap();
|
|
|
|
|
|
|
|
// working_directory.push("helix.scm");
|
|
|
|
|
|
|
|
// working_directory = working_directory.canonicalize().unwrap();
|
|
|
|
|
|
|
|
let helix_path =
|
|
|
|
"__module-mangler".to_string() + helix_module_path.as_os_str().to_str().unwrap();
|
|
|
|
|
2023-05-09 13:20:02 +08:00
|
|
|
// mangler/home/matt/Documents/steel/cogs/logging/log.scmlog/warn!__doc__
|
|
|
|
|
|
|
|
let module_prefix = "mangler".to_string() + helix_module_path.as_os_str().to_str().unwrap();
|
|
|
|
|
2023-05-08 12:53:19 +08:00
|
|
|
let module = engine.extract_value(&helix_path).unwrap();
|
|
|
|
|
|
|
|
if let steel::rvals::SteelVal::HashMapV(m) = module {
|
|
|
|
let exported = m
|
|
|
|
.iter()
|
|
|
|
.filter(|(_, v)| v.is_function())
|
|
|
|
.map(|(k, _)| {
|
|
|
|
if let steel::rvals::SteelVal::SymbolV(s) = k {
|
|
|
|
s.to_string()
|
|
|
|
} else {
|
|
|
|
panic!("Found a non symbol!")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect::<HashSet<_>>();
|
|
|
|
|
2023-05-09 13:20:02 +08:00
|
|
|
let docs = exported
|
2023-05-08 12:53:19 +08:00
|
|
|
.iter()
|
2023-05-09 13:20:02 +08:00
|
|
|
.filter_map(|x| {
|
|
|
|
if let Ok(steel::rvals::SteelVal::StringV(d)) =
|
|
|
|
engine.extract_value(&(module_prefix.to_string() + x.as_str() + "__doc__"))
|
|
|
|
{
|
|
|
|
Some((x.to_string(), d.to_string()))
|
|
|
|
} else {
|
|
|
|
None
|
2023-05-08 12:53:19 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect::<HashMap<_, _>>();
|
|
|
|
|
2023-05-09 13:20:02 +08:00
|
|
|
*EXPORTED_IDENTIFIERS.identifiers.write().unwrap() = exported;
|
2023-05-08 12:53:19 +08:00
|
|
|
*EXPORTED_IDENTIFIERS.docs.write().unwrap() = docs;
|
|
|
|
} else {
|
|
|
|
panic!("Unable to parse exported identifiers from helix module!")
|
|
|
|
}
|
|
|
|
|
|
|
|
std::rc::Rc::new(std::cell::RefCell::new(engine))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default, Debug)]
|
|
|
|
pub struct ExportedIdentifiers {
|
|
|
|
identifiers: Arc<RwLock<HashSet<String>>>,
|
|
|
|
docs: Arc<RwLock<HashMap<String, String>>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ExportedIdentifiers {
|
|
|
|
pub(crate) fn fuzzy_match<'a>(
|
|
|
|
fuzzy_matcher: &'a fuzzy_matcher::skim::SkimMatcherV2,
|
|
|
|
input: &'a str,
|
|
|
|
) -> Vec<(String, i64)> {
|
|
|
|
EXPORTED_IDENTIFIERS
|
|
|
|
.identifiers
|
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.iter()
|
|
|
|
.filter_map(|name| {
|
|
|
|
fuzzy_matcher
|
|
|
|
.fuzzy_match(name, input)
|
|
|
|
.map(|score| (name, score))
|
|
|
|
})
|
|
|
|
.map(|x| (x.0.to_string(), x.1))
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn is_exported(ident: &str) -> bool {
|
|
|
|
EXPORTED_IDENTIFIERS
|
|
|
|
.identifiers
|
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.contains(ident)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn engine_get_doc(ident: &str) -> Option<String> {
|
2023-07-12 12:37:40 +08:00
|
|
|
EXPORTED_IDENTIFIERS
|
|
|
|
.docs
|
|
|
|
.read()
|
|
|
|
.unwrap()
|
|
|
|
.get(ident)
|
|
|
|
.cloned()
|
2023-05-08 12:53:19 +08:00
|
|
|
}
|
|
|
|
|
2023-07-12 12:37:40 +08:00
|
|
|
// fn get_doc(&self, ident: &str) -> Option<String> {
|
|
|
|
// self.docs.read().unwrap().get(ident).cloned()
|
|
|
|
// }
|
2023-05-08 12:53:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_highlighted_text(cx: &mut Context) -> String {
|
|
|
|
let (view, doc) = current_ref!(cx.editor);
|
|
|
|
let text = doc.text().slice(..);
|
|
|
|
doc.selection(view.id).primary().slice(text).to_string()
|
|
|
|
}
|
|
|
|
|
2023-06-30 07:08:50 +08:00
|
|
|
fn current_selection(cx: &mut Context) -> Selection {
|
|
|
|
let (view, doc) = current_ref!(cx.editor);
|
|
|
|
doc.selection(view.id).clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_selection(cx: &mut Context, selection: Selection) {
|
|
|
|
let (view, doc) = current!(cx.editor);
|
|
|
|
doc.set_selection(view.id, selection)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn current_line_number(cx: &mut Context) -> usize {
|
|
|
|
let (view, doc) = current_ref!(cx.editor);
|
|
|
|
helix_core::coords_at_pos(
|
2023-07-03 03:57:44 +08:00
|
|
|
doc.text().slice(..),
|
|
|
|
doc.selection(view.id)
|
2023-06-30 07:08:50 +08:00
|
|
|
.primary()
|
|
|
|
.cursor(doc.text().slice(..)),
|
2023-07-03 03:57:44 +08:00
|
|
|
)
|
|
|
|
.row
|
2023-06-30 07:08:50 +08:00
|
|
|
}
|
|
|
|
|
2023-05-08 12:53:19 +08:00
|
|
|
fn get_selection(cx: &mut Context) -> String {
|
|
|
|
let (view, doc) = current_ref!(cx.editor);
|
|
|
|
let text = doc.text().slice(..);
|
|
|
|
|
|
|
|
let grapheme_start = doc.selection(view.id).primary().cursor(text);
|
|
|
|
let grapheme_end = graphemes::next_grapheme_boundary(text, grapheme_start);
|
|
|
|
|
|
|
|
if grapheme_start == grapheme_end {
|
|
|
|
return "".into();
|
|
|
|
}
|
|
|
|
|
|
|
|
let grapheme = text.slice(grapheme_start..grapheme_end).to_string();
|
|
|
|
|
|
|
|
let printable = grapheme.chars().fold(String::new(), |mut s, c| {
|
|
|
|
match c {
|
|
|
|
'\0' => s.push_str("\\0"),
|
|
|
|
'\t' => s.push_str("\\t"),
|
|
|
|
'\n' => s.push_str("\\n"),
|
|
|
|
'\r' => s.push_str("\\r"),
|
|
|
|
_ => s.push(c),
|
|
|
|
}
|
|
|
|
|
|
|
|
s
|
|
|
|
});
|
|
|
|
|
|
|
|
printable
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run_in_engine(cx: &mut Context, arg: String) -> anyhow::Result<()> {
|
|
|
|
let callback = async move {
|
|
|
|
let output = ENGINE
|
|
|
|
.with(|x| x.borrow_mut().run(&arg))
|
|
|
|
.map(|x| format!("{:?}", x));
|
|
|
|
|
|
|
|
let (output, success) = match output {
|
|
|
|
Ok(v) => (Tendril::from(v), true),
|
|
|
|
Err(e) => (Tendril::from(e.to_string()), false),
|
|
|
|
};
|
|
|
|
|
|
|
|
let call: job::Callback = Callback::EditorCompositor(Box::new(
|
|
|
|
move |editor: &mut Editor, compositor: &mut Compositor| {
|
|
|
|
if !output.is_empty() {
|
|
|
|
let contents = ui::Markdown::new(
|
|
|
|
format!("```\n{}\n```", output),
|
|
|
|
editor.syn_loader.clone(),
|
|
|
|
);
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
if success {
|
|
|
|
editor.set_status("Command succeeded");
|
|
|
|
} else {
|
|
|
|
editor.set_error("Command failed");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
));
|
|
|
|
Ok(call)
|
|
|
|
};
|
|
|
|
cx.jobs.callback(callback);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_helix_scm_path() -> String {
|
|
|
|
helix_loader::helix_module_file()
|
|
|
|
.to_str()
|
|
|
|
.unwrap()
|
|
|
|
.to_string()
|
|
|
|
}
|
2023-05-10 22:48:02 +08:00
|
|
|
|
|
|
|
fn get_init_scm_path() -> String {
|
|
|
|
helix_loader::steel_init_file()
|
|
|
|
.to_str()
|
|
|
|
.unwrap()
|
|
|
|
.to_string()
|
|
|
|
}
|
2023-05-15 09:01:52 +08:00
|
|
|
|
2023-06-06 12:09:27 +08:00
|
|
|
/// 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())))
|
|
|
|
}
|
|
|
|
|
2023-08-10 13:24:29 +08:00
|
|
|
fn set_scratch_buffer_name(cx: &mut Context, name: 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_mut(doc);
|
|
|
|
|
|
|
|
if let Some(current_doc) = current_doc {
|
|
|
|
current_doc.name = Some(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-30 07:08:50 +08:00
|
|
|
fn cx_current_focus(cx: &mut Context) -> helix_view::ViewId {
|
|
|
|
cx.editor.tree.focus
|
|
|
|
}
|
|
|
|
|
2023-06-06 12:09:27 +08:00
|
|
|
// 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()
|
|
|
|
}
|
|
|
|
|
2023-06-30 07:08:50 +08:00
|
|
|
fn is_document_in_view(editor: &mut Editor, doc_id: DocumentId) -> Option<helix_view::ViewId> {
|
2023-07-03 03:57:44 +08:00
|
|
|
editor
|
|
|
|
.tree
|
|
|
|
.traverse()
|
|
|
|
.find(|(_, v)| v.doc == doc_id)
|
|
|
|
.map(|(id, _)| id)
|
2023-06-30 07:08:50 +08:00
|
|
|
}
|
|
|
|
|
2023-06-06 12:09:27 +08:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
|
2023-06-30 07:08:50 +08:00
|
|
|
fn switch(editor: &mut Editor, doc_id: DocumentId) {
|
|
|
|
editor.switch(doc_id, Action::VerticalSplit)
|
|
|
|
}
|
|
|
|
|
|
|
|
// fn editor_set_focus(editor: &mut Editor, view_id: helix_view::ViewId) {
|
|
|
|
// editor.tree.focus = view_id
|
|
|
|
// }
|
|
|
|
|
|
|
|
fn editor_get_mode(editor: &mut Editor) -> Mode {
|
|
|
|
editor.mode
|
|
|
|
}
|
|
|
|
|
|
|
|
fn editor_set_mode(editor: &mut Editor, mode: Mode) {
|
|
|
|
editor.mode = mode
|
|
|
|
}
|
|
|
|
|
|
|
|
// fn insert_text(cx: &mut Context, text: String) {
|
|
|
|
// let count = cx.count();
|
|
|
|
// let reg_name = cx.register.unwrap_or('"');
|
|
|
|
// let (view, doc) = current!(cx.editor);
|
|
|
|
// let registers = &mut cx.editor.registers;
|
|
|
|
|
|
|
|
// if let Some(values) = registers.read(reg_name) {
|
|
|
|
// paste_impl(values, doc, view, pos, count, cx.editor.mode);
|
|
|
|
// }
|
|
|
|
// }
|
2023-06-06 12:09:27 +08:00
|
|
|
// cx->editor
|
|
|
|
//
|
|
|
|
|
2023-05-15 09:01:52 +08:00
|
|
|
fn run_shell_command_text(
|
|
|
|
cx: &mut Context,
|
|
|
|
args: &[Cow<str>],
|
|
|
|
_event: PromptEvent,
|
|
|
|
) -> anyhow::Result<String> {
|
|
|
|
let shell = cx.editor.config().shell.clone();
|
|
|
|
let args = args.join(" ");
|
|
|
|
|
|
|
|
let (output, success) = shell_impl(&shell, &args, None)?;
|
|
|
|
|
|
|
|
if success {
|
|
|
|
Ok(output.to_string())
|
|
|
|
} else {
|
|
|
|
anyhow::bail!("Command failed!: {}", output.to_string())
|
|
|
|
}
|
|
|
|
}
|
2023-05-30 12:41:13 +08:00
|
|
|
|
2023-07-03 03:57:44 +08:00
|
|
|
fn is_context(value: SteelVal) -> bool {
|
|
|
|
Context::as_mut_ref_from_ref(&value).is_ok()
|
|
|
|
}
|
2023-05-30 12:41:13 +08:00
|
|
|
|
|
|
|
// Overlay the dynamic component, see what happens?
|
|
|
|
// Probably need to pin the values to this thread - wrap it in a shim which pins the value
|
|
|
|
// to this thread? - call methods on the thread local value?
|
|
|
|
fn push_component(cx: &mut Context, component: &mut WrappedDynComponent) {
|
|
|
|
log::info!("Pushing dynamic component!");
|
|
|
|
|
2023-07-03 03:57:44 +08:00
|
|
|
let inner = component.inner.take().unwrap();
|
2023-05-30 12:41:13 +08:00
|
|
|
|
2023-07-03 03:57:44 +08:00
|
|
|
let callback = async move {
|
|
|
|
let call: Box<dyn FnOnce(&mut Editor, &mut Compositor, &mut job::Jobs)> = Box::new(
|
|
|
|
move |_editor: &mut Editor, compositor: &mut Compositor, _| compositor.push(inner),
|
|
|
|
);
|
|
|
|
Ok(call)
|
|
|
|
};
|
|
|
|
cx.jobs.local_callback(callback);
|
|
|
|
}
|
2023-05-30 12:41:13 +08:00
|
|
|
|
2023-07-03 03:57:44 +08:00
|
|
|
fn enqueue_command(cx: &mut Context, callback_fn: SteelVal) {
|
|
|
|
let callback = async move {
|
|
|
|
let call: Box<dyn FnOnce(&mut Editor, &mut Compositor, &mut job::Jobs)> = Box::new(
|
|
|
|
move |editor: &mut Editor, _compositor: &mut Compositor, jobs: &mut job::Jobs| {
|
|
|
|
let mut ctx = Context {
|
|
|
|
register: None,
|
|
|
|
count: None,
|
|
|
|
editor,
|
|
|
|
callback: None,
|
|
|
|
on_next_key_callback: None,
|
|
|
|
jobs,
|
|
|
|
};
|
|
|
|
|
|
|
|
let cloned_func = callback_fn.clone();
|
|
|
|
|
2023-07-12 12:23:03 +08:00
|
|
|
if let Err(e) = ENGINE.with(|x| {
|
|
|
|
x.borrow_mut()
|
|
|
|
.with_mut_reference::<Context, Context>(&mut ctx)
|
|
|
|
.consume(move |engine, args| {
|
|
|
|
engine.call_function_with_args(cloned_func.clone(), args)
|
|
|
|
})
|
|
|
|
}) {
|
|
|
|
present_error(&mut ctx, e);
|
|
|
|
}
|
2023-07-03 03:57:44 +08:00
|
|
|
},
|
|
|
|
);
|
|
|
|
Ok(call)
|
|
|
|
};
|
|
|
|
cx.jobs.local_callback(callback);
|
2023-05-30 12:41:13 +08:00
|
|
|
}
|
|
|
|
|
2023-08-10 13:24:29 +08:00
|
|
|
// Apply arbitrary delay for update rate...
|
|
|
|
fn enqueue_command_with_delay(cx: &mut Context, delay: SteelVal, callback_fn: SteelVal) {
|
|
|
|
let callback = async move {
|
|
|
|
let delay = delay.int_or_else(|| panic!("FIX ME")).unwrap();
|
|
|
|
|
|
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(delay as u64)).await;
|
|
|
|
|
|
|
|
let call: Box<dyn FnOnce(&mut Editor, &mut Compositor, &mut job::Jobs)> = Box::new(
|
|
|
|
move |editor: &mut Editor, _compositor: &mut Compositor, jobs: &mut job::Jobs| {
|
|
|
|
let mut ctx = Context {
|
|
|
|
register: None,
|
|
|
|
count: None,
|
|
|
|
editor,
|
|
|
|
callback: None,
|
|
|
|
on_next_key_callback: None,
|
|
|
|
jobs,
|
|
|
|
};
|
|
|
|
|
|
|
|
let cloned_func = callback_fn.clone();
|
|
|
|
|
|
|
|
if let Err(e) = ENGINE.with(|x| {
|
|
|
|
x.borrow_mut()
|
|
|
|
.with_mut_reference::<Context, Context>(&mut ctx)
|
|
|
|
.consume(move |engine, args| {
|
|
|
|
engine.call_function_with_args(cloned_func.clone(), args)
|
|
|
|
})
|
|
|
|
}) {
|
|
|
|
present_error(&mut ctx, e);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
Ok(call)
|
|
|
|
};
|
|
|
|
cx.jobs.local_callback(callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
// value _must_ be a future here. Otherwise awaiting will cause problems!
|
|
|
|
fn await_value(cx: &mut Context, value: SteelVal, callback_fn: SteelVal) {
|
|
|
|
if !value.is_future() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let callback = async move {
|
|
|
|
let future_value = value.as_future().unwrap().await;
|
|
|
|
|
|
|
|
let call: Box<dyn FnOnce(&mut Editor, &mut Compositor, &mut job::Jobs)> = Box::new(
|
|
|
|
move |editor: &mut Editor, _compositor: &mut Compositor, jobs: &mut job::Jobs| {
|
|
|
|
let mut ctx = Context {
|
|
|
|
register: None,
|
|
|
|
count: None,
|
|
|
|
editor,
|
|
|
|
callback: None,
|
|
|
|
on_next_key_callback: None,
|
|
|
|
jobs,
|
|
|
|
};
|
|
|
|
|
|
|
|
let cloned_func = callback_fn.clone();
|
|
|
|
|
|
|
|
match future_value {
|
|
|
|
Ok(inner) => {
|
|
|
|
let callback = move |engine: &mut Engine, mut args: Vec<SteelVal>| {
|
|
|
|
args.push(inner);
|
|
|
|
engine.call_function_with_args(cloned_func.clone(), args)
|
|
|
|
};
|
|
|
|
if let Err(e) = ENGINE.with(|x| {
|
|
|
|
x.borrow_mut()
|
|
|
|
.with_mut_reference::<Context, Context>(&mut ctx)
|
|
|
|
.consume_once(callback)
|
|
|
|
}) {
|
|
|
|
present_error(&mut ctx, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
present_error(&mut ctx, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
Ok(call)
|
|
|
|
};
|
|
|
|
cx.jobs.local_callback(callback);
|
|
|
|
}
|
2023-07-12 12:23:03 +08:00
|
|
|
// Check that we successfully created a directory?
|
|
|
|
fn create_directory(path: String) {
|
|
|
|
let path = helix_core::path::get_canonicalized_path(&PathBuf::from(path)).unwrap();
|
|
|
|
|
|
|
|
if path.exists() {
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
std::fs::create_dir(path).unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-13 12:39:27 +08:00
|
|
|
/// Change config at runtime. Access nested values by dot syntax, for
|
|
|
|
/// example to disable smart case search, use `:set search.smart-case false`.
|
|
|
|
fn set_options(
|
|
|
|
cx: &mut compositor::Context,
|
|
|
|
args: &[Cow<str>],
|
|
|
|
event: PromptEvent,
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
if event != PromptEvent::Validate {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
if args.len() % 2 != 0 {
|
|
|
|
anyhow::bail!("Bad arguments. Usage: `:set key field`");
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut config = serde_json::json!(&cx.editor.config().deref());
|
|
|
|
// let key_error = || anyhow::anyhow!("Unknown key `{}`", key);
|
|
|
|
// let field_error = |_| anyhow::anyhow!("Could not parse field `{}`", arg);
|
|
|
|
|
|
|
|
for args in args.chunks_exact(2) {
|
|
|
|
let (key, arg) = (&args[0].to_lowercase(), &args[1]);
|
|
|
|
|
|
|
|
let key_error = || anyhow::anyhow!("Unknown key `{}`", key);
|
|
|
|
let field_error = |_| anyhow::anyhow!("Could not parse field `{}`", arg);
|
|
|
|
|
|
|
|
// let mut config = serde_json::json!(&cx.editor.config().deref());
|
|
|
|
let pointer = format!("/{}", key.replace('.', "/"));
|
|
|
|
let value = config.pointer_mut(&pointer).ok_or_else(key_error)?;
|
|
|
|
|
|
|
|
*value = if value.is_string() {
|
|
|
|
// JSON strings require quotes, so we can't .parse() directly
|
|
|
|
Value::String(arg.to_string())
|
|
|
|
} else {
|
|
|
|
arg.parse().map_err(field_error)?
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
let config =
|
|
|
|
serde_json::from_value(config).map_err(|_| anyhow::anyhow!("Could not parse config"))?;
|
|
|
|
|
|
|
|
cx.editor
|
|
|
|
.config_events
|
|
|
|
.0
|
|
|
|
.send(ConfigEvent::Update(config))?;
|
|
|
|
Ok(())
|
|
|
|
}
|