From 0b5501d17515a6d540ec674b364927f3a78ba4ad Mon Sep 17 00:00:00 2001 From: mattwparas Date: Mon, 30 Oct 2023 19:44:30 -0700 Subject: [PATCH] clean up --- Cargo.lock | 8 +- Cargo.toml | 4 +- helix-term/src/application.rs | 38 +- helix-term/src/commands/engine.rs | 70 +- helix-term/src/commands/engine/components.rs | 178 +--- helix-term/src/commands/engine/scheme.rs | 869 +++++++++++-------- helix-term/src/commands/typed.rs | 55 +- helix-term/src/handlers.rs | 2 +- helix-term/src/handlers/completion.rs | 17 +- helix-term/src/job.rs | 7 - helix-term/src/ui/completion.rs | 26 +- helix-term/src/ui/mod.rs | 5 + helix-view/src/editor.rs | 1 + 13 files changed, 660 insertions(+), 620 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ac7bd17f..f996c27de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1720,9 +1720,9 @@ dependencies = [ [[package]] name = "im-lists" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f93ebe9d5265409edc0b5c2ebd96bf7dcd4125c1626bff0ece34b9300e490a" +checksum = "dbe1ea6399f751563e6f5d88bff90a5c7418f8e7abbdd34708412be793a73949" [[package]] name = "im-rc" @@ -2593,6 +2593,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "steel-core" version = "0.5.0" +source = "git+https://github.com/mattwparas/steel.git#11c124dbc8ee890429215bd5963e9a56cac7b923" dependencies = [ "abi_stable", "anyhow", @@ -2629,6 +2630,7 @@ dependencies = [ [[package]] name = "steel-derive" version = "0.4.0" +source = "git+https://github.com/mattwparas/steel.git#11c124dbc8ee890429215bd5963e9a56cac7b923" dependencies = [ "proc-macro2", "quote", @@ -2638,6 +2640,7 @@ dependencies = [ [[package]] name = "steel-gen" version = "0.2.0" +source = "git+https://github.com/mattwparas/steel.git#11c124dbc8ee890429215bd5963e9a56cac7b923" dependencies = [ "codegen", "serde", @@ -2647,6 +2650,7 @@ dependencies = [ [[package]] name = "steel-parser" version = "0.4.0" +source = "git+https://github.com/mattwparas/steel.git#11c124dbc8ee890429215bd5963e9a56cac7b923" dependencies = [ "logos", "num-bigint", diff --git a/Cargo.toml b/Cargo.toml index 6dd59bf4f..24cbffcc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,9 @@ default-members = [ ] [workspace.dependencies] -steel-core = { path = "../../steel/crates/steel-core", version = "0.5.0", features = ["modules", "anyhow", "dylibs", "colors"] } +# 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"] } tree-sitter = { version = "0.20", git = "https://github.com/tree-sitter/tree-sitter", rev = "ab09ae20d640711174b8da8a654f6b3dec93da1a" } nucleo = "0.2.0" diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 4ba5c2596..7c1f81cbb 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -381,6 +381,18 @@ impl Application { pub fn handle_config_events(&mut self, config_event: ConfigEvent) { match config_event { ConfigEvent::Refresh => self.refresh_config(), + ConfigEvent::UpdateLanguageConfiguration => match self.refresh_language_config() { + Ok(_) => { + // If we don't stash the theme here, the syntax highlighting is broken + let current_theme = std::mem::take(&mut self.editor.theme); + self.editor.set_theme(current_theme); + + self.editor.set_status("Language config refreshed"); + } + Err(err) => { + self.editor.set_error(err.to_string()); + } + }, // Since only the Application can make changes to Editor's config, // the Editor must send up a new copy of a modified config so that @@ -409,7 +421,8 @@ impl Application { /// refresh language config after config change fn refresh_language_config(&mut self) -> Result<(), Error> { - let syntax_config = helix_core::config::user_syntax_loader() + let syntax_config = crate::commands::engine::ScriptingEngine::load_language_configuration() + .unwrap_or_else(|| helix_core::config::user_syntax_loader()) .map_err(|err| anyhow::anyhow!("Failed to load language config: {}", err))?; self.syn_loader = std::sync::Arc::new(syntax::Loader::new(syntax_config)); @@ -427,15 +440,19 @@ impl Application { let theme = config .theme .as_ref() - .and_then(|theme| { - self.theme_loader - .load(theme) - .map_err(|e| { - log::warn!("failed to load theme `{}` - {}", theme, e); - e - }) - .ok() - .filter(|theme| (true_color || theme.is_16_color())) + .and_then(|theme| crate::commands::engine::ScriptingEngine::load_theme(theme)) + .or_else(|| { + // Check the name again + config.theme.as_ref().and_then(|theme| { + self.theme_loader + .load(theme) + .map_err(|e| { + log::warn!("failed to load theme `{}` - {}", theme, e); + e + }) + .ok() + .filter(|theme| (true_color || theme.is_16_color())) + }) }) .unwrap_or_else(|| self.theme_loader.default_theme(true_color)); @@ -443,7 +460,6 @@ impl Application { Ok(()) } - // TODO: @Matt - consider querying the engine for keybindings fn refresh_config(&mut self) { let mut refresh_config = || -> Result<(), Error> { let default_config = Config::load_default() diff --git a/helix-term/src/commands/engine.rs b/helix-term/src/commands/engine.rs index 7e7230231..7df0f8bab 100644 --- a/helix-term/src/commands/engine.rs +++ b/helix-term/src/commands/engine.rs @@ -1,10 +1,11 @@ -use helix_view::{document::Mode, input::KeyEvent}; +use helix_core::syntax::Configuration; +use helix_view::{document::Mode, input::KeyEvent, Theme}; -use std::{borrow::Cow, collections::HashMap}; +use std::borrow::Cow; use crate::{ compositor, - keymap::{KeyTrie, KeymapResult}, + keymap::KeymapResult, ui::{self, PromptEvent}, }; @@ -41,6 +42,7 @@ pub struct NoEngine; // This will be the boundary layer between the editor and the engine. pub struct ScriptingEngine; +// Macro to automatically dispatch to hopefully get some inlining macro_rules! manual_dispatch { ($kind:expr, $raw:tt ($($args:expr),* $(,)?) ) => { match $kind { @@ -131,6 +133,42 @@ impl ScriptingEngine { .flat_map(|kind| manual_dispatch!(kind, available_commands())) .collect() } + + pub fn load_theme(name: &str) -> Option { + for kind in PLUGIN_PRECEDENCE { + let theme = manual_dispatch!(kind, load_theme(name)); + + if theme.is_some() { + return theme; + } + } + + None + } + + pub fn themes() -> Option> { + for kind in PLUGIN_PRECEDENCE { + let themes = manual_dispatch!(kind, themes()); + + if themes.is_some() { + return themes; + } + } + + None + } + + pub fn load_language_configuration() -> Option> { + for kind in PLUGIN_PRECEDENCE { + let config = manual_dispatch!(kind, load_language_configuration()); + + if config.is_some() { + return config; + } + } + + None + } } impl PluginSystem for NoEngine { @@ -155,6 +193,7 @@ pub trait PluginSystem { /// Allow the engine to directly handle a keymap event. This is some of the tightest integration /// with the engine, directly intercepting any keymap events. By default, this just delegates to the /// editors default keybindings. + #[inline(always)] fn handle_keymap_event( &self, _editor: &mut ui::EditorView, @@ -167,6 +206,7 @@ pub trait PluginSystem { /// This attempts to call a function in the engine with the name `name` using the args `args`. The context /// is available here. Returns a bool indicating whether the function exists or not. + #[inline(always)] fn call_function_if_global_exists( &self, _cx: &mut Context, @@ -179,6 +219,7 @@ pub trait PluginSystem { /// This is explicitly for calling a function via the typed command interface, e.g. `:vsplit`. The context here /// that is available is more limited than the context available in `call_function_if_global_exists`. This also /// gives the ability to handle in progress commands with `PromptEvent`. + #[inline(always)] fn call_typed_command_if_global_exists<'a>( &self, _cx: &mut compositor::Context, @@ -190,12 +231,35 @@ pub trait PluginSystem { } /// Given an identifier, extract the documentation from the engine. + #[inline(always)] fn get_doc_for_identifier(&self, _ident: &str) -> Option { None } /// Fuzzy match the input against the fuzzy matcher, used for handling completions on typed commands + #[inline(always)] fn available_commands<'a>(&self) -> Vec> { Vec::new() } + + /// Retrieve a theme for a given name + #[inline(always)] + fn load_theme(&self, _name: &str) -> Option { + None + } + + /// Retrieve the list of themes that exist within the runtime + #[inline(always)] + fn themes(&self) -> Option> { + None + } + + /// Fetch the language configuration as monitored by the plugin system. + /// + /// For now - this maintains backwards compatibility with the existing toml configuration, + /// and as such the toml error is exposed here. + #[inline(always)] + fn load_language_configuration(&self) -> Option> { + None + } } diff --git a/helix-term/src/commands/engine/components.rs b/helix-term/src/commands/engine/components.rs index 1dca2a850..48a69a6f4 100644 --- a/helix-term/src/commands/engine/components.rs +++ b/helix-term/src/commands/engine/components.rs @@ -13,185 +13,10 @@ use crate::{ ui::{Popup, Prompt, PromptEvent}, }; +// TODO: Move the main configuration function to use this instead pub fn helix_component_module() -> BuiltInModule { let mut module = BuiltInModule::new("helix/components".to_string()); - module.register_fn("new-component!", SteelDynamicComponent::new_dyn); - - module.register_fn("SteelDynamicComponent?", |object: SteelVal| { - if let SteelVal::Custom(v) = object { - if let Some(wrapped) = v.borrow().as_any_ref().downcast_ref::() { - return wrapped.inner.as_any().is::(); - } else { - false - } - } else { - false - } - }); - - module.register_fn( - "SteelDynamicComponent-state", - SteelDynamicComponent::get_state, - ); - module.register_fn( - "SteelDynamicComponent-render", - SteelDynamicComponent::get_render, - ); - module.register_fn( - "SteelDynamicComponent-handle-event", - SteelDynamicComponent::get_handle_event, - ); - module.register_fn( - "SteelDynamicComponent-should-update", - SteelDynamicComponent::should_update, - ); - module.register_fn( - "SteelDynamicComponent-cursor", - SteelDynamicComponent::cursor, - ); - module.register_fn( - "SteelDynamicComponent-required-size", - SteelDynamicComponent::get_required_size, - ); - - // engine.register_fn("WrappedComponent", WrappedDynComponent::new) - - module.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)), - )), - } - }, - ); - - // prompt: Cow<'static, str>, - // history_register: Option, - // completion_fn: impl FnMut(&Editor, &str) -> Vec + 'static, - // callback_fn: impl FnMut(&mut Context, &str, PromptEvent) + 'static, - module.register_fn( - "Prompt::new", - |prompt: String, callback_fn: SteelVal| -> WrappedDynComponent { - let prompt = Prompt::new( - prompt.into(), - None, - |_, _| Vec::new(), - move |cx, input, prompt_event| { - if prompt_event != PromptEvent::Validate { - return; - } - - let mut ctx = Context { - register: None, - count: None, - editor: cx.editor, - callback: Vec::new(), - on_next_key_callback: None, - jobs: cx.jobs, - }; - - let cloned_func = callback_fn.clone(); - // let thunk = move |engine: &mut Engine, cx, input| { - // engine.call_function_with_args( - // cloned_func, - // vec![cx, input], - - // ) - - // }; - - ENGINE - .with(|x| { - x.borrow_mut() - .with_mut_reference::(&mut ctx) - .consume(move |engine, mut args| { - // Add the string as an argument to the callback - args.push(input.into_steelval().unwrap()); - - engine.call_function_with_args(cloned_func.clone(), args) - }) - }) - .unwrap(); - }, - ); - - WrappedDynComponent { - inner: Some(Box::new(prompt)), - } - }, - ); - - module.register_fn("Picker::new", |values: Vec| todo!()); - - // engine.register_fn( - // "Picker::new", - // |contents: &mut Wrapped - // ) - - module.register_fn("Component::Text", |contents: String| WrappedDynComponent { - inner: Some(Box::new(crate::ui::Text::new(contents))), - }); - - // Separate this out into its own component module - This just lets us call the underlying - // component, not sure if we can go from trait object -> trait object easily but we'll see! - module.register_fn( - "Component::render", - |t: &mut WrappedDynComponent, - area: helix_view::graphics::Rect, - frame: &mut tui::buffer::Buffer, - ctx: &mut Context| { - t.inner.as_mut().unwrap().render( - area, - frame, - &mut compositor::Context { - jobs: ctx.jobs, - editor: ctx.editor, - scroll: None, - }, - ) - }, - ); - - module.register_fn( - "Component::handle-event", - |s: &mut WrappedDynComponent, event: &helix_view::input::Event, ctx: &mut Context| { - s.inner.as_mut().unwrap().handle_event( - event, - &mut compositor::Context { - jobs: ctx.jobs, - editor: ctx.editor, - scroll: None, - }, - ) - }, - ); - - module.register_fn("Component::should-update", |s: &mut WrappedDynComponent| { - s.inner.as_mut().unwrap().should_update() - }); - - module.register_fn( - "Component::cursor", - |s: &WrappedDynComponent, area: helix_view::graphics::Rect, ctx: &Editor| { - s.inner.as_ref().unwrap().cursor(area, ctx) - }, - ); - - module.register_fn( - "Component::required-size", - |s: &mut WrappedDynComponent, viewport: (u16, u16)| { - s.inner.as_mut().unwrap().required_size(viewport) - }, - ); - module } @@ -242,6 +67,7 @@ impl SteelDynamicComponent { ) -> WrappedDynComponent { let s = Self::new(name, state, render, h); + // TODO: Add guards here for the WrappedDynComponent { inner: Some(Box::new(s)), } diff --git a/helix-term/src/commands/engine/scheme.rs b/helix-term/src/commands/engine/scheme.rs index 0d4038589..6d4f7d71f 100644 --- a/helix-term/src/commands/engine/scheme.rs +++ b/helix-term/src/commands/engine/scheme.rs @@ -4,33 +4,30 @@ use helix_core::{ path::expand_tilde, regex::Regex, shellwords::Shellwords, + syntax::Configuration, Range, Selection, Tendril, }; -use helix_loader::{current_working_dir, set_current_working_dir}; +use helix_event::register_hook; +use helix_loader::merge_toml_values; +use helix_lsp::lsp::CompletionItem; use helix_view::{ document::Mode, editor::{Action, ConfigEvent}, extension::document_id_to_usize, input::KeyEvent, - Document, DocumentId, Editor, + Document, DocumentId, Editor, Theme, }; 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, SteelString}, + rvals::{as_underlying_type, AsRefMutSteelValFromRef, FromSteelVal, IntoSteelVal}, steel_vm::{engine::Engine, register_fn::RegisterFn}, - SteelErr, SteelVal, + steelerr, SteelErr, SteelVal, }; -use std::{ - borrow::Cow, - collections::{HashMap, VecDeque}, - ops::Deref, - path::PathBuf, - sync::Mutex, -}; +use std::{borrow::Cow, cell::RefCell, collections::HashMap, ops::Deref, path::PathBuf, rc::Rc}; use std::{ collections::HashSet, sync::{Arc, RwLock}, @@ -42,9 +39,10 @@ use crate::{ commands::insert, compositor::{self, Component, Compositor}, config::Config, + events::{OnModeSwitch, PostCommand, PostInsertChar}, job::{self, Callback}, keymap::{self, merge_keys, KeyTrie, KeymapResult}, - ui::{self, menu::Item, Popup, Prompt, PromptEvent}, + ui::{self, Popup, Prompt, PromptEvent}, }; use components::SteelDynamicComponent; @@ -104,10 +102,131 @@ thread_local! { SteelVal::boxed(SteelVal::empty_hashmap()); pub static GLOBAL_KEYBINDING_MAP: SteelVal = get_keymap().into_steelval().unwrap(); + + static THEME_MAP: ThemeContainer = ThemeContainer::new(); + + static LANGUAGE_CONFIGURATIONS: LanguageConfigurationContainer = LanguageConfigurationContainer::new(); +} + +// Any configurations that we'd like to overlay from the toml +struct LanguageConfigurationContainer { + configuration: Rc>>, +} + +impl LanguageConfigurationContainer { + fn new() -> Self { + Self { + configuration: Rc::new(RefCell::new(None)), + } + } +} + +impl Custom for LanguageConfigurationContainer {} + +impl LanguageConfigurationContainer { + fn add_configuration(&self, config_as_string: String) -> Result<(), String> { + let left = self + .configuration + .replace(Some(toml::Value::Boolean(false))); + + 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 + .replace(Some(merge_toml_values(left, right, 3))); + + Ok(()) + } + Err(e) => Err(e), + } + } else { + let right = serde_json::from_str(&config_as_string).map_err(|err| err.to_string())?; + + self.configuration.replace(Some(right)); + + Ok(()) + } + } + + fn as_language_configuration(&self) -> Option> { + if let Some(right) = self.configuration.borrow().clone() { + let config = helix_loader::config::user_lang_config(); + + let res = config + .map(|left| merge_toml_values(left, right, 3)) + .and_then(|x| x.try_into()); + + Some(res) + // ) + } else { + None + } + } + + fn get_language_configuration() -> Option> { + LANGUAGE_CONFIGURATIONS.with(|x| x.as_language_configuration()) + } +} + +struct ThemeContainer { + themes: Rc>>, +} + +impl Custom for ThemeContainer {} + +impl ThemeContainer { + fn new() -> Self { + Self { + themes: Rc::new(RefCell::new(HashMap::new())), + } + } + + fn get(name: &str) -> Option { + THEME_MAP.with(|x| x.themes.borrow().get(name).cloned()) + } + + fn names() -> Vec { + THEME_MAP.with(|x| x.themes.borrow().keys().cloned().collect()) + } + + fn register(name: String, theme: Theme) { + THEME_MAP.with(|x| x.themes.borrow_mut().insert(name, theme)); + } +} + +fn load_language_configuration_api(engine: &mut Engine) { + let mut module = BuiltInModule::new("helix/core/languages".to_string()); + + module.register_fn( + "register-language-configuration!", + |language_configuration: String| -> Result<(), String> { + LANGUAGE_CONFIGURATIONS.with(|x| x.add_configuration(language_configuration)) + }, + ); + + module.register_fn("flush-configuration", refresh_language_configuration); + + engine.register_module(module); } fn load_theme_api(engine: &mut Engine) { - todo!() + let mut module = BuiltInModule::new("helix/core/themes"); + + module.register_fn( + "register-theme!", + |name: String, theme_as_json_string: String| -> Result<(), String> { + Ok(ThemeContainer::register( + name, + serde_json::from_str(&theme_as_json_string).map_err(|err| err.to_string())?, + )) + }, + ); + + engine.register_module(module); } fn load_keymap_api(engine: &mut Engine, api: KeyMapApi) { @@ -122,6 +241,11 @@ fn load_keymap_api(engine: &mut Engine, api: KeyMapApi) { module.register_fn("helix-deep-copy-keymap", api.deep_copy_keymap); + // Alternatively, could store these values in a steel module, like so: + // let keymap_core_map = helix_loader::runtime_file(&PathBuf::from("steel").join("keymap.scm")); + // let require_module = format!("(require {})", keymap_core_map.to_str().unwrap()); + // engine.run(&require_module).unwrap(); + // This should be associated with a corresponding scheme module to wrap this up module.register_value( "*buffer-or-extension-keybindings*", @@ -165,12 +289,58 @@ fn load_static_commands(engine: &mut Engine) { } } + // Adhoc static commands that probably needs evaluating + module.register_fn("insert_char", insert_char); + module.register_fn("insert_string", insert_string); + module.register_fn("current_selection", get_selection); + module.register_fn("current-highlighted-text!", get_highlighted_text); + module.register_fn("get-current-line-number", current_line_number); + module.register_fn("current-selection-object", current_selection); + module.register_fn("set-current-selection-object!", set_selection); + 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); + module.register_fn("search-in-directory", search_in_directory); + module.register_fn("regex-selection", regex_selection); + module.register_fn("replace-selection-with", replace_selection); + module.register_fn("show-completion-prompt-with", show_completion_prompt); + module.register_fn("move-window-far-left", move_window_to_the_left); + module.register_fn("move-window-far-right", move_window_to_the_right); + + module.register_fn("block-on-shell-command", run_shell_command_text); + + module.register_fn("cx->current-file", current_path); + engine.register_module(module); } fn load_typed_commands(engine: &mut Engine) { let mut module = BuiltInModule::new("helix/core/typable".to_string()); + { + let func = |cx: &mut Context, args: &[Cow], event: PromptEvent| { + let mut cx = compositor::Context { + editor: cx.editor, + scroll: None, + jobs: cx.jobs, + }; + + set_options(&mut cx, args, event) + }; + + module.register_fn("set-options", func); + } + + module.register_value( + "PromptEvent::Validate", + PromptEvent::Validate.into_steelval().unwrap(), + ); + module.register_value( + "PromptEvent::Update", + PromptEvent::Update.into_steelval().unwrap(), + ); + // 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| { @@ -189,16 +359,78 @@ fn load_typed_commands(engine: &mut Engine) { engine.register_module(module); } -fn load_editor_api(engine: &mut Engine, api: EditorApi) { - todo!() +fn load_editor_api(engine: &mut Engine, _api: EditorApi) { + 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); + + module.register_fn("set-scratch-buffer-name!", set_scratch_buffer_name); + + 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) + RegisterFn::< + _, + steel::steel_vm::register_fn::MarkerWrapper8<( + helix_view::Editor, + DocumentId, + Document, + Document, + helix_view::Editor, + )>, + 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?"); + + module.register_fn("editor-cursor", Editor::cursor); + + module.register_fn("cx->cursor", |cx: &mut Context| cx.editor.cursor()); + + // TODO: + // Position related functions. These probably should be defined alongside the actual impl for Custom in the core crate + 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("cx->themes", get_themes); + + engine.register_module(module); } fn load_document_api(engine: &mut Engine, api: DocumentApi) { - todo!() + todo!("Decide what should go in the document API!") } fn load_component_api(engine: &mut Engine, api: ComponentApi) { - todo!() + todo!("Decide what should go in the component API") } pub struct SteelScriptingEngine; @@ -243,27 +475,22 @@ impl super::PluginSystem for SteelScriptingEngine { args: &[Cow], ) -> bool { if ENGINE.with(|x| x.borrow().global_exists(name)) { - let args = steel::List::from( - args.iter() - .map(|x| x.clone().into_steelval().unwrap()) - .collect::>(), - ); + let mut args = args + .iter() + .map(|x| x.clone().into_steelval().unwrap()) + .collect::>(); if let Err(e) = ENGINE.with(|x| { let mut guard = x.borrow_mut(); { - guard.register_value("_helix_args", steel::rvals::SteelVal::ListV(args)); - - let res = guard.run_with_reference::( - cx, - "*context*", - &format!("(apply {} (cons *context* _helix_args))", name), - ); - - guard.register_value("_helix_args", steel::rvals::SteelVal::Void); + guard.with_mut_reference::(cx).consume( + move |engine, mut arguments| { + arguments.append(&mut args); - res + engine.call_function_by_name_with_args(name, arguments) + }, + ) } }) { cx.editor.set_error(format!("{}", e)); @@ -287,19 +514,13 @@ impl super::PluginSystem for SteelScriptingEngine { // We're finalizing the event - we actually want to call the function if event == PromptEvent::Validate { - // TODO: @Matt - extract this whole API call here to just be inside the engine module - // For what its worth, also explore a more elegant API for calling apply with some arguments, - // this does work, but its a little opaque. if let Err(e) = ENGINE.with(|x| { - let args = steel::List::from( - args[1..] - .iter() - .map(|x| x.clone().into_steelval().unwrap()) - .collect::>(), - ); + let mut args = args[1..] + .iter() + .map(|x| x.clone().into_steelval().unwrap()) + .collect::>(); let mut guard = x.borrow_mut(); - // let mut maybe_callback = None; let res = { let mut cx = Context { @@ -311,17 +532,13 @@ impl super::PluginSystem for SteelScriptingEngine { jobs: cx.jobs, }; - guard.register_value("_helix_args", steel::rvals::SteelVal::ListV(args)); - - let res = guard.run_with_reference::( - &mut cx, - "*context*", - &format!("(apply {} (cons *context* _helix_args))", parts[0]), - ); + guard + .with_mut_reference(&mut cx) + .consume(|engine, mut arguments| { + arguments.append(&mut args); - guard.register_value("_helix_args", steel::rvals::SteelVal::Void); - - res + engine.call_function_by_name_with_args(&parts[0], arguments) + }) }; res @@ -359,6 +576,24 @@ impl super::PluginSystem for SteelScriptingEngine { .map(|x| x.clone().into()) .collect::>() } + + fn load_theme(&self, name: &str) -> Option { + ThemeContainer::get(name) + } + + fn themes(&self) -> Option> { + let names = ThemeContainer::names(); + + if !names.is_empty() { + Some(names) + } else { + None + } + } + + fn load_language_configuration(&self) -> Option> { + LanguageConfigurationContainer::get_language_configuration() + } } impl SteelScriptingEngine { @@ -600,10 +835,7 @@ fn run_initialization_script(cx: &mut Context) { return; } - let helix_path = - "__module-mangler".to_string() + helix_module_path.as_os_str().to_str().unwrap(); - - if let Ok(module) = guard.extract_value(&helix_path) { + if let Ok(module) = guard.get_module(helix_module_path) { if let steel::rvals::SteelVal::HashMapV(m) = module { let exported = m .iter() @@ -677,113 +909,9 @@ fn run_initialization_script(cx: &mut Context) { // pub static KEYBINDING_QUEUE: Lazy = // Lazy::new(|| SharedKeyBindingsEventQueue::new()); -pub static CALLBACK_QUEUE: Lazy = Lazy::new(|| CallbackQueue::new()); - pub static EXPORTED_IDENTIFIERS: Lazy = Lazy::new(|| ExportedIdentifiers::default()); -pub static STATUS_LINE_MESSAGE: Lazy = Lazy::new(|| StatusLineMessage::new()); - -pub struct StatusLineMessage { - message: Arc>>, -} - -impl StatusLineMessage { - pub fn new() -> Self { - Self { - message: std::sync::Arc::new(std::sync::RwLock::new(None)), - } - } - - pub fn set(message: String) { - *STATUS_LINE_MESSAGE.message.write().unwrap() = Some(message); - } - - pub fn get() -> Option { - STATUS_LINE_MESSAGE.message.read().unwrap().clone() - } -} - -// impl Item for SteelVal { -// type Data = (); - -// // TODO: This shouldn't copy the data every time -// fn format(&self, _data: &Self::Data) -> tui::widgets::Row { -// let formatted = self.to_string(); - -// formatted -// .strip_prefix("\"") -// .unwrap_or(&formatted) -// .strip_suffix("\"") -// .unwrap_or(&formatted) -// .to_owned() -// .into() -// } -// } - -pub struct CallbackQueue { - queue: Arc>>, -} - -impl CallbackQueue { - pub fn new() -> Self { - Self { - queue: Arc::new(Mutex::new(VecDeque::new())), - } - } - - pub fn enqueue(value: String) { - CALLBACK_QUEUE.queue.lock().unwrap().push_back(value); - } - - // Dequeue should probably be a R/W lock? - // pub fn dequeue() -> Option { - // CALLBACK_QUEUE.queue.lock().unwrap().pop_front() - // } -} - -/// In order to send events from the engine back to the configuration, we can created a shared -/// queue that the engine and the config push and pull from. Alternatively, we could use a channel -/// directly, however this was easy enough to set up. -// pub struct SharedKeyBindingsEventQueue { -// raw_bindings: Arc>>, -// } - -// impl SharedKeyBindingsEventQueue { -// pub fn new() -> Self { -// Self { -// raw_bindings: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())), -// } -// } - -// pub fn merge(other_as_json: String) { -// KEYBINDING_QUEUE -// .raw_bindings -// .lock() -// .unwrap() -// .push(other_as_json); -// } - -// pub fn get() -> Option> { -// let guard = KEYBINDING_QUEUE.raw_bindings.lock().unwrap(); - -// if let Some(first) = guard.get(0).clone() { -// let mut initial = serde_json::from_str(first).unwrap(); - -// // while let Some(remaining_event) = guard.pop_front() { -// for remaining_event in guard.iter() { -// let bindings = serde_json::from_str(remaining_event).unwrap(); - -// merge_keys(&mut initial, bindings); -// } - -// return Some(initial); -// } - -// None -// } -// } - impl Custom for PromptEvent {} impl<'a> CustomReference for Context<'a> {} @@ -794,10 +922,6 @@ fn get_editor<'a>(cx: &'a mut Context<'a>) -> &'a mut Editor { cx.editor } -fn get_ro_editor<'a>(cx: &'a mut Context<'a>) -> &'a Editor { - &cx.editor -} - fn get_themes(cx: &mut Context) -> Vec { ui::completers::theme(cx.editor, "") .into_iter() @@ -825,13 +949,6 @@ impl FromSteelVal for compositor::EventResult { } } -// impl CustomReference for tui::buffer::Buffer {} - -// TODO: Call the function inside the component, using the global engine. Consider running in its own engine -// but leaving it all in the same one is kinda nice - -// Does this work? - struct WrappedDynComponent { inner: Option>, } @@ -894,27 +1011,135 @@ impl Component for BoxDynComponent { } } -// Make this be prompt event validate? -fn call_function_in_external_engine( - ctx: &mut Context, - name: String, - args: Vec, - engine_type: String, -) { - // Wire up entire plugins separately? - match engine_type.as_str() { - "rhai" => todo!(), - _ => {} +// OnModeSwitch<'a, 'cx> { old_mode: Mode, new_mode: Mode, cx: &'a mut commands::Context<'cx> } +// PostInsertChar<'a, 'cx> { c: char, cx: &'a mut commands::Context<'cx> } +// PostCommand<'a, 'cx> { command: & 'a MappableCommand, cx: &'a mut commands::Context<'cx> } + +#[derive(Debug, Clone, Copy)] +struct OnModeSwitchEvent { + old_mode: Mode, + new_mode: Mode, +} + +impl Custom for OnModeSwitchEvent {} + +// MappableCommands can be values too! +impl Custom for MappableCommand {} + +fn register_hook(event_kind: String, function_name: String) -> steel::UnRecoverableResult { + match event_kind.as_str() { + "on-mode-switch" => { + register_hook!(move |event: &mut OnModeSwitch<'_, '_>| { + if ENGINE.with(|x| x.borrow().global_exists(&function_name)) { + if let Err(e) = ENGINE.with(|x| { + let mut guard = x.borrow_mut(); + + let minimized_event = OnModeSwitchEvent { + old_mode: event.old_mode, + 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) + }) + }) { + event.cx.editor.set_error(format!("{}", e)); + } + } + + Ok(()) + }); + + Ok(SteelVal::Void).into() + } + "post-insert-char" => { + register_hook!(move |event: &mut PostInsertChar<'_, '_>| { + if ENGINE.with(|x| x.borrow().global_exists(&function_name)) { + 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) + }) + }) { + event.cx.editor.set_error(format!("{}", e)); + } + } + + Ok(()) + }); + + Ok(SteelVal::Void).into() + } + "post-command" => { + register_hook!(move |event: &mut PostCommand<'_, '_>| { + if ENGINE.with(|x| x.borrow().global_exists(&function_name)) { + 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) + }) + }) { + event.cx.editor.set_error(format!("{}", e)); + } + } + + Ok(()) + }); + + Ok(SteelVal::Void).into() + } + // Unimplemented! + // "document-did-change" => { + // todo!() + // } + // "selection-did-change" => { + // todo!() + // } + _ => steelerr!(Generic => "Unable to register hook: Unknown event type: {}", event_kind) + .into(), } } +fn load_rope_api(engine: &mut Engine) { + let mut rope_slice_module = rope_module(); + + rope_slice_module.register_fn("document->slice", document_to_text); + + engine.register_module(rope_slice_module); +} + fn configure_engine() -> std::rc::Rc> { let mut engine = steel::steel_vm::engine::Engine::new(); log::info!("Loading engine!"); + // TODO: Load (require-builtin helix/core/editor) in more or less every file that needs it + load_editor_api(&mut engine, EditorApi); + load_typed_commands(&mut engine); + load_static_commands(&mut engine); + load_keymap_api(&mut engine, KeyMapApi::new()); + load_theme_api(&mut engine); + load_rope_api(&mut engine); + load_language_configuration_api(&mut engine); + + // Async context used for referencing the context? + engine.register_value("*helix-async-context*", SteelVal::Void); + // Include this? - engine.register_fn("call-function-in-context", call_function_in_external_engine); + // engine.register_fn("call-function-in-context", call_function_in_external_engine); + + engine.register_fn("register-hook!", register_hook); engine.register_value("*context*", SteelVal::Void); @@ -925,16 +1150,6 @@ fn configure_engine() -> std::rc::Rcpos", cx_pos_within_text); - engine.register_fn("enqueue-callback!", CallbackQueue::enqueue); - - load_keymap_api(&mut engine, KeyMapApi::new()); - - let mut rope_slice_module = rope_module(); - - rope_slice_module.register_fn("document->slice", document_to_text); - - engine.register_module(rope_slice_module); - // Find the workspace engine.register_fn("helix-find-workspace", || { helix_core::find_workspace().0.to_str().unwrap().to_string() @@ -1002,6 +1217,8 @@ fn configure_engine() -> std::rc::Rc WrappedDynComponent { + let callback_fn_guard = callback_fn.as_rooted(); + let prompt = Prompt::new( prompt.into(), None, @@ -1022,7 +1239,7 @@ fn configure_engine() -> std::rc::Rc std::rc::Rc| todo!()); - - // engine.register_fn( - // "Picker::new", - // |contents: &mut Wrapped - // ) - engine.register_fn("Component::Text", |contents: String| WrappedDynComponent { inner: Some(Box::new(crate::ui::Text::new(contents))), }); @@ -1141,175 +1351,7 @@ fn configure_engine() -> std::rc::Rc, - helix_view::Editor, - helix_view::Editor, - Context<'static>, - )>, - helix_view::Editor, - >::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); - engine.register_fn("editor-switch!", switch); - engine.register_fn("editor-set-focus!", Editor::focus); - engine.register_fn("editor-mode", editor_get_mode); - engine.register_fn("editor-set-mode!", editor_set_mode); - engine.register_fn("editor-doc-in-view?", is_document_in_view); - - // engine.register_fn("editor->get-document", get_document); - - // TODO: These are some horrendous type annotations, however... they do work? - // If the type annotations are a bit more ergonomic, we might be able to get away with this - // (i.e. if they're sensible enough) - RegisterFn::< - _, - steel::steel_vm::register_fn::MarkerWrapper8<( - helix_view::Editor, - DocumentId, - Document, - Document, - helix_view::Editor, - )>, - Document, - >::register_fn(&mut engine, "editor->get-document", get_document); - - // Check if the doc exists first - engine.register_fn("editor-doc-exists?", document_exists); - engine.register_fn("Document-path", document_path); - engine.register_fn("Document-focused-at", document_focused_at); - engine.register_fn("editor-all-documents", editor_all_documents); - - engine.register_fn("helix.context?", is_context); - engine.register_type::("DocumentId?"); - - // RegisterFn::< - // _, - // steel::steel_vm::register_fn::MarkerWrapper7<( - // Context<'_>, - // helix_view::Editor, - // helix_view::Editor, - // Context<'static>, - // )>, - // helix_view::Editor, - // >::register_fn(&mut engine, "cx-editor-ro!", get_ro_editor); - - engine.register_fn("editor-cursor", Editor::cursor); - - engine.register_fn("cx->cursor", |cx: &mut Context| cx.editor.cursor()); - - // TODO: - // Position related functions. These probably should be defined alongside the actual impl for Custom in the core crate - engine.register_fn("Position::new", helix_core::Position::new); - engine.register_fn("Position::default", helix_core::Position::default); - engine.register_fn("Position-row", |position: helix_core::Position| { - position.row - }); - - engine.register_fn("cx->themes", get_themes); - engine.register_fn("set-status-line!", StatusLineMessage::set); - - engine.register_module(module); - - let mut module = BuiltInModule::new("helix/core/typable".to_string()); - - { - let func = |cx: &mut Context, args: &[Cow], event: PromptEvent| { - let mut cx = compositor::Context { - editor: cx.editor, - scroll: None, - jobs: cx.jobs, - }; - - set_options(&mut cx, args, event) - }; - - module.register_fn("set-options", func); - } - - module.register_value( - "PromptEvent::Validate", - PromptEvent::Validate.into_steelval().unwrap(), - ); - module.register_value( - "PromptEvent::Update", - PromptEvent::Update.into_steelval().unwrap(), - ); - - // 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); - - let mut module = BuiltInModule::new("helix/core/static".to_string()); - - 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); - } - } - - module.register_fn("insert_char", insert_char); - module.register_fn("insert_string", insert_string); - module.register_fn("current_selection", get_selection); - module.register_fn("current-highlighted-text!", get_highlighted_text); - module.register_fn("get-current-line-number", current_line_number); - - module.register_fn("current-selection-object", current_selection); - module.register_fn("set-current-selection-object!", set_selection); - - 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); - - module.register_fn("search-in-directory", search_in_directory); - module.register_fn("regex-selection", regex_selection); - module.register_fn("replace-selection-with", replace_selection); - - module.register_fn("block-on-shell-command", run_shell_command_text); - - module.register_fn("cx->current-file", current_path); - - engine.register_module(module); + // TODO: Load (require-builtin helix/core/editor) in more or less every file that needs it engine.register_fn("push-component!", push_component); engine.register_fn("enqueue-thread-local-callback", enqueue_command); @@ -1466,12 +1508,12 @@ fn set_scratch_buffer_name(cx: &mut Context, name: String) { } } -fn cx_current_focus(cx: &mut Context) -> helix_view::ViewId { - cx.editor.tree.focus -} +// TODO: Use this over handing around the editor reference, probably +// fn cx_current_focus(cx: &mut Context) -> helix_view::ViewId { +// cx.editor.tree.focus +// } // TODO: Expose the below in a separate module, make things a bit more clear! - fn current_focus(editor: &mut Editor) -> helix_view::ViewId { editor.tree.focus } @@ -1567,6 +1609,8 @@ fn push_component(cx: &mut Context, component: &mut WrappedDynComponent) { } fn enqueue_command(cx: &mut Context, callback_fn: SteelVal) { + let rooted = callback_fn.as_rooted(); + let callback = async move { let call: Box = Box::new( move |editor: &mut Editor, _compositor: &mut Compositor, jobs: &mut job::Jobs| { @@ -1579,7 +1623,7 @@ fn enqueue_command(cx: &mut Context, callback_fn: SteelVal) { jobs, }; - let cloned_func = callback_fn.clone(); + let cloned_func = rooted.value(); if let Err(e) = ENGINE.with(|x| { x.borrow_mut() @@ -1599,6 +1643,8 @@ fn enqueue_command(cx: &mut Context, callback_fn: SteelVal) { // Apply arbitrary delay for update rate... fn enqueue_command_with_delay(cx: &mut Context, delay: SteelVal, callback_fn: SteelVal) { + let rooted = callback_fn.as_rooted(); + let callback = async move { let delay = delay.int_or_else(|| panic!("FIX ME")).unwrap(); @@ -1615,7 +1661,7 @@ fn enqueue_command_with_delay(cx: &mut Context, delay: SteelVal, callback_fn: St jobs, }; - let cloned_func = callback_fn.clone(); + let cloned_func = rooted.value(); if let Err(e) = ENGINE.with(|x| { x.borrow_mut() @@ -1638,6 +1684,9 @@ fn await_value(cx: &mut Context, value: SteelVal, callback_fn: SteelVal) { if !value.is_future() { return; } + + let rooted = callback_fn.as_rooted(); + let callback = async move { let future_value = value.as_future().unwrap().await; @@ -1652,7 +1701,7 @@ fn await_value(cx: &mut Context, value: SteelVal, callback_fn: SteelVal) { jobs, }; - let cloned_func = callback_fn.clone(); + let cloned_func = rooted.value(); match future_value { Ok(inner) => { @@ -1736,6 +1785,15 @@ fn set_options( Ok(()) } +pub fn refresh_language_configuration(cx: &mut Context) -> anyhow::Result<()> { + cx.editor + .config_events + .0 + .send(ConfigEvent::UpdateLanguageConfiguration)?; + + Ok(()) +} + pub fn cx_pos_within_text(cx: &mut Context) -> usize { let (view, doc) = current_ref!(cx.editor); @@ -1748,7 +1806,7 @@ pub fn cx_pos_within_text(cx: &mut Context) -> usize { pos } -pub fn get_helix_cwd(cx: &mut Context) -> Option { +pub fn get_helix_cwd(_cx: &mut Context) -> Option { helix_loader::current_working_dir() .as_os_str() .to_str() @@ -1888,3 +1946,58 @@ fn replace_selection(cx: &mut Context, value: String) { doc.apply(&transaction, view.id); } + +fn show_completion_prompt(cx: &mut Context, items: Vec) { + let (view, doc) = current!(cx.editor); + + let items = items + .into_iter() + .map(|x| crate::ui::CompletionItem { + item: CompletionItem::new_simple(x, "".to_string()), + language_server_id: usize::MAX, + resolved: true, + }) + .collect(); + + let text = doc.text(); + let cursor = doc.selection(view.id).primary().cursor(text.slice(..)); + + let trigger = crate::handlers::completion::Trigger::new( + cursor, + view.id, + doc.id(), + crate::handlers::completion::TriggerKind::Manual, + ); + + let savepoint = doc.savepoint(view); + + let callback = async move { + let call: job::Callback = Callback::EditorCompositor(Box::new( + move |editor: &mut Editor, compositor: &mut Compositor| { + crate::handlers::completion::show_completion( + editor, compositor, items, trigger, savepoint, + ); + }, + )); + Ok(call) + }; + cx.jobs.callback(callback); +} + +fn move_window_to_the_left(cx: &mut Context) { + while cx + .editor + .tree + .swap_split_in_direction(helix_view::tree::Direction::Left) + .is_some() + {} +} + +fn move_window_to_the_right(cx: &mut Context) { + while cx + .editor + .tree + .swap_split_in_direction(helix_view::tree::Direction::Right) + .is_some() + {} +} diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 5310ee0ad..b84063cbb 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -892,7 +892,10 @@ fn theme( // Ensures that a preview theme gets cleaned up if the user backspaces until the prompt is empty. cx.editor.unset_theme_preview(); } else if let Some(theme_name) = args.first() { - if let Ok(theme) = cx.editor.theme_loader.load(theme_name) { + if let Ok(theme) = crate::commands::engine::ScriptingEngine::load_theme(theme_name) + .map(|x| Ok(x)) + .unwrap_or_else(|| cx.editor.theme_loader.load(theme_name)) + { if !(true_color || theme.is_16_color()) { bail!("Unsupported theme: theme requires true color support"); } @@ -902,15 +905,25 @@ fn theme( } PromptEvent::Validate => { if let Some(theme_name) = args.first() { - let theme = cx - .editor - .theme_loader - .load(theme_name) - .map_err(|err| anyhow::anyhow!("Could not load theme: {}", err))?; - if !(true_color || theme.is_16_color()) { - bail!("Unsupported theme: theme requires true color support"); - } - cx.editor.set_theme(theme); + // let theme = cx + // .editor + // .theme_loader + // .load(theme_name) + // .map_err(|err| anyhow::anyhow!("Could not load theme: {}", err))?; + // if !(true_color || theme.is_16_color()) { + // bail!("Unsupported theme: theme requires true color support"); + // } + // cx.editor.set_theme(theme); + + if let Ok(theme) = crate::commands::engine::ScriptingEngine::load_theme(theme_name) + .map(|x| Ok(x)) + .unwrap_or_else(|| cx.editor.theme_loader.load(theme_name)) + { + if !(true_color || theme.is_16_color()) { + bail!("Unsupported theme: theme requires true color support"); + } + cx.editor.set_theme(theme); + }; } else { let name = cx.editor.theme.name().to_string(); @@ -3087,28 +3100,6 @@ pub(super) fn command_mode(cx: &mut Context) { let words = shellwords.words(); if words.is_empty() || (words.len() == 1 && !shellwords.ends_with_whitespace()) { - // let globals = - // crate::commands::engine::ScriptingEngine::fuzzy_match(&FUZZY_MATCHER, input) - // .into_iter() - // .map(|x| (Cow::from(x.0), x.1)) - // .collect::>(); - - // // If the command has not been finished yet, complete commands. - // let mut matches: Vec<_> = typed::TYPABLE_COMMAND_LIST - // .iter() - // .filter_map(|command| { - // FUZZY_MATCHER - // .fuzzy_match(command.name, input) - // .map(|score| (Cow::from(command.name), score)) - // }) - // .chain(globals) - // .collect(); - - // matches.sort_unstable_by_key(|(_file, score)| std::cmp::Reverse(*score)); - // matches - // .into_iter() - // .map(|(name, _)| (0.., name.into())) - // .collect() fuzzy_match( input, TYPABLE_COMMAND_LIST diff --git a/helix-term/src/handlers.rs b/helix-term/src/handlers.rs index cd59e8a1d..879d53318 100644 --- a/helix-term/src/handlers.rs +++ b/helix-term/src/handlers.rs @@ -22,7 +22,7 @@ fn rope_ends_with(text: &str, rope: RopeSlice<'_>) -> bool { .map_or(false, |end| end == text) } -mod completion; +pub(crate) mod completion; mod signature_help; pub fn setup(config: Arc>) -> Handlers { diff --git a/helix-term/src/handlers/completion.rs b/helix-term/src/handlers/completion.rs index a68981e41..6787ac768 100644 --- a/helix-term/src/handlers/completion.rs +++ b/helix-term/src/handlers/completion.rs @@ -32,20 +32,31 @@ use crate::ui::{self, CompletionItem, Popup}; use super::Handlers; #[derive(Debug, PartialEq, Eq, Clone, Copy)] -enum TriggerKind { +pub(crate) enum TriggerKind { Auto, TriggerChar, Manual, } #[derive(Debug, Clone, Copy)] -struct Trigger { +pub(crate) struct Trigger { pos: usize, view: ViewId, doc: DocumentId, kind: TriggerKind, } +impl Trigger { + pub(crate) fn new(pos: usize, view: ViewId, doc: DocumentId, kind: TriggerKind) -> Self { + Self { + pos, + view, + doc, + kind, + } + } +} + #[derive(Debug)] pub(super) struct CompletionHandler { /// currently active trigger which will cause a @@ -284,7 +295,7 @@ fn request_completion( }); } -fn show_completion( +pub(crate) fn show_completion( editor: &mut Editor, compositor: &mut Compositor, items: Vec, diff --git a/helix-term/src/job.rs b/helix-term/src/job.rs index f18cdcb77..a9b5fe25f 100644 --- a/helix-term/src/job.rs +++ b/helix-term/src/job.rs @@ -151,13 +151,6 @@ impl Jobs { } } - // pub async fn next_job(&mut self) -> Option>> { - // tokio::select! { - // event = self.futures.next() => { event } - // event = self.wait_futures.next() => { event } - // } - // } - pub fn add(&self, j: Job) { if j.wait { self.wait_futures.push(j.future); diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index d5ab318db..b7db2f0c8 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -278,22 +278,36 @@ impl Completion { // always present here let mut item = item.unwrap().clone(); - let language_server = language_server!(item); - let offset_encoding = language_server.offset_encoding(); + // let language_server = language_server!(item); + // let offset_encoding = language_server.offset_encoding(); - let language_server = editor - .language_servers - .get_by_id(item.language_server_id) - .unwrap(); + // let language_server = editor + // .language_servers + // .get_by_id(item.language_server_id) + // .unwrap(); + + let mut language_server_option = None; // resolve item if not yet resolved if !item.resolved { + let language_server = language_server!(item); + // let offset_encoding = language_server.offset_encoding(); + if let Some(resolved) = Self::resolve_completion_item(language_server, item.item.clone()) { item.item = resolved; } + + language_server_option = Some(language_server); }; + + // let language_server = language_server!(item); + let offset_encoding = language_server_option + .as_ref() + .map(|x| x.offset_encoding()) + .unwrap_or_default(); + // if more text was entered, remove it doc.restore(view, &savepoint, true); // save an undo checkpoint before the completion diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index cf20f80cb..65d56e88f 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -270,6 +270,11 @@ pub mod completers { for rt_dir in helix_loader::runtime_dirs() { names.extend(theme::Loader::read_names(&rt_dir.join("themes"))); } + + if let Some(themes) = crate::commands::engine::ScriptingEngine::themes() { + names.extend(themes); + } + names.push("default".into()); names.push("base16_default".into()); names.sort(); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 601af5c89..60ae93cd6 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -971,6 +971,7 @@ pub enum EditorEvent { pub enum ConfigEvent { Refresh, Update(Box), + UpdateLanguageConfiguration, } enum ThemeAction {