From 01996b2bec334b544cdf249cbbfd01bdff0776c4 Mon Sep 17 00:00:00 2001 From: mattwparas Date: Mon, 1 Jan 2024 21:18:07 -0800 Subject: [PATCH] No more passing around the helix context for every function call --- Cargo.lock | 133 +---- Cargo.toml | 4 +- helix-core/src/extensions.rs | 12 +- helix-core/src/indent.rs | 1 - helix-term/src/commands.rs | 2 - helix-term/src/commands/engine/scheme.rs | 729 +++++++++++++++++------ helix-view/src/editor.rs | 1 - rust-toolchain.toml | 4 +- 8 files changed, 572 insertions(+), 314 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42ad12234..427444183 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,17 +65,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.3" @@ -159,9 +148,9 @@ dependencies = [ [[package]] name = "async-ffi" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ed5a937a789391ebc1c77d3a15b060e0d100e6d7c483a2af3f250d6b8dc2a23" +checksum = "f4de21c0feef7e5a556e51af767c953f0501f7f300ba785cc99c47bdc8081a50" dependencies = [ "abi_stable", ] @@ -187,12 +176,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" - [[package]] name = "bincode" version = "1.3.3" @@ -347,17 +330,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "colored" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" -dependencies = [ - "is-terminal", - "lazy_static", - "windows-sys", -] - [[package]] name = "const_panic" version = "0.2.8" @@ -481,12 +453,15 @@ dependencies = [ [[package]] name = "dashmap" -version = "4.0.2" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "num_cpus", + "hashbrown 0.14.0", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] @@ -1394,16 +1369,6 @@ dependencies = [ "memmap2 0.5.10", ] -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash 0.7.6", - "serde", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -1416,7 +1381,8 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.3", + "ahash", + "serde", ] [[package]] @@ -1425,7 +1391,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" dependencies = [ - "ahash 0.8.3", + "ahash", "allocator-api2", ] @@ -1433,7 +1399,7 @@ dependencies = [ name = "helix-core" version = "0.6.0" dependencies = [ - "ahash 0.8.3", + "ahash", "arc-swap", "bitflags 2.4.0", "chrono", @@ -1484,7 +1450,7 @@ dependencies = [ name = "helix-event" version = "0.6.0" dependencies = [ - "ahash 0.8.3", + "ahash", "anyhow", "futures-executor", "hashbrown 0.13.2", @@ -1720,9 +1686,9 @@ dependencies = [ [[package]] name = "im-lists" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe1ea6399f751563e6f5d88bff90a5c7418f8e7abbdd34708412be793a73949" +checksum = "a8ce6c776654abe411e4ccd52524f87e8ca16e2c487dbe127521eb048a46f7e6" [[package]] name = "im-rc" @@ -1744,7 +1710,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8" dependencies = [ - "ahash 0.8.3", + "ahash", "hashbrown 0.12.3", ] @@ -1784,17 +1750,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys", -] - [[package]] name = "itoa" version = "1.0.9" @@ -1821,12 +1776,12 @@ dependencies = [ [[package]] name = "lasso" -version = "0.6.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeb7b21a526375c5ca55f1a6dfd4e1fad9fa4edd750f530252a718a44b2608f0" +checksum = "4644821e1c3d7a560fe13d842d13f587c07348a1a05d3a797152d41c90c56df2" dependencies = [ "dashmap", - "hashbrown 0.11.2", + "hashbrown 0.13.2", "serde", ] @@ -1884,29 +1839,6 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" -[[package]] -name = "logos" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1" -dependencies = [ - "logos-derive", -] - -[[package]] -name = "logos-derive" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" -dependencies = [ - "beef", - "fnv", - "proc-macro2", - "quote", - "regex-syntax 0.6.29", - "syn 1.0.109", -] - [[package]] name = "lsp-types" version = "0.94.1" @@ -2426,18 +2358,18 @@ checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -2446,9 +2378,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -2593,7 +2525,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "steel-core" version = "0.5.0" -source = "git+https://github.com/mattwparas/steel.git#45f582560445da5faaaa19098c5b50e7f872c10e" +source = "git+https://github.com/mattwparas/steel.git#03fe335199334f44396949fda2508de3545bc895" dependencies = [ "abi_stable", "anyhow", @@ -2601,7 +2533,6 @@ dependencies = [ "bincode", "chrono", "codespan-reporting", - "colored", "futures-task", "futures-util", "fxhash", @@ -2609,7 +2540,6 @@ dependencies = [ "im-rc", "lasso", "log", - "logos", "num", "once_cell", "pretty", @@ -2619,7 +2549,6 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "slotmap", "steel-derive", "steel-gen", "steel-parser", @@ -2630,7 +2559,7 @@ dependencies = [ [[package]] name = "steel-derive" version = "0.4.0" -source = "git+https://github.com/mattwparas/steel.git#45f582560445da5faaaa19098c5b50e7f872c10e" +source = "git+https://github.com/mattwparas/steel.git#03fe335199334f44396949fda2508de3545bc895" dependencies = [ "proc-macro2", "quote", @@ -2640,7 +2569,7 @@ dependencies = [ [[package]] name = "steel-gen" version = "0.2.0" -source = "git+https://github.com/mattwparas/steel.git#45f582560445da5faaaa19098c5b50e7f872c10e" +source = "git+https://github.com/mattwparas/steel.git#03fe335199334f44396949fda2508de3545bc895" dependencies = [ "codegen", "serde", @@ -2650,10 +2579,12 @@ dependencies = [ [[package]] name = "steel-parser" version = "0.4.0" -source = "git+https://github.com/mattwparas/steel.git#45f582560445da5faaaa19098c5b50e7f872c10e" +source = "git+https://github.com/mattwparas/steel.git#03fe335199334f44396949fda2508de3545bc895" dependencies = [ - "logos", + "lasso", "num-bigint", + "once_cell", + "pretty", "serde", "serde_derive", ] diff --git a/Cargo.toml b/Cargo.toml index 24cbffcc0..4a64d3e66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,8 @@ default-members = [ [workspace.dependencies] # If working locally, use the local path dependency -# steel-core = { path = "../../steel/crates/steel-core", version = "0.5.0", features = ["modules", "anyhow", "dylibs", "colors"] } -steel-core = { git = "https://github.com/mattwparas/steel.git", version = "0.5.0", features = ["modules", "anyhow", "dylibs", "colors"] } +# steel-core = { path = "../../steel/crates/steel-core", version = "0.5.0", features = ["modules", "anyhow", "dylibs"] } +steel-core = { git = "https://github.com/mattwparas/steel.git", version = "0.5.0", features = ["modules", "anyhow", "dylibs"] } tree-sitter = { version = "0.20", git = "https://github.com/tree-sitter/tree-sitter", rev = "ab09ae20d640711174b8da8a654f6b3dec93da1a" } nucleo = "0.2.0" diff --git a/helix-core/src/extensions.rs b/helix-core/src/extensions.rs index 5f14dae3c..e1e0bbb9d 100644 --- a/helix-core/src/extensions.rs +++ b/helix-core/src/extensions.rs @@ -1,16 +1,12 @@ #[cfg(feature = "steel")] pub mod steel_implementations { - use std::{borrow::Cow, cell::Cell, rc::Rc}; + use std::borrow::Cow; - use ropey::iter::Chars; use smallvec::SmallVec; use steel::{ - gc::unsafe_erased_pointers::CustomReference, rvals::{Custom, SteelString}, - steel_vm::{ - builtin::BuiltInModule, register_fn::RegisterFn, register_fn::RegisterFnBorrowed, - }, + steel_vm::{builtin::BuiltInModule, register_fn::RegisterFn}, }; impl steel::rvals::Custom for crate::Position {} @@ -100,10 +96,6 @@ pub mod steel_implementations { maybe_owned.trim_start().starts_with(pat.as_str()) } - - // pub fn as_cow(&'a self) -> SRopeSliceCowStr<'a> { - // SRopeSliceCowStr(std::borrow::Cow::from(self.slice)) - // } } pub fn rope_module() -> BuiltInModule { diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index 6bd0d780d..33963b649 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -1,6 +1,5 @@ use std::{borrow::Cow, collections::HashMap}; -use once_cell::sync::Lazy; use tree_sitter::{Query, QueryCursor, QueryPredicateArg}; use crate::{ diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 4972a411e..968056728 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -190,8 +190,6 @@ macro_rules! static_commands { impl MappableCommand { pub fn execute(&self, cx: &mut Context) { - log::info!("Running command"); - match &self { Self::Typable { name, args, doc: _ } => { let args: Vec> = args.iter().map(Cow::from).collect(); diff --git a/helix-term/src/commands/engine/scheme.rs b/helix-term/src/commands/engine/scheme.rs index 6d4f7d71f..bb3459d10 100644 --- a/helix-term/src/commands/engine/scheme.rs +++ b/helix-term/src/commands/engine/scheme.rs @@ -15,19 +15,22 @@ use helix_view::{ editor::{Action, ConfigEvent}, extension::document_id_to_usize, input::KeyEvent, - Document, DocumentId, Editor, Theme, + Document, DocumentId, Editor, Theme, ViewId, }; use once_cell::sync::Lazy; use serde_json::Value; use steel::{ gc::unsafe_erased_pointers::CustomReference, rerrs::ErrorKind, - rvals::{as_underlying_type, AsRefMutSteelValFromRef, FromSteelVal, IntoSteelVal}, + rvals::{as_underlying_type, AsRefMutSteelValFromRef, FromSteelVal, IntoSteelVal, SteelString}, steel_vm::{engine::Engine, register_fn::RegisterFn}, - steelerr, SteelErr, SteelVal, + steelerr, List, SteelErr, SteelVal, }; -use std::{borrow::Cow, cell::RefCell, collections::HashMap, ops::Deref, path::PathBuf, rc::Rc}; +use std::{ + borrow::Cow, cell::RefCell, collections::HashMap, ops::Deref, path::PathBuf, rc::Rc, + sync::atomic::AtomicUsize, +}; use std::{ collections::HashSet, sync::{Arc, RwLock}, @@ -47,28 +50,20 @@ use crate::{ use components::SteelDynamicComponent; -use super::{components, shell_impl, Context, MappableCommand, TYPABLE_COMMAND_LIST}; +use super::{components, Context, MappableCommand, TYPABLE_COMMAND_LIST}; use insert::{insert_char, insert_string}; thread_local! { pub static ENGINE: std::rc::Rc> = configure_engine(); } -enum CoreModules { - Document(DocumentApi), - Editor(EditorApi), - Component(ComponentApi), - TypedCommands(TypedCommandsApi), - StaticCommands(StaticCommandsApi), -} - // 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 DocumentApi; +// pub struct EditorApi; +// pub struct ComponentApi; +// pub struct TypedCommandsApi; +// pub struct StaticCommandsApi; pub struct KeyMapApi { get_keymap: fn() -> EmbeddedKeyMap, @@ -132,8 +127,6 @@ impl LanguageConfigurationContainer { if let Some(left) = left { let right = serde_json::from_str(&config_as_string).map_err(|err| err.to_string()); - // panic!("{:#?}", right); - match right { Ok(right) => { self.configuration @@ -267,6 +260,9 @@ fn load_keymap_api(engine: &mut Engine, api: KeyMapApi) { fn load_static_commands(engine: &mut Engine) { let mut module = BuiltInModule::new("helix/core/static"); + let mut builtin_static_command_module = + "(require-builtin helix/core/static as helix.static.)".to_string(); + for command in TYPABLE_COMMAND_LIST { let func = |cx: &mut Context| { let mut cx = compositor::Context { @@ -286,151 +282,315 @@ fn load_static_commands(engine: &mut Engine) { for command in MappableCommand::STATIC_COMMAND_LIST { if let MappableCommand::Static { name, fun, .. } = command { module.register_fn(name, fun); + + builtin_static_command_module.push_str(&format!( + r#" +(provide {}) +(define ({}) + (helix.static.{} *helix.cx*)) +"#, + name, name, name + )); } } + let mut template_function_arity_1 = |name: &str| { + builtin_static_command_module.push_str(&format!( + r#" +(provide {}) +(define ({} arg) + (helix.static.{} *helix.cx* arg)) +"#, + name, name, name + )); + }; + // Adhoc static commands that probably needs evaluating + // Note: The current templating here to generate the wrapper functions + // does not need to happen every time. This can probably just happen + // once, and then wired up to the xtask framework. Otherwise, for dev + // builds we can have it happen each time so that the functions are + // consistent. + + // Arity 1 module.register_fn("insert_char", insert_char); + template_function_arity_1("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); - module.register_fn("get-current-line-number", current_line_number); - module.register_fn("current-selection-object", current_selection); + template_function_arity_1("insert_string"); module.register_fn("set-current-selection-object!", set_selection); + template_function_arity_1("set-current-selection-object!"); module.register_fn("run-in-engine!", run_in_engine); - module.register_fn("get-helix-scm-path", get_helix_scm_path); - module.register_fn("get-init-scm-path", get_init_scm_path); - module.register_fn("get-helix-cwd", get_helix_cwd); + template_function_arity_1("run-in-engine!"); module.register_fn("search-in-directory", search_in_directory); + template_function_arity_1("search-in-directory"); module.register_fn("regex-selection", regex_selection); + template_function_arity_1("regex-selection"); module.register_fn("replace-selection-with", replace_selection); + template_function_arity_1("replace-selection-with"); module.register_fn("show-completion-prompt-with", show_completion_prompt); + template_function_arity_1("show-completion-prompt-with"); + module.register_fn("cx->current-file", current_path); + template_function_arity_1("cx->current-file"); + + let mut template_function_arity_0 = |name: &str| { + builtin_static_command_module.push_str(&format!( + r#" +(provide {}) +(define ({}) + (helix.static.{} *helix.cx*)) +"#, + name, name, name + )); + }; + + // Arity 0 + module.register_fn("current_selection", get_selection); + template_function_arity_0("current_selection"); + + module.register_fn("current-highlighted-text!", get_highlighted_text); + template_function_arity_0("current-highlighted-text!"); + + module.register_fn("get-current-line-number", current_line_number); + template_function_arity_0("get-current-line-number"); + + module.register_fn("current-selection-object", current_selection); + template_function_arity_0("current-selection-object"); + + module.register_fn("get-helix-cwd", get_helix_cwd); + template_function_arity_0("get-helix-cwd"); + module.register_fn("move-window-far-left", move_window_to_the_left); + template_function_arity_0("move-window-far-left"); + module.register_fn("move-window-far-right", move_window_to_the_right); + template_function_arity_0("move-window-far-right"); + + // This should probably just get removed? + // module.register_fn("block-on-shell-command", run_shell_command_text); + + let mut template_function_no_context = |name: &str| { + builtin_static_command_module.push_str(&format!( + r#" +(provide {}) +(define {} helix.static.{}) + "#, + name, name, name + )) + }; + + module.register_fn("get-helix-scm-path", get_helix_scm_path); + module.register_fn("get-init-scm-path", get_init_scm_path); - module.register_fn("block-on-shell-command", run_shell_command_text); + template_function_no_context("get-helix-scm-path"); + template_function_no_context("get-init-scm-path"); - module.register_fn("cx->current-file", current_path); + let mut target_directory = PathBuf::from(std::env::var("STEEL_HOME").unwrap()); + target_directory.push("cogs"); + target_directory.push("helix"); + + if !target_directory.exists() { + std::fs::create_dir(&target_directory).unwrap(); + } + + target_directory.push("static.scm"); + + std::fs::write(target_directory, builtin_static_command_module).unwrap(); engine.register_module(module); } fn load_typed_commands(engine: &mut Engine) { let mut module = BuiltInModule::new("helix/core/typable".to_string()); + let mut builtin_typable_command_module = + "(require-builtin helix/core/typable as helix.)".to_string(); { - let func = |cx: &mut Context, args: &[Cow], event: PromptEvent| { + let func = |cx: &mut Context, args: &[Cow]| { let mut cx = compositor::Context { editor: cx.editor, scroll: None, jobs: cx.jobs, }; - set_options(&mut cx, args, event) + set_options(&mut cx, args, PromptEvent::Validate) }; module.register_fn("set-options", func); - } + let name = "set-options"; - module.register_value( - "PromptEvent::Validate", - PromptEvent::Validate.into_steelval().unwrap(), - ); - module.register_value( - "PromptEvent::Update", - PromptEvent::Update.into_steelval().unwrap(), - ); + builtin_typable_command_module.push_str(&format!( + r#" +(provide {}) + +(define ({} . args) + (helix.{} *helix.cx* args)) +"#, + name, name, name + )); + } // 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], event: PromptEvent| { + let func = |cx: &mut Context, args: &[Cow]| { let mut cx = compositor::Context { editor: cx.editor, scroll: None, jobs: cx.jobs, }; - (command.fun)(&mut cx, args, event) + (command.fun)(&mut cx, args, PromptEvent::Validate) }; module.register_fn(command.name, func); + + // Create an ephemeral builtin module to reference until I figure out how + // to wrap the functions with a reference to the engine context better. + builtin_typable_command_module.push_str(&format!( + r#" +(provide {}) + +;;@doc +{} +(define ({} . args) + (helix.{} *helix.cx* args)) +"#, + command.name, + command + .doc + .lines() + .map(|x| { + let mut line = ";;".to_string(); + line.push_str(x); + line.push_str("\n"); + line + }) + .collect::(), + command.name, + command.name + )); + } + + let mut target_directory = PathBuf::from(std::env::var("STEEL_HOME").unwrap()); + target_directory.push("cogs"); + target_directory.push("helix"); + + if !target_directory.exists() { + std::fs::create_dir(&target_directory).unwrap(); } + target_directory.push("commands.scm"); + + std::fs::write(target_directory, builtin_typable_command_module).unwrap(); + engine.register_module(module); } -fn load_editor_api(engine: &mut Engine, _api: EditorApi) { +fn load_editor_api(engine: &mut Engine) { let mut module = BuiltInModule::new("helix/core/editor"); - RegisterFn::< - _, - steel::steel_vm::register_fn::MarkerWrapper7<( - Context<'_>, - helix_view::Editor, - helix_view::Editor, - Context<'static>, - )>, - helix_view::Editor, - >::register_fn(&mut module, "cx-editor!", get_editor); + let mut builtin_editor_command_module = + "(require-builtin helix/core/editor as helix.)".to_string(); + + let mut template_function_arity_0 = |name: &str| { + builtin_editor_command_module.push_str(&format!( + r#" +(provide {}) +(define ({}) + (helix.{} *helix.cx*)) +"#, + name, name, name + )); + }; + // Arity 0 + module.register_fn("editor-focus", cx_current_focus); + module.register_fn("editor-mode", cx_get_mode); + module.register_fn("cx->themes", get_themes); + module.register_fn("editor-all-documents", cx_editor_all_documents); + module.register_fn("cx->cursor", |cx: &mut Context| cx.editor.cursor()); + + template_function_arity_0("editor-focus"); + template_function_arity_0("editor-mode"); + template_function_arity_0("cx->themes"); + template_function_arity_0("editor-all-documents"); + template_function_arity_0("cx->cursor"); + + let mut template_function_arity_1 = |name: &str| { + builtin_editor_command_module.push_str(&format!( + r#" +(provide {}) +(define ({} arg) + (helix.{} *helix.cx* arg)) +"#, + name, name, name + )); + }; + + // Arity 1 + module.register_fn("editor->doc-id", cx_get_document_id); + module.register_fn("editor-switch!", cx_switch); + module.register_fn("editor-set-focus!", |cx: &mut Context, view_id: ViewId| { + cx.editor.focus(view_id) + }); + module.register_fn("editor-set-mode!", cx_set_mode); + module.register_fn("editor-doc-in-view?", cx_is_document_in_view); module.register_fn("set-scratch-buffer-name!", set_scratch_buffer_name); + module.register_fn("editor-doc-exists?", cx_document_exists); + + template_function_arity_1("editor->doc-id"); + template_function_arity_1("editor-switch!"); + template_function_arity_1("editor-set-focus!"); + template_function_arity_1("editor-set-mode!"); + template_function_arity_1("editor-doc-in-view?"); + template_function_arity_1("set-scratch-buffer-name!"); + template_function_arity_1("editor-doc-exists?"); + template_function_arity_1("editor->get-document"); - module.register_fn("editor-focus", current_focus); - module.register_fn("editor->doc-id", get_document_id); - module.register_fn("doc-id->usize", document_id_to_usize); - module.register_fn("editor-switch!", switch); - module.register_fn("editor-set-focus!", Editor::focus); - module.register_fn("editor-mode", editor_get_mode); - module.register_fn("editor-set-mode!", editor_set_mode); - module.register_fn("editor-doc-in-view?", is_document_in_view); - - // 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) + // Doesn't use the context + // module.register_fn("doc-id->usize", document_id_to_usize); + + // Arity 1 RegisterFn::< _, steel::steel_vm::register_fn::MarkerWrapper8<( - helix_view::Editor, + Context, DocumentId, Document, Document, - helix_view::Editor, + Context, )>, Document, - >::register_fn(&mut module, "editor->get-document", get_document); - - // Check if the doc exists first - module.register_fn("editor-doc-exists?", document_exists); - module.register_fn("Document-path", document_path); - module.register_fn("Document-focused-at", document_focused_at); - module.register_fn("editor-all-documents", editor_all_documents); - - module.register_fn("helix.context?", is_context); - module.register_type::("DocumentId?"); + >::register_fn(&mut module, "editor->get-document", cx_get_document); // I do not like this - module.register_fn("editor-cursor", Editor::cursor); + // module.register_fn("Document-path", document_path); - module.register_fn("cx->cursor", |cx: &mut Context| cx.editor.cursor()); + // module.register_fn("helix.context?", is_context); + // module.register_type::("DocumentId?"); // TODO: // Position related functions. These probably should be defined alongside the actual impl for Custom in the core crate - module.register_fn("Position::new", helix_core::Position::new); - module.register_fn("Position::default", helix_core::Position::default); - module.register_fn("Position-row", |position: helix_core::Position| { - position.row - }); + // module.register_fn("position", helix_core::Position::new); + // module.register_fn("position-default", helix_core::Position::default); + // module.register_fn("position-row", |position: helix_core::Position| { + // position.row + // }); - module.register_fn("cx->themes", get_themes); + // module.register_fn("cx->themes", get_themes); - engine.register_module(module); -} + // Not the best usage here, duplicating it in a bunch of spots for now + let mut target_directory = PathBuf::from(std::env::var("STEEL_HOME").unwrap()); + target_directory.push("cogs"); + target_directory.push("helix"); -fn load_document_api(engine: &mut Engine, api: DocumentApi) { - todo!("Decide what should go in the document API!") -} + if !target_directory.exists() { + std::fs::create_dir(&target_directory).unwrap(); + } -fn load_component_api(engine: &mut Engine, api: ComponentApi) { - todo!("Decide what should go in the component API") + target_directory.push("editor.scm"); + + std::fs::write(target_directory, builtin_editor_command_module).unwrap(); + + engine.register_module(module); } pub struct SteelScriptingEngine; @@ -485,10 +645,14 @@ impl super::PluginSystem for SteelScriptingEngine { { guard.with_mut_reference::(cx).consume( - move |engine, mut arguments| { - arguments.append(&mut args); + move |engine, arguments| { + let context = arguments[0].clone(); + engine.update_value("*helix.cx*", context); - engine.call_function_by_name_with_args(name, arguments) + // arguments.append(&mut args); + + // TODO: Get rid of this clone + engine.call_function_by_name_with_args(name, args.clone()) }, ) } @@ -515,7 +679,7 @@ impl super::PluginSystem for SteelScriptingEngine { // We're finalizing the event - we actually want to call the function if event == PromptEvent::Validate { if let Err(e) = ENGINE.with(|x| { - let mut args = args[1..] + let args = args[1..] .iter() .map(|x| x.clone().into_steelval().unwrap()) .collect::>(); @@ -534,10 +698,11 @@ impl super::PluginSystem for SteelScriptingEngine { guard .with_mut_reference(&mut cx) - .consume(|engine, mut arguments| { - arguments.append(&mut args); - - engine.call_function_by_name_with_args(&parts[0], arguments) + .consume(move |engine, arguments| { + let context = arguments[0].clone(); + engine.update_value("*helix.cx*", context); + // TODO: Fix this clone + engine.call_function_by_name_with_args(&parts[0], args.clone()) }) }; @@ -824,10 +989,11 @@ fn run_initialization_script(cx: &mut Context) { ENGINE.with(|engine| { let mut guard = engine.borrow_mut(); - let res = guard.run(&format!( - r#"(require "{}")"#, - helix_module_path.to_str().unwrap() - )); + let res = guard.run_with_reference( + cx, + "*helix.cx*", + &format!(r#"(require "{}")"#, helix_module_path.to_str().unwrap()), + ); // Present the error in the helix.scm loading if let Err(e) = res { @@ -918,10 +1084,6 @@ impl<'a> CustomReference for Context<'a> {} steel::custom_reference!(Context<'a>); -fn get_editor<'a>(cx: &'a mut Context<'a>) -> &'a mut Editor { - cx.editor -} - fn get_themes(cx: &mut Context) -> Vec { ui::completers::theme(cx.editor, "") .into_iter() @@ -1039,12 +1201,13 @@ fn register_hook(event_kind: String, function_name: String) -> steel::UnRecovera new_mode: event.new_mode, }; - guard - .with_mut_reference(event.cx) - .consume(|engine, mut args| { - args.push(minimized_event.into_steelval().unwrap()); - engine.call_function_by_name_with_args(&function_name, args) - }) + guard.with_mut_reference(event.cx).consume(|engine, args| { + let context = args[0].clone(); + engine.update_value("*helix.cx*", context); + + let args = vec![minimized_event.into_steelval().unwrap()]; + engine.call_function_by_name_with_args(&function_name, args) + }) }) { event.cx.editor.set_error(format!("{}", e)); } @@ -1061,12 +1224,16 @@ fn register_hook(event_kind: String, function_name: String) -> steel::UnRecovera if let Err(e) = ENGINE.with(|x| { let mut guard = x.borrow_mut(); - guard - .with_mut_reference(event.cx) - .consume(|engine, mut args| { - args.push(event.c.into()); - engine.call_function_by_name_with_args(&function_name, args) - }) + guard.with_mut_reference(event.cx).consume(|engine, args| { + let context = args[0].clone(); + engine.update_value("*helix.cx*", context); + + // args.push(event.c.into()); + engine.call_function_by_name_with_args( + &function_name, + vec![event.c.into()], + ) + }) }) { event.cx.editor.set_error(format!("{}", e)); } @@ -1083,12 +1250,16 @@ fn register_hook(event_kind: String, function_name: String) -> steel::UnRecovera if let Err(e) = ENGINE.with(|x| { let mut guard = x.borrow_mut(); - guard - .with_mut_reference(event.cx) - .consume(|engine, mut args| { - args.push(event.command.clone().into_steelval().unwrap()); - engine.call_function_by_name_with_args(&function_name, args) - }) + guard.with_mut_reference(event.cx).consume(|engine, args| { + let context = args[0].clone(); + engine.update_value("*helix.cx*", context); + + // args.push(event.command.clone().into_steelval().unwrap()); + engine.call_function_by_name_with_args( + &function_name, + vec![event.command.clone().into_steelval().unwrap()], + ) + }) }) { event.cx.editor.set_error(format!("{}", e)); } @@ -1119,13 +1290,166 @@ fn load_rope_api(engine: &mut Engine) { engine.register_module(rope_slice_module); } -fn configure_engine() -> std::rc::Rc> { - let mut engine = steel::steel_vm::engine::Engine::new(); +struct SteelEngine(Engine); + +impl SteelEngine { + pub fn call_function_by_name( + &mut self, + function_name: SteelString, + args: List, + ) -> steel::rvals::Result { + self.0 + .call_function_by_name_with_args(function_name.as_str(), args.into_iter().collect()) + } + + /// Calling a function that was not defined in the runtime it was created in could + /// result in panics. You have been warned. + pub fn call_function( + &mut self, + function: SteelVal, + args: List, + ) -> steel::rvals::Result { + self.0 + .call_function_with_args(function, args.into_iter().collect()) + } + + pub fn require_module(&mut self, module: SteelString) -> steel::rvals::Result<()> { + self.0.run(&format!("(require \"{}\")", module)).map(|_| ()) + } +} + +impl Custom for SteelEngine {} + +static ENGINE_ID: AtomicUsize = AtomicUsize::new(0); + +thread_local! { + pub static ENGINE_MAP: SteelVal = + SteelVal::boxed(SteelVal::empty_hashmap()); +} + +// Low level API work, these need to be loaded into the global environment in a predictable +// location, otherwise callbacks from plugin engines will not be handled properly! +fn load_engine_api(engine: &mut Engine) { + fn id_to_engine(value: SteelVal) -> Option { + if let SteelVal::Boxed(b) = ENGINE_MAP.with(|x| x.clone()) { + if let SteelVal::HashMapV(h) = b.borrow().clone() { + return h.get(&value).cloned(); + } + } + + None + } + + // module + engine + .register_fn("helix.controller.create-engine", || { + SteelEngine(configure_engine_impl(Engine::new())) + }) + .register_fn("helix.controller.fresh-engine-id", || { + ENGINE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + }) + .register_fn( + "helix.controller.call-function-by-name", + SteelEngine::call_function_by_name, + ) + .register_fn("helix.controller.call-function", SteelEngine::call_function) + .register_fn( + "helix.controller.require-module", + SteelEngine::require_module, + ) + .register_value( + "helix.controller.engine-map", + ENGINE_MAP.with(|x| x.clone()), + ) + .register_fn("helix.controller.id->engine", id_to_engine); +} + +fn load_misc_api(engine: &mut Engine) { + let mut module = BuiltInModule::new("helix/core/misc"); + + let mut builtin_misc_module = "(require-builtin helix/core/misc as helix.)".to_string(); + + let mut template_function_arity_0 = |name: &str| { + builtin_misc_module.push_str(&format!( + r#" +(provide {}) +(define ({}) + (helix.{} *helix.cx*)) +"#, + name, name, name + )); + }; + + // Arity 0 + module.register_fn("hx.cx->pos", cx_pos_within_text); + + template_function_arity_0("hx.cx->pos"); + + let mut template_function_arity_1 = |name: &str| { + builtin_misc_module.push_str(&format!( + r#" +(provide {}) +(define ({} arg) + (helix.{} *helix.cx* arg)) +"#, + name, name, name + )); + }; + + // Arity 1 + module.register_fn("hx.custom-insert-newline", custom_insert_newline); + module.register_fn("push-component!", push_component); + module.register_fn("enqueue-thread-local-callback", enqueue_command); + + template_function_arity_1("hx.custom-insert-newline"); + template_function_arity_1("push-component!"); + template_function_arity_1("enqueue-thread-local-callback"); + + let mut template_function_arity_2 = |name: &str| { + builtin_misc_module.push_str(&format!( + r#" +(provide {}) +(define ({} arg1 arg2) + (helix.{} *helix.cx* arg1 arg2)) +"#, + name, name, name + )); + }; + + // Arity 2 + module.register_fn( + "enqueue-thread-local-callback-with-delay", + enqueue_command_with_delay, + ); + + // Arity 2 + module.register_fn("helix-await-callback", await_value); + + template_function_arity_2("enqueue-thread-local-callback-with-delay"); + template_function_arity_2("helix-await-callback"); + + let mut target_directory = PathBuf::from(std::env::var("STEEL_HOME").unwrap()); + target_directory.push("cogs"); + target_directory.push("helix"); + + if !target_directory.exists() { + std::fs::create_dir(&target_directory).unwrap(); + } + + target_directory.push("misc.scm"); + + std::fs::write(target_directory, builtin_misc_module).unwrap(); + + engine.register_module(module); +} +fn configure_engine_impl(mut engine: Engine) -> Engine { log::info!("Loading engine!"); + engine.register_value("*helix.cx*", SteelVal::Void); + // TODO: Load (require-builtin helix/core/editor) in more or less every file that needs it - load_editor_api(&mut engine, EditorApi); + load_editor_api(&mut engine); load_typed_commands(&mut engine); load_static_commands(&mut engine); load_keymap_api(&mut engine, KeyMapApi::new()); @@ -1133,30 +1457,23 @@ fn configure_engine() -> std::rc::Rcpos", cx_pos_within_text); - // Find the workspace engine.register_fn("helix-find-workspace", || { helix_core::find_workspace().0.to_str().unwrap().to_string() }); + engine.register_fn("Document-path", document_path); + engine.register_fn("doc-id->usize", document_id_to_usize); + // Get the current OS - engine.register_fn("current-os!", || std::env::consts::OS); engine.register_fn("new-component!", SteelDynamicComponent::new_dyn); engine.register_fn("SteelDynamicComponent?", |object: SteelVal| { @@ -1196,26 +1513,8 @@ fn configure_engine() -> std::rc::Rc WrappedDynComponent { - // let inner = contents.inner.take().unwrap(); // Panic, for now - - // WrappedDynComponent { - // inner: Some(Box::new( - // Popup::::new("popup", BoxDynComponent::new(inner)) - // .position(Some(position)), - // )), - // } - // }, - // ); - engine.register_fn( - "Prompt::new", + "prompt", |prompt: String, callback_fn: SteelVal| -> WrappedDynComponent { let callback_fn_guard = callback_fn.as_rooted(); @@ -1244,11 +1543,20 @@ fn configure_engine() -> std::rc::Rc(&mut ctx) - .consume(move |engine, mut args| { + .consume(move |engine, args| { + let context = args[0].clone(); + + // Should work I believe, but this way we can start taking out the references + // to typed commands directly using this + engine.update_value("*helix.cx*", context); + // Add the string as an argument to the callback - args.push(input.into_steelval().unwrap()); + // args.push(input.into_steelval().unwrap()); - engine.call_function_with_args(cloned_func.clone(), args) + engine.call_function_with_args( + cloned_func.clone(), + vec![input.into_steelval().unwrap()], + ) }) }) { present_error(&mut ctx, e); @@ -1263,7 +1571,7 @@ fn configure_engine() -> std::rc::Rc| -> WrappedDynComponent { let picker = ui::Picker::new( Vec::new(), @@ -1351,19 +1659,17 @@ fn configure_engine() -> std::rc::Rc std::rc::Rc> { + let engine = configure_engine_impl(steel::steel_vm::engine::Engine::new()); - // Create directory since we can't do that in the current state - engine.register_fn("hx.create-directory", create_directory); + // engine.run(&"(require \"/home/matt/Documents/helix-fork/helix/helix-term/src/commands/engine/controller.scm\" + // (for-syntax \"/home/matt/Documents/helix-fork/helix/helix-term/src/commands/engine/controller.scm\"))").unwrap(); std::rc::Rc::new(std::cell::RefCell::new(engine)) } @@ -1518,16 +1824,28 @@ fn current_focus(editor: &mut Editor) -> helix_view::ViewId { editor.tree.focus } +fn cx_current_focus(cx: &mut Context) -> helix_view::ViewId { + cx.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 } +fn cx_get_document_id(cx: &mut Context, view_id: helix_view::ViewId) -> DocumentId { + cx.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 cx_get_document<'a>(cx: &'a mut Context, doc_id: DocumentId) -> &'a Document { + cx.editor.documents.get(&doc_id).unwrap() +} + fn document_to_text(doc: &Document) -> SteelRopeSlice { SteelRopeSlice::new(doc.text().clone()) } @@ -1540,10 +1858,22 @@ fn is_document_in_view(editor: &mut Editor, doc_id: DocumentId) -> Option Option { + cx.editor + .tree + .traverse() + .find(|(_, v)| v.doc == doc_id) + .map(|(id, _)| id) +} + fn document_exists(editor: &mut Editor, doc_id: DocumentId) -> bool { editor.documents.get(&doc_id).is_some() } +fn cx_document_exists(cx: &mut Context, doc_id: DocumentId) -> bool { + cx.editor.documents.get(&doc_id).is_some() +} + fn document_path(doc: &Document) -> Option { doc.path().and_then(|x| x.to_str()).map(|x| x.to_string()) } @@ -1558,33 +1888,32 @@ fn editor_all_documents(editor: &mut Editor) -> Vec { editor.documents.keys().copied().collect() } +fn cx_editor_all_documents(cx: &mut Context) -> Vec { + cx.editor.documents.keys().copied().collect() +} + fn switch(editor: &mut Editor, doc_id: DocumentId) { editor.switch(doc_id, Action::VerticalSplit) } +fn cx_switch(cx: &mut Context, doc_id: DocumentId) { + cx.editor.switch(doc_id, Action::VerticalSplit) +} + fn editor_get_mode(editor: &mut Editor) -> Mode { editor.mode } +fn cx_get_mode(cx: &mut Context) -> Mode { + cx.editor.mode +} + fn editor_set_mode(editor: &mut Editor, mode: Mode) { editor.mode = mode } -fn run_shell_command_text( - cx: &mut Context, - args: &[Cow], - _event: PromptEvent, -) -> anyhow::Result { - 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()) - } +fn cx_set_mode(cx: &mut Context, mode: Mode) { + cx.editor.mode = mode } fn is_context(value: SteelVal) -> bool { @@ -1629,7 +1958,10 @@ fn enqueue_command(cx: &mut Context, callback_fn: SteelVal) { x.borrow_mut() .with_mut_reference::(&mut ctx) .consume(move |engine, args| { - engine.call_function_with_args(cloned_func.clone(), args) + let context = args[0].clone(); + engine.update_value("*helix.cx*", context); + + engine.call_function_with_args(cloned_func.clone(), Vec::new()) }) }) { present_error(&mut ctx, e); @@ -1667,7 +1999,9 @@ fn enqueue_command_with_delay(cx: &mut Context, delay: SteelVal, callback_fn: St x.borrow_mut() .with_mut_reference::(&mut ctx) .consume(move |engine, args| { - engine.call_function_with_args(cloned_func.clone(), args) + let context = args[0].clone(); + engine.update_value("*helix.cx*", context); + engine.call_function_with_args(cloned_func.clone(), Vec::new()) }) }) { present_error(&mut ctx, e); @@ -1705,9 +2039,12 @@ fn await_value(cx: &mut Context, value: SteelVal, callback_fn: SteelVal) { match future_value { Ok(inner) => { - let callback = move |engine: &mut Engine, mut args: Vec| { - args.push(inner); - engine.call_function_with_args(cloned_func.clone(), args) + let callback = move |engine: &mut Engine, args: Vec| { + let context = args[0].clone(); + engine.update_value("*helix.cx*", context); + + // args.push(inner); + engine.call_function_with_args(cloned_func.clone(), vec![inner]) }; if let Err(e) = ENGINE.with(|x| { x.borrow_mut() @@ -1984,6 +2321,7 @@ fn show_completion_prompt(cx: &mut Context, items: Vec) { cx.jobs.callback(callback); } +// TODO: Remove this! fn move_window_to_the_left(cx: &mut Context) { while cx .editor @@ -1993,6 +2331,7 @@ fn move_window_to_the_left(cx: &mut Context) { {} } +// TODO: Remove this! fn move_window_to_the_right(cx: &mut Context) { while cx .editor diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 60ae93cd6..27eba23fb 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -883,7 +883,6 @@ pub struct Breakpoint { use futures_util::stream::{Flatten, Once}; -#[repr(C)] pub struct Editor { /// Current editing mode. pub mode: Mode, diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 4b395fcdf..0e0d8bc5e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.65.0" -components = ["rustfmt", "rust-src"] \ No newline at end of file +channel = "1.70.0" +components = ["rustfmt", "rust-src"]