diff --git a/Cargo.lock b/Cargo.lock index 78f2fd67e..eb55d8050 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1488,9 +1488,9 @@ dependencies = [ [[package]] name = "im-lists" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c07eff2c41645923382085ea8627509e5184f7a668f75d0c1e16091f7af4798" +checksum = "3e7233fb8b1ffc0b1d6033fd311a74a0443164628c62abbc871185ee95098b63" dependencies = [ "smallvec", ] @@ -1742,6 +1742,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" dependencies = [ "num-traits", + "serde", ] [[package]] @@ -1775,6 +1776,7 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", + "serde", ] [[package]] @@ -2307,7 +2309,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "steel-core" version = "0.6.0" -source = "git+https://github.com/mattwparas/steel.git#ad307d9ab163b99ac6dc0bc06a04601042a3f325" +source = "git+https://github.com/mattwparas/steel.git#1fb88e561474c723a4fd2675553d6091a380aef1" dependencies = [ "abi_stable", "anyhow", @@ -2335,6 +2337,7 @@ dependencies = [ "steel-derive", "steel-gen", "steel-parser", + "strsim", "weak-table", "which 4.4.2", ] @@ -2342,7 +2345,7 @@ dependencies = [ [[package]] name = "steel-derive" version = "0.5.0" -source = "git+https://github.com/mattwparas/steel.git#ad307d9ab163b99ac6dc0bc06a04601042a3f325" +source = "git+https://github.com/mattwparas/steel.git#1fb88e561474c723a4fd2675553d6091a380aef1" dependencies = [ "proc-macro2", "quote", @@ -2352,7 +2355,7 @@ dependencies = [ [[package]] name = "steel-gen" version = "0.2.0" -source = "git+https://github.com/mattwparas/steel.git#ad307d9ab163b99ac6dc0bc06a04601042a3f325" +source = "git+https://github.com/mattwparas/steel.git#1fb88e561474c723a4fd2675553d6091a380aef1" dependencies = [ "codegen", "serde", @@ -2362,15 +2365,16 @@ dependencies = [ [[package]] name = "steel-parser" version = "0.6.0" -source = "git+https://github.com/mattwparas/steel.git#ad307d9ab163b99ac6dc0bc06a04601042a3f325" +source = "git+https://github.com/mattwparas/steel.git#1fb88e561474c723a4fd2675553d6091a380aef1" dependencies = [ "fxhash", "lasso", - "num-bigint", + "num", "once_cell", "pretty", "serde", "serde_derive", + "smallvec", ] [[package]] @@ -2379,6 +2383,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9557cb6521e8d009c51a8666f09356f4b817ba9ba0981a305bd86aee47bd35c" +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "syn" version = "1.0.109" diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6c3936a6c..b0b354e17 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -81,7 +81,6 @@ use ignore::{DirEntry, WalkBuilder, WalkState}; pub type OnKeyCallback = Box; -#[repr(C)] pub struct Context<'a> { pub register: Option, pub count: Option, diff --git a/helix-term/src/commands/engine.rs b/helix-term/src/commands/engine.rs index 9afaef7fc..a910b46a8 100644 --- a/helix-term/src/commands/engine.rs +++ b/helix-term/src/commands/engine.rs @@ -1,6 +1,5 @@ use arc_swap::ArcSwapAny; -use helix_core::syntax::Configuration; -use helix_view::{document::Mode, input::KeyEvent, Theme}; +use helix_view::{document::Mode, input::KeyEvent}; use std::{borrow::Cow, sync::Arc}; @@ -11,7 +10,7 @@ use crate::{ ui::{self, PromptEvent}, }; -use super::{shell_impl, Context, MappableCommand, TYPABLE_COMMAND_LIST}; +use super::{Context, MappableCommand, TYPABLE_COMMAND_LIST}; #[cfg(feature = "steel")] mod components; @@ -138,6 +137,12 @@ impl ScriptingEngine { .flat_map(|kind| manual_dispatch!(kind, available_commands())) .collect() } + + pub fn generate_sources() { + for kind in PLUGIN_PRECEDENCE { + manual_dispatch!(kind, generate_sources()) + } + } } impl PluginSystem for NoEngine { @@ -215,4 +220,6 @@ pub trait PluginSystem { fn available_commands<'a>(&self) -> Vec> { Vec::new() } + + fn generate_sources(&self) {} } diff --git a/helix-term/src/commands/engine/scheme.rs b/helix-term/src/commands/engine/scheme.rs index 48018c695..e8aa26fa0 100644 --- a/helix-term/src/commands/engine/scheme.rs +++ b/helix-term/src/commands/engine/scheme.rs @@ -21,17 +21,19 @@ use helix_view::{ Document, DocumentId, Editor, ViewId, }; use once_cell::sync::Lazy; -use serde_json::Value; use steel::{ gc::unsafe_erased_pointers::CustomReference, rerrs::ErrorKind, rvals::{as_underlying_type, FromSteelVal, IntoSteelVal, SteelString}, steel_vm::{engine::Engine, register_fn::RegisterFn}, - steelerr, List, SteelErr, SteelVal, + steelerr, SteelErr, SteelVal, }; use std::{ - borrow::Cow, collections::HashMap, ops::Deref, path::PathBuf, sync::atomic::AtomicUsize, + borrow::Cow, + collections::HashMap, + path::PathBuf, + sync::atomic::{AtomicUsize, Ordering}, time::Duration, }; use std::{ @@ -56,13 +58,20 @@ use components::SteelDynamicComponent; use super::{components, Context, MappableCommand, TYPABLE_COMMAND_LIST}; use insert::{insert_char, insert_string}; +static ENGINE_COUNT: AtomicUsize = AtomicUsize::new(0); + // TODO: // I'm not entirely sure this is correct, however from observation it seems that // interactions with this really only happen on one thread. I haven't yet found // multiple instances of the engine getting created. I'll need to go observe how // and when the engine gets accessed. thread_local! { - pub static ENGINE: std::rc::Rc> = configure_engine(); + pub static ENGINE: std::rc::Rc> = { + if ENGINE_COUNT.fetch_add(1, Ordering::SeqCst) != 0 { + panic!("More than one instance of the steel runtime is not allowed!"); + } + configure_engine() + }; } pub struct KeyMapApi { @@ -94,7 +103,8 @@ impl KeyMapApi { // and can instead just refer to the single configuration object. // // Possibly also introduce buffer / language specific keybindings -// there as well. +// there as well, however how that interaction is done is still up +// for debate. thread_local! { pub static BUFFER_OR_EXTENSION_KEYBINDING_MAP: SteelVal = SteelVal::boxed(SteelVal::empty_hashmap()); @@ -135,11 +145,14 @@ fn load_keymap_api(engine: &mut Engine, api: KeyMapApi) { engine.register_module(module); } -fn load_static_commands(engine: &mut Engine) { +fn load_static_commands(engine: &mut Engine, generate_sources: bool) { let mut module = BuiltInModule::new("helix/core/static"); - let mut builtin_static_command_module = - "(require-builtin helix/core/static as helix.static.)".to_string(); + let mut builtin_static_command_module = if generate_sources { + "(require-builtin helix/core/static as helix.static.)".to_string() + } else { + "".to_string() + }; for command in TYPABLE_COMMAND_LIST { let func = |cx: &mut Context| { @@ -161,26 +174,30 @@ fn load_static_commands(engine: &mut Engine) { if let MappableCommand::Static { name, fun, .. } = command { module.register_fn(name, fun); - builtin_static_command_module.push_str(&format!( - r#" + if generate_sources { + builtin_static_command_module.push_str(&format!( + r#" (provide {}) (define ({}) (helix.static.{} *helix.cx*)) "#, - name, name, name - )); + name, name, name + )); + } } } let mut template_function_arity_1 = |name: &str| { - builtin_static_command_module.push_str(&format!( - r#" + if generate_sources { + builtin_static_command_module.push_str(&format!( + r#" (provide {}) (define ({} arg) (helix.static.{} *helix.cx* arg)) "#, - name, name, name - )); + name, name, name + )); + } }; // Adhoc static commands that probably needs evaluating @@ -209,14 +226,16 @@ fn load_static_commands(engine: &mut Engine) { template_function_arity_1("cx->current-file"); let mut template_function_arity_0 = |name: &str| { - builtin_static_command_module.push_str(&format!( - r#" + if generate_sources { + builtin_static_command_module.push_str(&format!( + r#" (provide {}) (define ({}) (helix.static.{} *helix.cx*)) "#, - name, name, name - )); + name, name, name + )); + } }; // Arity 0 @@ -242,13 +261,15 @@ fn load_static_commands(engine: &mut Engine) { template_function_arity_0("move-window-far-right"); let mut template_function_no_context = |name: &str| { - builtin_static_command_module.push_str(&format!( - r#" + if generate_sources { + builtin_static_command_module.push_str(&format!( + r#" (provide {}) (define {} helix.static.{}) "#, - name, name, name - )) + name, name, name + )) + } }; module.register_fn("get-helix-scm-path", get_helix_scm_path); @@ -257,48 +278,29 @@ fn load_static_commands(engine: &mut Engine) { template_function_no_context("get-helix-scm-path"); template_function_no_context("get-init-scm-path"); - let mut target_directory = helix_runtime_search_path(); + if generate_sources { + let mut target_directory = helix_runtime_search_path(); - if !target_directory.exists() { - std::fs::create_dir(&target_directory).unwrap(); - } + if !target_directory.exists() { + std::fs::create_dir(&target_directory).unwrap(); + } - target_directory.push("static.scm"); + target_directory.push("static.scm"); - std::fs::write(target_directory, builtin_static_command_module).unwrap(); + std::fs::write(target_directory, builtin_static_command_module).unwrap(); + } engine.register_module(module); } -fn load_typed_commands(engine: &mut Engine) { +fn load_typed_commands(engine: &mut Engine, generate_sources: bool) { 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]| { - let mut cx = compositor::Context { - editor: cx.editor, - scroll: None, - jobs: cx.jobs, - }; - - set_options(&mut cx, args, PromptEvent::Validate) - }; - - module.register_fn("set-options", func); - let name = "set-options"; - - builtin_typable_command_module.push_str(&format!( - r#" -(provide {}) - -(define ({} . args) - (helix.{} *helix.cx* args)) -"#, - name, name, name - )); - } + let mut builtin_typable_command_module = if generate_sources { + "(require-builtin helix/core/typable as helix.)".to_string() + } else { + "".to_string() + }; // Register everything in the typable command list. Now these are all available for command in TYPABLE_COMMAND_LIST { @@ -314,10 +316,11 @@ fn load_typed_commands(engine: &mut Engine) { 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#" + if generate_sources { + // 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 @@ -325,30 +328,33 @@ fn load_typed_commands(engine: &mut Engine) { (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 - )); + 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 = helix_runtime_search_path(); - if !target_directory.exists() { - std::fs::create_dir(&target_directory).unwrap(); - } + if generate_sources { + let mut target_directory = helix_runtime_search_path(); + if !target_directory.exists() { + std::fs::create_dir(&target_directory).unwrap(); + } - target_directory.push("commands.scm"); + target_directory.push("commands.scm"); - std::fs::write(target_directory, builtin_typable_command_module).unwrap(); + std::fs::write(target_directory, builtin_typable_command_module).unwrap(); + } engine.register_module(module); } @@ -389,7 +395,27 @@ fn fp_max_depth(config: &mut FilePickerConfig, option: Option) { config.max_depth = option; } -fn load_configuration_api(engine: &mut Engine) { +fn sw_enable(config: &mut SoftWrap, option: Option) { + config.enable = option; +} + +fn sw_max_wrap(config: &mut SoftWrap, option: Option) { + config.max_wrap = option; +} + +fn sw_max_indent_retain(config: &mut SoftWrap, option: Option) { + config.max_indent_retain = option; +} + +fn sw_wrap_indicator(config: &mut SoftWrap, option: Option) { + config.wrap_indicator = option; +} + +fn wrap_at_text_width(config: &mut SoftWrap, option: Option) { + config.wrap_at_text_width = option; +} + +fn load_configuration_api(engine: &mut Engine, generate_sources: bool) { let mut module = BuiltInModule::new("helix/core/configuration"); module.register_fn("update-configuration!", |ctx: &mut Context| { @@ -400,56 +426,6 @@ fn load_configuration_api(engine: &mut Engine) { .unwrap(); }); - let mut builtin_configuration_module = - "(require-builtin helix/core/configuration as helix.)".to_string(); - - builtin_configuration_module.push_str(&format!( - r#" -(provide update-configuration!) -(define (update-configuration!) - (helix.update-configuration! *helix.cx*)) -"#, - )); - - let mut template_file_picker_function = |name: &str| { - builtin_configuration_module.push_str(&format!( - r#" -(provide {}) -(define ({} arg) - (lambda (picker) - (helix.{} picker arg) - picker)) -"#, - name, name, name - )); - }; - - let file_picker_functions = &[ - "fp-hidden", - "fp-follow-symlinks", - "fp-deduplicate-links", - "fp-parents", - "fp-ignore", - "fp-git-ignore", - "fp-git-global", - "fp-git-exclude", - "fp-max-depth", - ]; - - for name in file_picker_functions { - template_file_picker_function(name); - } - - builtin_configuration_module.push_str(&format!( - r#" -(provide file-picker) -(define (file-picker . args) - (helix.register-file-picker - *helix.config* - (foldl (lambda (func config) (func config)) (helix.raw-file-picker) args))) -"#, - )); - module .register_fn("raw-file-picker", || FilePickerConfig::default()) .register_fn("register-file-picker", HelixConfiguration::file_picker) @@ -463,61 +439,14 @@ fn load_configuration_api(engine: &mut Engine) { .register_fn("fp-git-exclude", fp_git_exclude) .register_fn("fp-max-depth", fp_max_depth); - let mut template_function_arity_1 = |name: &str| { - builtin_configuration_module.push_str(&format!( - r#" -(provide {}) -(define ({} arg) - (helix.{} *helix.config* arg)) -"#, - name, name, name - )); - }; - - let functions = &[ - "scrolloff", - "scroll_lines", - "mouse", - "shell", - "line-number", - "cursorline", - "cursorcolumn", - "middle-click-paste", - "auto-pairs", - "auto-completion", - "auto-format", - "auto-save", - "text-width", - "idle-timeout", - "completion-timeout", - "preview-completion-insert", - "completion-trigger-len", - "completion-replace", - "auto-info", - "cursor-shape", - "true-color", - "insert-final-newline", - "color-modes", - "gutters", - // "file-picker", - "statusline", - "undercurl", - "search", - "lsp", - "terminal", - "rulers", - "whitespace", - "bufferline", - "indent-guides", - // "soft-wrap", - "workspace-lsp-roots", - "default-line-ending", - "smart-tab", - ]; - - for func in functions { - template_function_arity_1(func); - } + module + .register_fn("raw-soft-wrap", || SoftWrap::default()) + .register_fn("register-soft-wrap", HelixConfiguration::soft_wrap) + .register_fn("sw-enable", sw_enable) + .register_fn("sw-max-wrap", sw_max_wrap) + .register_fn("sw-max-indent-retain", sw_max_indent_retain) + .register_fn("sw-wrap-indicator", sw_wrap_indicator) + .register_fn("sw-wrap-at-text-width", wrap_at_text_width); module .register_fn("scrolloff", HelixConfiguration::scrolloff) @@ -570,7 +499,7 @@ fn load_configuration_api(engine: &mut Engine) { .register_fn("whitespace", HelixConfiguration::whitespace) .register_fn("bufferline", HelixConfiguration::bufferline) .register_fn("indent-guides", HelixConfiguration::indent_guides) - // .register_fn("soft-wrap", HelixConfiguration::soft_wrap) + .register_fn("soft-wrap", HelixConfiguration::soft_wrap) .register_fn( "workspace-lsp-roots", HelixConfiguration::workspace_lsp_roots, @@ -581,35 +510,179 @@ fn load_configuration_api(engine: &mut Engine) { ) .register_fn("smart-tab", HelixConfiguration::smart_tab); - let mut target_directory = helix_runtime_search_path(); + // Keybinding stuff + module + .register_fn("keybindings", HelixConfiguration::keybindings) + .register_fn("get-keybindings", HelixConfiguration::get_keybindings); - if !target_directory.exists() { - std::fs::create_dir(&target_directory).unwrap(); - } + if generate_sources { + let mut builtin_configuration_module = + "(require-builtin helix/core/configuration as helix.)".to_string(); - target_directory.push("configuration.scm"); + builtin_configuration_module.push_str(&format!( + r#" +(provide update-configuration!) +(define (update-configuration!) + (helix.update-configuration! *helix.cx*)) +"#, + )); - std::fs::write(target_directory, builtin_configuration_module).unwrap(); + // Register the get keybindings function + builtin_configuration_module.push_str(&format!( + r#" +(provide get-keybindings) +(define (get-keybindings) + (helix.get-keybindings *helix.cx*)) +"#, + )); - engine.register_module(module); -} + let mut template_soft_wrap = |name: &str| { + builtin_configuration_module.push_str(&format!( + r#" +(provide {}) +(define ({} arg) + (lambda (picker) + (helix.{} picker arg) + picker)) +"#, + name, name, name + )); + }; -fn load_editor_api(engine: &mut Engine) { - let mut module = BuiltInModule::new("helix/core/editor"); + let soft_wrap_functions = &[ + "sw-enable", + "sw-max-wrap", + "sw-max-indent-retain", + "sw-wrap-indicator", + "sw-wrap-at-text-width", + ]; - let mut builtin_editor_command_module = - "(require-builtin helix/core/editor as helix.)".to_string(); + for name in soft_wrap_functions { + template_soft_wrap(name); + } - let mut template_function_arity_0 = |name: &str| { - builtin_editor_command_module.push_str(&format!( - r#" + let mut template_file_picker_function = |name: &str| { + builtin_configuration_module.push_str(&format!( + r#" (provide {}) -(define ({}) - (helix.{} *helix.cx*)) +(define ({} arg) + (lambda (picker) + (helix.{} picker arg) + picker)) +"#, + name, name, name + )); + }; + + let file_picker_functions = &[ + "fp-hidden", + "fp-follow-symlinks", + "fp-deduplicate-links", + "fp-parents", + "fp-ignore", + "fp-git-ignore", + "fp-git-global", + "fp-git-exclude", + "fp-max-depth", + ]; + + for name in file_picker_functions { + template_file_picker_function(name); + } + + builtin_configuration_module.push_str(&format!( + r#" +(provide file-picker) +(define (file-picker . args) + (helix.register-file-picker + *helix.config* + (foldl (lambda (func config) (func config)) (helix.raw-file-picker) args))) "#, - name, name, name )); - }; + + builtin_configuration_module.push_str(&format!( + r#" +(provide soft-wrap) +(define (soft-wrap . args) + (helix.register-soft-wrap + *helix.config* + (foldl (lambda (func config) (func config)) (helix.raw-soft-wrap) args))) +"#, + )); + + let mut template_function_arity_1 = |name: &str| { + builtin_configuration_module.push_str(&format!( + r#" +(provide {}) +(define ({} arg) + (helix.{} *helix.config* arg)) +"#, + name, name, name + )); + }; + + let functions = &[ + "scrolloff", + "scroll_lines", + "mouse", + "shell", + "line-number", + "cursorline", + "cursorcolumn", + "middle-click-paste", + "auto-pairs", + "auto-completion", + "auto-format", + "auto-save", + "text-width", + "idle-timeout", + "completion-timeout", + "preview-completion-insert", + "completion-trigger-len", + "completion-replace", + "auto-info", + "cursor-shape", + "true-color", + "insert-final-newline", + "color-modes", + "gutters", + // "file-picker", + "statusline", + "undercurl", + "search", + "lsp", + "terminal", + "rulers", + "whitespace", + "bufferline", + "indent-guides", + // "soft-wrap", + "workspace-lsp-roots", + "default-line-ending", + "smart-tab", + "keybindings", + ]; + + for func in functions { + template_function_arity_1(func); + } + + let mut target_directory = helix_runtime_search_path(); + + if !target_directory.exists() { + std::fs::create_dir(&target_directory).unwrap(); + } + + target_directory.push("configuration.scm"); + + std::fs::write(target_directory, builtin_configuration_module).unwrap(); + } + + engine.register_module(module); +} + +fn load_editor_api(engine: &mut Engine, generate_sources: bool) { + let mut module = BuiltInModule::new("helix/core/editor"); // Arity 0 module.register_fn("editor-focus", cx_current_focus); @@ -618,23 +691,6 @@ fn load_editor_api(engine: &mut Engine) { 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); @@ -646,18 +702,6 @@ fn load_editor_api(engine: &mut Engine) { 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"); - - // Doesn't use the context - // module.register_fn("doc-id->usize", document_id_to_usize); - // Arity 1 RegisterFn::< _, @@ -671,34 +715,57 @@ fn load_editor_api(engine: &mut Engine) { Document, >::register_fn(&mut module, "editor->get-document", cx_get_document); // I do not like this - // module.register_fn("Document-path", document_path); + if generate_sources { + 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 + )); + }; - // module.register_fn("helix.context?", is_context); - // module.register_type::("DocumentId?"); + 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"); - // TODO: - // Position related functions. These probably should be defined alongside the actual impl for Custom in the core crate - // 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 - // }); + 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 + )); + }; - // module.register_fn("cx->themes", get_themes); + 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"); - // Not the best usage here, duplicating it in a bunch of spots for now - // let mut target_directory = PathBuf::from(std::env::var("HELIX_RUNTIME").unwrap()); - // target_directory.push("cogs"); - // target_directory.push("helix"); - let mut target_directory = helix_runtime_search_path(); + let mut target_directory = helix_runtime_search_path(); - if !target_directory.exists() { - std::fs::create_dir(&target_directory).unwrap(); - } + if !target_directory.exists() { + std::fs::create_dir(&target_directory).unwrap(); + } - target_directory.push("editor.scm"); + target_directory.push("editor.scm"); - std::fs::write(target_directory, builtin_editor_command_module).unwrap(); + std::fs::write(target_directory, builtin_editor_command_module).unwrap(); + } engine.register_module(module); } @@ -853,6 +920,12 @@ impl super::PluginSystem for SteelScriptingEngine { .map(|x| x.clone().into()) .collect::>() } + + fn generate_sources(&self) { + // Generate sources directly with a fresh engine + let mut engine = Engine::new(); + configure_builtin_sources(&mut engine, true); + } } impl SteelScriptingEngine { @@ -1089,6 +1162,17 @@ impl HelixConfiguration { self.configuration.store(Arc::new(config)); } + // Overlay new keybindings + fn keybindings(&self, keybindings: EmbeddedKeyMap) { + let mut app_config = self.load_config(); + merge_keys(&mut app_config.keys, keybindings.0); + self.store_config(app_config); + } + + fn get_keybindings(&self) -> EmbeddedKeyMap { + EmbeddedKeyMap(self.load_config().keys.clone()) + } + fn scrolloff(&self, lines: usize) { let mut app_config = self.load_config(); app_config.editor.scrolloff = lines; @@ -1424,9 +1508,6 @@ fn run_initialization_script(cx: &mut Context, configuration: Arc = -// Lazy::new(|| SharedKeyBindingsEventQueue::new()); - pub static EXPORTED_IDENTIFIERS: Lazy = Lazy::new(|| ExportedIdentifiers::default()); @@ -1648,7 +1729,7 @@ impl SteelEngine { pub fn call_function_by_name( &mut self, function_name: SteelString, - args: List, + args: Vec, ) -> steel::rvals::Result { self.0 .call_function_by_name_with_args(function_name.as_str(), args.into_iter().collect()) @@ -1659,7 +1740,7 @@ impl SteelEngine { pub fn call_function( &mut self, function: SteelVal, - args: List, + args: Vec, ) -> steel::rvals::Result { self.0 .call_function_with_args(function, args.into_iter().collect()) @@ -1716,20 +1797,26 @@ fn load_engine_api(engine: &mut Engine) { .register_fn("helix.controller.id->engine", id_to_engine); } -fn load_misc_api(engine: &mut Engine) { +fn load_misc_api(engine: &mut Engine, generate_sources: bool) { let mut module = BuiltInModule::new("helix/core/misc"); - let mut builtin_misc_module = "(require-builtin helix/core/misc as helix.)".to_string(); + let mut builtin_misc_module = if generate_sources { + "(require-builtin helix/core/misc as helix.)".to_string() + } else { + "".to_string() + }; let mut template_function_arity_0 = |name: &str| { - builtin_misc_module.push_str(&format!( - r#" + if generate_sources { + builtin_misc_module.push_str(&format!( + r#" (provide {}) (define ({}) (helix.{} *helix.cx*)) "#, - name, name, name - )); + name, name, name + )); + } }; // Arity 0 @@ -1738,14 +1825,16 @@ fn load_misc_api(engine: &mut Engine) { template_function_arity_0("hx.cx->pos"); let mut template_function_arity_1 = |name: &str| { - builtin_misc_module.push_str(&format!( - r#" + if generate_sources { + builtin_misc_module.push_str(&format!( + r#" (provide {}) (define ({} arg) (helix.{} *helix.cx* arg)) "#, - name, name, name - )); + name, name, name + )); + } }; // Arity 1 @@ -1758,14 +1847,16 @@ fn load_misc_api(engine: &mut Engine) { template_function_arity_1("enqueue-thread-local-callback"); let mut template_function_arity_2 = |name: &str| { - builtin_misc_module.push_str(&format!( - r#" + if generate_sources { + builtin_misc_module.push_str(&format!( + r#" (provide {}) (define ({} arg1 arg2) (helix.{} *helix.cx* arg1 arg2)) "#, - name, name, name - )); + name, name, name + )); + } }; // Arity 2 @@ -1780,15 +1871,17 @@ fn load_misc_api(engine: &mut Engine) { template_function_arity_2("enqueue-thread-local-callback-with-delay"); template_function_arity_2("helix-await-callback"); - let mut target_directory = helix_runtime_search_path(); + if generate_sources { + let mut target_directory = helix_runtime_search_path(); - if !target_directory.exists() { - std::fs::create_dir(&target_directory).unwrap(); - } + if !target_directory.exists() { + std::fs::create_dir(&target_directory).unwrap(); + } - target_directory.push("misc.scm"); + target_directory.push("misc.scm"); - std::fs::write(target_directory, builtin_misc_module).unwrap(); + std::fs::write(target_directory, builtin_misc_module).unwrap(); + } engine.register_module(module); } @@ -1797,6 +1890,19 @@ fn helix_runtime_search_path() -> PathBuf { helix_loader::config_dir().join("helix") } +pub fn configure_builtin_sources(engine: &mut Engine, generate_sources: bool) { + load_editor_api(engine, generate_sources); + load_configuration_api(engine, generate_sources); + load_typed_commands(engine, generate_sources); + load_static_commands(engine, generate_sources); + if !generate_sources { + // Note: This is going to be completely revamped soon. + load_keymap_api(engine, KeyMapApi::new()); + } + load_rope_api(engine); + load_misc_api(engine, generate_sources); +} + fn configure_engine_impl(mut engine: Engine) -> Engine { log::info!("Loading engine!"); @@ -1805,13 +1911,8 @@ fn configure_engine_impl(mut engine: Engine) -> Engine { engine.register_value("*helix.cx*", SteelVal::Void); engine.register_value("*helix.config*", SteelVal::Void); - load_editor_api(&mut engine); - load_configuration_api(&mut engine); - load_typed_commands(&mut engine); - load_static_commands(&mut engine); - load_keymap_api(&mut engine, KeyMapApi::new()); - load_rope_api(&mut engine); - load_misc_api(&mut engine); + // Don't generate source directories here + configure_builtin_sources(&mut engine, false); // Hooks engine.register_fn("register-hook!", register_hook); @@ -1922,38 +2023,35 @@ fn configure_engine_impl(mut engine: Engine) -> Engine { }, ); - engine.register_fn( - "picker", - |values: steel::List| -> WrappedDynComponent { - let picker = ui::Picker::new( - Vec::new(), - PathBuf::from(""), - move |cx, path: &PathBuf, action| { - if let Err(e) = cx.editor.open(path, action) { - let err = if let Some(err) = e.source() { - format!("{}", err) - } else { - format!("unable to open \"{}\"", path.display()) - }; - cx.editor.set_error(err); - } - }, - ) - .with_preview(|_editor, path| Some((path.clone().into(), None))); + engine.register_fn("picker", |values: Vec| -> WrappedDynComponent { + let picker = ui::Picker::new( + Vec::new(), + PathBuf::from(""), + move |cx, path: &PathBuf, action| { + if let Err(e) = cx.editor.open(path, action) { + let err = if let Some(err) = e.source() { + format!("{}", err) + } else { + format!("unable to open \"{}\"", path.display()) + }; + cx.editor.set_error(err); + } + }, + ) + .with_preview(|_editor, path| Some((path.clone().into(), None))); - let injector = picker.injector(); + let injector = picker.injector(); - for file in values { - if injector.push(PathBuf::from(file)).is_err() { - break; - } + for file in values { + if injector.push(PathBuf::from(file)).is_err() { + break; } + } - WrappedDynComponent { - inner: Some(Box::new(ui::overlay::overlaid(picker))), - } - }, - ); + WrappedDynComponent { + inner: Some(Box::new(ui::overlay::overlaid(picker))), + } + }); engine.register_fn("Component::Text", |contents: String| WrappedDynComponent { inner: Some(Box::new(crate::ui::Text::new(contents))), @@ -2017,7 +2115,7 @@ fn configure_engine_impl(mut engine: Engine) -> Engine { engine } -fn configure_engine() -> std::rc::Rc> { +pub fn configure_engine() -> std::rc::Rc> { let engine = configure_engine_impl(steel::steel_vm::engine::Engine::new()); std::rc::Rc::new(std::cell::RefCell::new(engine)) @@ -2366,53 +2464,6 @@ fn create_directory(path: String) { } } -/// 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], - 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(()) -} - pub fn cx_pos_within_text(cx: &mut Context) -> usize { let (view, doc) = current_ref!(cx.editor); diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs new file mode 100644 index 000000000..470cbf8f1 --- /dev/null +++ b/xtask/src/codegen.rs @@ -0,0 +1,5 @@ +use helix_term::commands::ScriptingEngine; + +pub fn code_gen() { + ScriptingEngine::generate_sources() +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 1421fd1a1..42aba2dca 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,3 +1,4 @@ +mod codegen; mod docgen; mod helpers; mod path; @@ -9,6 +10,7 @@ use std::{env, error::Error}; type DynError = Box; pub mod tasks { + use crate::codegen::code_gen; use crate::docgen::{lang_features, typable_commands, write}; use crate::docgen::{LANG_SUPPORT_MD_OUTPUT, TYPABLE_COMMANDS_MD_OUTPUT}; use crate::querycheck::query_check; @@ -32,6 +34,10 @@ pub mod tasks { query_check() } + pub fn codegen() { + code_gen() + } + pub fn print_help() { println!( " @@ -54,6 +60,7 @@ fn main() -> Result<(), DynError> { "docgen" => tasks::docgen()?, "themelint" => tasks::themelint(env::args().nth(2))?, "query-check" => tasks::querycheck()?, + "code-gen" => tasks::codegen(), invalid => return Err(format!("Invalid task name: {}", invalid).into()), }, };