diff --git a/Cargo.lock b/Cargo.lock index 926221595..89555d423 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -157,6 +157,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "async-ffi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed5a937a789391ebc1c77d3a15b060e0d100e6d7c483a2af3f250d6b8dc2a23" +dependencies = [ + "abi_stable", +] + [[package]] name = "atty" version = "0.2.14" @@ -189,12 +198,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" - [[package]] name = "beef" version = "0.5.2" @@ -313,10 +316,7 @@ checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ "android-tzdata", "iana-time-zone", - "js-sys", "num-traits", - "time 0.1.45", - "wasm-bindgen", "winapi", ] @@ -382,15 +382,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "coolor" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4d7a805ca0d92f8c61a31c809d4323fdaa939b0b440e544d21db7797c5aaad" -dependencies = [ - "crossterm 0.23.2", -] - [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -421,20 +412,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" -dependencies = [ - "cfg-if", - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - [[package]] name = "crossbeam-channel" version = "0.5.8" @@ -445,40 +422,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.16" @@ -488,22 +431,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossterm" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2102ea4f781910f8a5b98dd061f4c2023f479ce7bb1236330099ceb5a93cf17" -dependencies = [ - "bitflags 1.3.2", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - [[package]] name = "crossterm" version = "0.26.1" @@ -702,31 +629,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" -dependencies = [ - "futures-core", - "futures-sink", -] - [[package]] name = "futures-core" version = "0.3.28" @@ -744,12 +646,6 @@ dependencies = [ "futures-util", ] -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - [[package]] name = "futures-macro" version = "0.3.28" @@ -761,12 +657,6 @@ dependencies = [ "syn 2.0.23", ] -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - [[package]] name = "futures-task" version = "0.3.28" @@ -779,13 +669,9 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ - "futures-channel", "futures-core", - "futures-io", "futures-macro", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -826,7 +712,7 @@ checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -1015,7 +901,7 @@ dependencies = [ "bstr", "itoa", "thiserror", - "time 0.3.22", + "time", ] [[package]] @@ -1625,7 +1511,7 @@ dependencies = [ "arc-swap", "chrono", "content_inspector", - "crossterm 0.26.1", + "crossterm", "dlopen", "dlopen_derive", "fern", @@ -1665,7 +1551,7 @@ version = "0.6.0" dependencies = [ "bitflags 2.3.3", "cassowary", - "crossterm 0.26.1", + "crossterm", "helix-core", "helix-view", "log", @@ -1700,7 +1586,7 @@ dependencies = [ "bitflags 2.3.3", "chardetng", "clipboard-win", - "crossterm 0.26.1", + "crossterm", "futures-util", "helix-core", "helix-dap", @@ -1889,15 +1775,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.6" @@ -2047,24 +1924,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "minimad" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1b13e2000bd8e238d97a97de6fc30224f89a08b0aa5aaa09ed1bd68ba2fa1" -dependencies = [ - "once_cell", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2088,7 +1947,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys", ] @@ -2268,12 +2127,11 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "pretty" -version = "0.11.3" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f3aa1e3ca87d3b124db7461265ac176b40c277f37e503eaa29c9c75c037846" +checksum = "563c9d701c3a31dfffaaf9ce23507ba09cbe0b9125ba176d15e629b0235e9acc" dependencies = [ "arrayvec", - "log", "typed-arena", "unicode-segmentation", ] @@ -2350,6 +2208,12 @@ dependencies = [ "proc-macro2 1.0.63", ] +[[package]] +name = "radix_fmt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426" + [[package]] name = "rand" version = "0.8.5" @@ -2445,21 +2309,6 @@ dependencies = [ "tstr", ] -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - [[package]] name = "ropey" version = "1.6.0" @@ -2499,28 +2348,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "rustls" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f" -dependencies = [ - "log", - "ring", - "rustls-webpki", - "sct", -] - -[[package]] -name = "rustls-webpki" -version = "0.100.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "ryu" version = "1.0.13" @@ -2542,16 +2369,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "semver" version = "1.0.17" @@ -2718,12 +2535,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "static_assertions" version = "1.1.0" @@ -2732,48 +2543,43 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "steel-core" -version = "0.4.0" +version = "0.5.0" dependencies = [ "abi_stable", "anyhow", + "async-ffi", "bincode", "chrono", "codespan-reporting", "colored", - "dlopen", - "dlopen_derive", - "futures", + "futures-task", + "futures-util", "fxhash", "im-lists", "im-rc", - "itertools", "lasso", - "lazy_static", "log", "logos", "num", "once_cell", "pretty", "quickscope", + "radix_fmt", "rand", "serde", "serde_derive", "serde_json", "slotmap", - "smallvec", "steel-derive", "steel-gen", "steel-parser", - "termimad", - "thiserror", - "ureq", "weak-table", "which", ] [[package]] name = "steel-derive" -version = "0.2.0" +version = "0.4.0" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.29", @@ -2785,14 +2591,13 @@ name = "steel-gen" version = "0.2.0" dependencies = [ "codegen", - "itertools", "serde", "serde_derive", ] [[package]] name = "steel-parser" -version = "0.2.0" +version = "0.4.0" dependencies = [ "logos", "num-bigint", @@ -2868,20 +2673,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "termimad" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8743d628f9b0eb33087c1e1c4915d91efca23ae69f7c81981489128a0e17d300" -dependencies = [ - "coolor", - "crossbeam", - "crossterm 0.23.2", - "minimad", - "thiserror", - "unicode-width", -] - [[package]] name = "termini" version = "1.0.0" @@ -2941,17 +2732,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - [[package]] name = "time" version = "0.3.22" @@ -3179,30 +2959,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "ureq" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9" -dependencies = [ - "base64", - "flate2", - "log", - "once_cell", - "rustls", - "rustls-webpki", - "serde", - "serde_json", - "url", - "webpki-roots", -] - [[package]] name = "url" version = "2.4.0" @@ -3231,12 +2987,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3303,25 +3053,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549" -[[package]] -name = "web-sys" -version = "0.3.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" -dependencies = [ - "rustls-webpki", -] - [[package]] name = "which" version = "4.4.0" diff --git a/Cargo.toml b/Cargo.toml index 0997f8335..38240ce0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ default-members = [ ] [workspace.dependencies] -steel-core = { path = "../../steel/crates/steel-core", version = "0.4.0", features = ["modules", "anyhow", "blocking_requests"] } +steel-core = { path = "../../steel/crates/steel-core", version = "0.5.0", features = ["modules", "anyhow", "dylibs", "colors"] } [profile.release] lto = "thin" diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index 6f449cd4f..4170525d6 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -960,7 +960,6 @@ pub fn custom_indent_for_newline( let line = text_up_to_cursor.line(cursor); // We want to ignore comments - let l = std::borrow::Cow::from(line); if l.trim_start().starts_with(";") { if cursor == 0 { @@ -972,8 +971,6 @@ pub fn custom_indent_for_newline( continue; } - // log::info!("Line: {}", line); - for (index, char) in line.chars_at(line.len_chars()).reversed().enumerate() { match char { ')' | ']' | '}' => { diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 5d2307bb3..683c7d546 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -266,7 +266,7 @@ impl Application { jobs: &mut app.jobs, }; - crate::commands::run_initialization_script(&mut cx); + crate::commands::ScriptingEngine::run_initialization_script(&mut cx); } Ok(app) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e97960c1b..9ded152f6 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -8,8 +8,7 @@ pub use dap::*; use helix_vcs::Hunk; pub use lsp::*; -pub use engine::{initialize_engine, run_initialization_script}; -use steel::rvals::IntoSteelVal; +pub use engine::ScriptingEngine; use tokio::sync::oneshot; use tui::widgets::Row; pub use typed::*; @@ -55,7 +54,6 @@ use movement::Movement; use crate::{ args, - commands::engine::{CallbackQueue, ScriptingEngine}, compositor::{self, Component, Compositor}, filter_picker_entry, job::Callback, @@ -214,7 +212,7 @@ impl MappableCommand { cx.editor.set_error(format!("{}", e)); } } else { - ScriptingEngine::call_function_if_global_exists(cx, name, args) + ScriptingEngine::call_function_if_global_exists(cx, name, args); } } Self::Static { fun, .. } => (fun)(cx), @@ -2558,6 +2556,7 @@ fn buffer_picker(cx: &mut Context) { struct BufferMeta { id: DocumentId, path: Option, + name: Option, is_modified: bool, is_current: bool, focused_at: std::time::Instant, @@ -2571,7 +2570,11 @@ fn buffer_picker(cx: &mut Context) { .path .as_deref() .map(helix_core::path::get_relative_path); - let path = match path.as_deref().and_then(Path::to_str) { + let path = match path + .as_deref() + .and_then(Path::to_str) + .or_else(|| self.name.as_ref().map(|x| x.as_str())) + { Some(path) => path, None => SCRATCH_BUFFER_NAME, }; @@ -2591,6 +2594,7 @@ fn buffer_picker(cx: &mut Context) { let new_meta = |doc: &Document| BufferMeta { id: doc.id(), path: doc.path().cloned(), + name: doc.name.clone(), is_modified: doc.is_modified(), is_current: doc.id() == current, focused_at: doc.focused_at, diff --git a/helix-term/src/commands/engine.rs b/helix-term/src/commands/engine.rs index 15fa89189..f451a87d8 100644 --- a/helix-term/src/commands/engine.rs +++ b/helix-term/src/commands/engine.rs @@ -77,6 +77,125 @@ pub trait PluginSystem { fn get_doc_for_identifier(ident: &str) -> Option; } +// 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), +} + +impl KeyMapApi { + fn new() -> Self { + KeyMapApi { + get_keymap, + default_keymap, + empty_keymap, + string_to_embedded_keymap, + merge_keybindings, + } + } +} + +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); + + // 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], 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!() +} + impl ScriptingEngine { pub fn initialize() { initialize_engine(); @@ -111,11 +230,8 @@ impl ScriptingEngine { }; if let Some(extension) = extension { - let special_buffer_map = "*buffer-or-extension-keybindings*"; - - let value = ENGINE.with(|x| x.borrow().extract_value(special_buffer_map).clone()); - - if let Ok(SteelVal::HashMapV(map)) = value { + if let SteelVal::HashMapV(map) = BUFFER_OR_EXTENSION_KEYBINDING_MAP.with(|x| x.clone()) + { if let Some(value) = map.get(&SteelVal::StringV(extension.into())) { if let SteelVal::Custom(inner) = value { if let Some(_) = steel::rvals::as_underlying_type::( @@ -128,16 +244,12 @@ impl ScriptingEngine { } } - // reverse-buffer-map -> label -> keybinding map - let value = ENGINE.with(|x| x.borrow().extract_value("*reverse-buffer-map*").clone()); - - if let Ok(SteelVal::HashMapV(map)) = value { + // TODO: Remove these clones + if let SteelVal::HashMapV(map) = REVERSE_BUFFER_MAP.with(|x| x.clone()) { if let Some(label) = map.get(&SteelVal::IntV(document_id_to_usize(doc_id) as isize)) { - let special_buffer_map = "*buffer-or-extension-keybindings*"; - - let value = ENGINE.with(|x| x.borrow().extract_value(special_buffer_map).clone()); - - if let Ok(SteelVal::HashMapV(map)) = value { + if let SteelVal::HashMapV(map) = + BUFFER_OR_EXTENSION_KEYBINDING_MAP.with(|x| x.clone()) + { if let Some(value) = map.get(label) { if let SteelVal::Custom(inner) = value { if let Some(_) = steel::rvals::as_underlying_type::( @@ -154,7 +266,11 @@ impl ScriptingEngine { None } - pub fn call_function_if_global_exists(cx: &mut Context, name: &str, args: Vec>) { + pub fn call_function_if_global_exists( + cx: &mut Context, + name: &str, + args: Vec>, + ) -> bool { if ENGINE.with(|x| x.borrow().global_exists(name)) { let args = steel::List::from( args.iter() @@ -181,6 +297,9 @@ impl ScriptingEngine { }) { cx.editor.set_error(format!("{}", e)); } + true + } else { + false } } @@ -230,21 +349,9 @@ impl ScriptingEngine { guard.register_value("_helix_args", steel::rvals::SteelVal::Void); - // if let Some(callback) = cx.callback.take() { - // panic!("Found a callback!"); - // maybe_callback = Some(callback); - // } - res }; - // TODO: Recursively (or otherwise) keep retrying until we're back - // into the engine context, executing a function. We might need to set up - // some sort of fuel or something - // if let Some(callback) = maybe_callback { - // (callback)(_, cx); - // } - res }) { compositor_present_error(cx, e); @@ -431,7 +538,7 @@ pub fn merge_keybindings(left: &mut EmbeddedKeyMap, right: EmbeddedKeyMap) { /// 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 -pub fn run_initialization_script(cx: &mut Context) { +fn run_initialization_script(cx: &mut Context) { log::info!("Loading init.scm..."); let helix_module_path = helix_loader::steel_init_file(); @@ -440,7 +547,12 @@ pub fn run_initialization_script(cx: &mut Context) { if let Ok(contents) = std::fs::read_to_string(&helix_module_path) { let res = ENGINE.with(|x| { x.borrow_mut() - .run_with_reference::(cx, "*helix.cx*", &contents) + .run_with_reference_from_path::( + cx, + "*helix.cx*", + &contents, + helix_module_path, + ) }); match res { @@ -452,18 +564,11 @@ pub fn run_initialization_script(cx: &mut Context) { } else { log::info!("No init.scm found, skipping loading.") } - - // Start the worker thread - i.e. message passing to the workers - // configure_background_thread() } -// pub static MINOR_MODES: Lazy = Lazy::new(|| SharedKeyBindingsEventQueue::new()); -// pub static CALLBACK_QUEUE: Lazy = Lazy::new(|| ) - pub static CALLBACK_QUEUE: Lazy = Lazy::new(|| CallbackQueue::new()); pub static EXPORTED_IDENTIFIERS: Lazy = @@ -715,6 +820,9 @@ fn configure_engine() -> std::rc::Rc 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( + // "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::::new("popup", BoxDynComponent::new(inner)) + // .position(Some(position)), + // )), + // } + // }, + // ); engine.register_fn( "Prompt::new", @@ -915,6 +1023,8 @@ fn configure_engine() -> std::rc::Rc::register_fn(&mut engine, "cx-editor!", get_editor); + engine.register_fn("set-scratch-buffer-name!", set_scratch_buffer_name); + engine.register_fn("editor-focus", current_focus); engine.register_fn("editor->doc-id", get_document_id); engine.register_fn("doc-id->usize", document_id_to_usize); @@ -1015,58 +1125,6 @@ fn configure_engine() -> std::rc::Rc]>, - // event: PromptEvent| - // -> anyhow::Result<()> { - // // Ensure the lifetime of these variables - // let _config = cx.editor.config.clone(); - // let _theme_loader = cx.editor.theme_loader.clone(); - // let _syn_loader = cx.editor.syn_loader.clone(); - - // println!("{}", Arc::strong_count(&_config)); - // println!("{}", Arc::strong_count(&_theme_loader)); - // println!("{}", Arc::strong_count(&_syn_loader)); - // // println!("{:p}", _theme_loader); - // // println!("{:p}", _syn_loader); - - // (inner)(cx, &_theme_loader, &_syn_loader, args, &event) - // }; - - // module.register_owned_fn(command.name.to_string(), func); - // } - - // engine.register_module(module); - // } - engine.register_module(module); let mut module = BuiltInModule::new("helix/core/static".to_string()); @@ -1113,6 +1171,12 @@ fn configure_engine() -> std::rc::Rc Option { current_doc.and_then(|x| x.path().and_then(|x| x.to_str().map(|x| x.to_string()))) } +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); + } +} + fn cx_current_focus(cx: &mut Context) -> helix_view::ViewId { cx.editor.tree.focus } @@ -1477,6 +1553,87 @@ fn enqueue_command(cx: &mut Context, callback_fn: SteelVal) { cx.jobs.local_callback(callback); } +// 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 = 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::(&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 = 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| { + 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::(&mut ctx) + .consume_once(callback) + }) { + present_error(&mut ctx, e); + } + } + Err(e) => { + present_error(&mut ctx, e); + } + } + }, + ); + Ok(call) + }; + cx.jobs.local_callback(callback); +} // Check that we successfully created a directory? fn create_directory(path: String) { let path = helix_core::path::get_canonicalized_path(&PathBuf::from(path)).unwrap(); diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 9b819a927..da47171c4 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -636,6 +636,8 @@ pub(super) fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> let (modified_ids, modified_names): (Vec<_>, Vec<_>) = editor .documents() .filter(|doc| doc.is_modified()) + // Named scratch documents should not be included here + .filter(|doc| doc.name.is_none()) .map(|doc| (doc.id(), doc.display_name())) .unzip(); if let Some(first) = modified_ids.first() { diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index b2d9f7bef..2cab2a9ec 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -127,7 +127,7 @@ FLAGS: helix_loader::initialize_config_file(args.config_file.clone()); // Initialize the engine before we boot up! - helix_term::commands::initialize_engine(); + helix_term::commands::ScriptingEngine::initialize(); let config = match Config::load_default() { Ok(config) => config, diff --git a/helix-term/src/ui/document.rs b/helix-term/src/ui/document.rs index 80da1c542..031326ae9 100644 --- a/helix-term/src/ui/document.rs +++ b/helix-term/src/ui/document.rs @@ -6,7 +6,7 @@ use helix_core::str_utils::char_to_byte_idx; use helix_core::syntax::Highlight; use helix_core::syntax::HighlightEvent; use helix_core::text_annotations::TextAnnotations; -use helix_core::{visual_offset_from_block, Position, RopeSlice}; +use helix_core::{visual_offset_from_block, Position, RopeSlice, Range}; use helix_view::editor::{WhitespaceConfig, WhitespaceRenderValue}; use helix_view::graphics::Rect; use helix_view::theme::Style; @@ -64,6 +64,7 @@ impl> Iterator for StyleIter<'_, H> { .fold(self.text_style, |acc, span| { acc.patch(self.theme.highlight(span.0)) }); + return Some((style, end)); } } @@ -72,6 +73,55 @@ impl> Iterator for StyleIter<'_, H> { } } + +/// A wrapper around a HighlightIterator +/// that merges the layered highlights to create the final text style +/// and yields the active text style and the char_idx where the active +/// style will have to be recomputed. +struct RawStyleIter<'a, H: Iterator> { + text_style: Style, + active_highlights: Vec, + highlight_iter: H, + theme: &'a Theme, +} + +impl> Iterator for RawStyleIter<'_, H> { + type Item = (Style, usize); + fn next(&mut self) -> Option<(Style, usize)> { + while let Some(event) = self.highlight_iter.next() { + + // let style = self.active_highlights.iter().fold(self.text_style, |acc, span| { + // acc.patch(event.1) + // }); + + return Some((self.text_style.patch(event.1), event.0.head)) + + // match event { + // HighlightEvent::HighlightStart(highlights) => { + // self.active_highlights.push(highlights) + // } + // HighlightEvent::HighlightEnd => { + // self.active_highlights.pop(); + // } + // HighlightEvent::Source { start, end } => { + // if start == end { + // continue; + // } + // let style = self + // .active_highlights + // .iter() + // .fold(self.text_style, |acc, span| { + // acc.patch(self.theme.highlight(span.0)) + // }); + + // return Some((style, end)); + // } + // } + } + None + } +} + #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub struct LinePos { /// Indicates whether the given visual line @@ -103,6 +153,7 @@ pub fn render_document( translated_positions: &mut [TranslatedPosition], ) { let mut renderer = TextRenderer::new(surface, doc, theme, offset.horizontal_offset, viewport); + render_text( &mut renderer, doc.text().slice(..), @@ -113,6 +164,7 @@ pub fn render_document( theme, line_decoration, translated_positions, + &doc.highlights, ) } @@ -161,6 +213,7 @@ pub fn render_text<'t>( theme: &Theme, line_decorations: &mut [Box], translated_positions: &mut [TranslatedPosition], + highlight_overrides: &[(helix_core::Range, Style)], ) { let ( Position { @@ -178,6 +231,7 @@ pub fn render_text<'t>( let (mut formatter, mut first_visible_char_idx) = DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, text_annotations, offset.anchor); + let mut styles = StyleIter { text_style: renderer.text_style, active_highlights: Vec::with_capacity(64), @@ -215,7 +269,7 @@ pub fn render_text<'t>( renderer, last_pos, ); - } + } break; }; @@ -286,6 +340,8 @@ pub fn render_text<'t>( style_span.0 }; + // log::info!("Grapheme style: {:?}", grapheme_style); + let virt = grapheme.is_virtual(); renderer.draw_grapheme( grapheme.grapheme, @@ -301,6 +357,170 @@ pub fn render_text<'t>( for line_decoration in &mut *line_decorations { line_decoration.render_foreground(renderer, last_line_pos, char_pos); } + + // If we have nothing... don't continue + // if highlight_overrides.is_empty() { + // return; + // } + + // { + + // let ( + // Position { + // row: mut row_off, .. + // }, + // mut char_pos, + // ) = visual_offset_from_block( + // text, + // offset.anchor, + // offset.anchor, + // text_fmt, + // text_annotations, + // ); + // row_off += offset.vertical_offset; + + // let (mut formatter, mut first_visible_char_idx) = + // DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, text_annotations, offset.anchor); + + // let mut styles = RawStyleIter { + // text_style: renderer.text_style, + // active_highlights: Vec::with_capacity(64), + // highlight_iter: std::iter::once(( + // helix_core::Range::new(0, highlight_overrides[0].0.anchor), + // Style::default(), + // )).chain(highlight_overrides.iter().cloned()), + // theme + // }; + // // StyleIter { + // // text_style: renderer.text_style, + // // active_highlights: Vec::with_capacity(64), + // // highlight_iter, + // // theme, + // // }; + // // .chain( + // // highlight_overrides + // // .iter() + // // .map(|(range, style)| (*style, range.anchor)); + // // ); + + // let mut last_line_pos = LinePos { + // first_visual_line: false, + // doc_line: usize::MAX, + // visual_line: u16::MAX, + // start_char_idx: usize::MAX, + // }; + // let mut is_in_indent_area = true; + // let mut last_line_indent_level = 0; + // let mut style_span = styles + // .next() + // .unwrap_or_else(|| (Style::default(), usize::MAX)); + + // loop { + // // formattter.line_pos returns to line index of the next grapheme + // // so it must be called before formatter.next + // let doc_line = formatter.line_pos(); + // let Some((grapheme, mut pos)) = formatter.next() else { + // let mut last_pos = formatter.visual_pos(); + // if last_pos.row >= row_off { + // last_pos.col -= 1; + // last_pos.row -= row_off; + // // check if any positions translated on the fly (like cursor) are at the EOF + // translate_positions( + // char_pos + 1, + // first_visible_char_idx, + // translated_positions, + // text_fmt, + // renderer, + // last_pos, + // ); + // } + // break; + // }; + + // // skip any graphemes on visual lines before the block start + // if pos.row < row_off { + // if char_pos >= style_span.1 { + // style_span = if let Some(style_span) = styles.next() { + // style_span + // } else { + // break; + // } + // } + // char_pos += grapheme.doc_chars(); + // first_visible_char_idx = char_pos + 1; + // continue; + // } + // pos.row -= row_off; + + // // if the end of the viewport is reached stop rendering + // if pos.row as u16 >= renderer.viewport.height { + // break; + // } + + // // apply decorations before rendering a new line + // if pos.row as u16 != last_line_pos.visual_line { + // if pos.row > 0 { + // renderer.draw_indent_guides(last_line_indent_level, last_line_pos.visual_line); + // is_in_indent_area = true; + // for line_decoration in &mut *line_decorations { + // line_decoration.render_foreground(renderer, last_line_pos, char_pos); + // } + // } + // last_line_pos = LinePos { + // first_visual_line: doc_line != last_line_pos.doc_line, + // doc_line, + // visual_line: pos.row as u16, + // start_char_idx: char_pos, + // }; + // for line_decoration in &mut *line_decorations { + // line_decoration.render_background(renderer, last_line_pos); + // } + // } + + // // acquire the correct grapheme style + // if char_pos >= style_span.1 { + // style_span = styles.next().unwrap_or((Style::default(), usize::MAX)); + // } + // char_pos += grapheme.doc_chars(); + + // // check if any positions translated on the fly (like cursor) has been reached + // translate_positions( + // char_pos, + // first_visible_char_idx, + // translated_positions, + // text_fmt, + // renderer, + // pos, + // ); + + // let grapheme_style = if let GraphemeSource::VirtualText { highlight } = grapheme.source { + // let style = renderer.text_style; + // if let Some(highlight) = highlight { + // style.patch(theme.highlight(highlight.0)) + // } else { + // style + // } + // } else { + // style_span.0 + // }; + + // let virt = grapheme.is_virtual(); + // renderer.draw_grapheme( + // grapheme.grapheme, + // grapheme_style, + // virt, + // &mut last_line_indent_level, + // &mut is_in_indent_area, + // pos, + // ); + // } + + // renderer.draw_indent_guides(last_line_indent_level, last_line_pos.visual_line); + // for line_decoration in &mut *line_decorations { + // line_decoration.render_foreground(renderer, last_line_pos, char_pos); + // } + // } + } #[derive(Debug)] diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 50135432a..194f2ef09 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -221,6 +221,13 @@ impl EditorView { statusline::RenderContext::new(editor, doc, view, is_focused, &self.spinners); statusline::render(&mut context, statusline_area, surface); + + // Custom rendering table? + // for x in 0..20 { + // if let Some(cell) = surface.get_mut(x, 0) { + // cell.fg = Color::Green; + // } + // } } pub fn render_rulers( diff --git a/helix-term/src/ui/statusline.rs b/helix-term/src/ui/statusline.rs index 29625f36c..cb6ec8cc1 100644 --- a/helix-term/src/ui/statusline.rs +++ b/helix-term/src/ui/statusline.rs @@ -425,6 +425,7 @@ where let path = rel_path .as_ref() .map(|p| p.to_string_lossy()) + .or_else(|| context.doc.name.as_ref().map(|x| x.into())) .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into()); format!(" {} ", path) }; @@ -455,6 +456,7 @@ where let path = rel_path .as_ref() .and_then(|p| p.as_path().file_name().map(|s| s.to_string_lossy())) + .or_else(|| context.doc.name.as_ref().map(|x| x.into())) .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into()); format!(" {} ", path) }; diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index b08370f9f..6e9739507 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -185,6 +185,14 @@ pub struct Document { // when document was used for most-recent-used buffer picker pub focused_at: std::time::Instant, + + // TODO: This is not really something we _want_, but more seeing if it is + // enough to get away with custom applications of colors. + // Selection -> Style to apply for that selection AFTER rendering. + pub highlights: Vec<(Range, crate::graphics::Style)>, + + // A name separate from the file name + pub name: Option, } /// Inlay hints for a single `(Document, View)` combo. @@ -634,6 +642,25 @@ where use helix_lsp::{lsp, Client, LanguageServerName}; use url::Url; +#[derive(Clone, PartialEq, Eq)] +struct RawHighlight { + pub start: usize, + pub end: usize, + pub style: crate::graphics::Style, +} + +impl PartialOrd for RawHighlight { + fn partial_cmp(&self, other: &Self) -> Option { + self.end.partial_cmp(&other.start) + } +} + +impl Ord for RawHighlight { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.end.cmp(&other.start) + } +} + impl Document { pub fn from( text: Rope, @@ -673,6 +700,11 @@ impl Document { config, version_control_head: None, focused_at: std::time::Instant::now(), + highlights: vec![( + helix_core::Range::new(10, 20), + crate::graphics::Style::default().fg(crate::graphics::Color::Green), + )], + name: None, } } @@ -1623,6 +1655,7 @@ impl Document { pub fn display_name(&self) -> Cow<'static, str> { self.relative_path() .map(|path| path.to_string_lossy().to_string().into()) + .or_else(|| self.name.as_ref().map(|x| Cow::Owned(x.clone()))) .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into()) }