diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 375846b02..dc8f66039 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -174,7 +174,40 @@ pub struct LanguageConfiguration { pub persistent_diagnostic_sources: Vec, } -#[derive(Debug, PartialEq, Eq, Hash)] +impl Clone for LanguageConfiguration { + fn clone(&self) -> Self { + LanguageConfiguration { + language_id: self.language_id.clone(), + language_server_language_id: self.language_server_language_id.clone(), + scope: self.scope.clone(), + file_types: self.file_types.clone(), + shebangs: self.shebangs.clone(), + roots: self.roots.clone(), + comment_tokens: self.comment_tokens.clone(), + block_comment_tokens: self.block_comment_tokens.clone(), + text_width: self.text_width.clone(), + soft_wrap: self.soft_wrap.clone(), + auto_format: self.auto_format.clone(), + formatter: self.formatter.clone(), + diagnostic_severity: self.diagnostic_severity.clone(), + grammar: self.grammar.clone(), + injection_regex: self.injection_regex.clone(), + highlight_config: self.highlight_config.clone(), + language_servers: self.language_servers.clone(), + indent: self.indent.clone(), + indent_query: OnceCell::new(), + textobject_query: OnceCell::new(), + debugger: self.debugger.clone(), + auto_pairs: self.auto_pairs.clone(), + rulers: self.rulers.clone(), + workspace_lsp_roots: self.workspace_lsp_roots.clone(), + persistent_diagnostic_sources: self.persistent_diagnostic_sources.clone(), + path_completion: self.path_completion, + } + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum FileType { /// The extension of the file, either the `Path::extension` or the full /// filename if the file does not have an extension. @@ -376,7 +409,7 @@ enum LanguageServerFeatureConfiguration { Simple(String), } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct LanguageServerFeatures { pub name: String, pub only: HashSet, @@ -455,7 +488,8 @@ where builder.build().map(Some).map_err(serde::de::Error::custom) } -#[derive(Debug, Serialize, Deserialize)] +// TODO: Remove clone once the configuration API is decided +#[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] pub struct LanguageServerConfiguration { pub command: String, @@ -540,7 +574,7 @@ pub struct DebuggerQuirks { pub absolute_paths: bool, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] pub struct IndentationConfiguration { #[serde(deserialize_with = "deserialize_tab_width")] @@ -859,7 +893,8 @@ pub struct SoftWrap { pub wrap_at_text_width: Option, } -#[derive(Debug)] +// TODO: Remove clone once the configuration API is decided +#[derive(Debug, Clone)] struct FileTypeGlob { glob: globset::Glob, language_id: usize, @@ -871,7 +906,8 @@ impl FileTypeGlob { } } -#[derive(Debug)] +// TODO: Remove clone once the configuration API is decided +#[derive(Debug, Clone)] struct FileTypeGlobMatcher { matcher: globset::GlobSet, file_types: Vec, @@ -901,18 +937,16 @@ impl FileTypeGlobMatcher { } // Expose loader as Lazy<> global since it's always static? - -#[derive(Debug)] +// TODO: Remove clone once the configuration API is decided +#[derive(Debug, Clone)] pub struct Loader { // highlight_names ? language_configs: Vec>, language_config_ids_by_extension: HashMap, // Vec language_config_ids_glob_matcher: FileTypeGlobMatcher, language_config_ids_by_shebang: HashMap, - language_server_configs: HashMap, - - scopes: ArcSwap>, + scopes: Arc>>, } pub type LoaderError = globset::Error; @@ -952,7 +986,8 @@ impl Loader { language_config_ids_glob_matcher: FileTypeGlobMatcher::new(file_type_globs)?, language_config_ids_by_shebang, language_server_configs: config.language_server, - scopes: ArcSwap::from_pointee(Vec::new()), + // TODO: Remove this once the configuration API is decided + scopes: Arc::new(ArcSwap::from_pointee(Vec::new())), }) } @@ -1039,6 +1074,12 @@ impl Loader { self.language_configs.iter() } + pub fn language_configs_mut( + &mut self, + ) -> impl Iterator> { + self.language_configs.iter_mut() + } + pub fn language_server_configs(&self) -> &HashMap { &self.language_server_configs } diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index a5741e1d6..9425a1d69 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -282,6 +282,7 @@ impl Application { crate::commands::ScriptingEngine::run_initialization_script( &mut cx, app.config.clone(), + app.syn_loader.clone(), ); } diff --git a/helix-term/src/commands/engine.rs b/helix-term/src/commands/engine.rs index 5c7528458..89b306214 100644 --- a/helix-term/src/commands/engine.rs +++ b/helix-term/src/commands/engine.rs @@ -1,4 +1,5 @@ -use arc_swap::ArcSwapAny; +use arc_swap::{ArcSwap, ArcSwapAny}; +use helix_core::syntax; use helix_view::{document::Mode, input::KeyEvent}; use std::{borrow::Cow, sync::Arc}; @@ -64,9 +65,17 @@ impl ScriptingEngine { pub fn run_initialization_script( cx: &mut Context, configuration: Arc>>, + language_configuration: Arc>, ) { for kind in PLUGIN_PRECEDENCE { - manual_dispatch!(kind, run_initialization_script(cx, configuration.clone())) + manual_dispatch!( + kind, + run_initialization_script( + cx, + configuration.clone(), + language_configuration.clone() + ) + ) } } @@ -159,6 +168,7 @@ pub trait PluginSystem { &self, _cx: &mut Context, _configuration: Arc>>, + _language_configuration: Arc>, ) { } diff --git a/helix-term/src/commands/engine/steel.rs b/helix-term/src/commands/engine/steel.rs index b47a043a6..6362a04b0 100644 --- a/helix-term/src/commands/engine/steel.rs +++ b/helix-term/src/commands/engine/steel.rs @@ -1,11 +1,11 @@ -use arc_swap::ArcSwapAny; +use arc_swap::{ArcSwap, ArcSwapAny}; use crossterm::event::{Event, KeyCode, KeyModifiers}; use helix_core::{ diagnostic::Severity, extensions::steel_implementations::{rope_module, SteelRopeSlice}, find_workspace, graphemes, shellwords::Shellwords, - syntax::{AutoPairConfig, SoftWrap}, + syntax::{self, AutoPairConfig, IndentationConfiguration, LanguageConfiguration, SoftWrap}, Range, Selection, Tendril, }; use helix_event::register_hook; @@ -17,6 +17,7 @@ use helix_view::{ GutterConfig, IndentGuidesConfig, LineEndingConfig, LineNumber, LspConfig, SearchConfig, SmartTabConfig, StatusLineConfig, TerminalConfig, WhitespaceConfig, }, + events::DocumentFocusLost, extension::document_id_to_usize, input::KeyEvent, theme::Color, @@ -876,10 +877,6 @@ fn languages_api(engine: &mut Engine, generate_sources: bool) { todo!() } -// fn test(ctx: &mut Context) { -// ctx.editor.syn_loader.load() -// } - // TODO: // This isn't the best API since it pretty much requires deserializing // the whole theme model each time. While its not _horrible_, it is @@ -1106,8 +1103,9 @@ impl super::PluginSystem for SteelScriptingEngine { &self, cx: &mut Context, configuration: Arc>>, + language_configuration: Arc>, ) { - run_initialization_script(cx, configuration); + run_initialization_script(cx, configuration, language_configuration); } fn handle_keymap_event( @@ -1431,15 +1429,71 @@ pub fn steel_init_file() -> PathBuf { preferred_config_path("init.scm") } -#[derive(Clone)] struct HelixConfiguration { configuration: Arc>>, + language_configuration: Arc>, +} + +#[derive(Clone)] +struct IndividualLanguageConfiguration { + config: LanguageConfiguration, +} + +impl Custom for IndividualLanguageConfiguration {} + +impl IndividualLanguageConfiguration { + pub fn set_indentation_config(&mut self, tab_width: usize, unit: String) { + self.config.indent = Some(IndentationConfiguration { tab_width, unit }); + } + + // Apply end of line configuration on doc open? + // pub fn set_end_of_line(&mut self) { + // self. + // } } impl Custom for HelixConfiguration {} + // impl Custom for LineNumber {} impl HelixConfiguration { + fn store_language_configuration(&self, language_config: syntax::Loader) { + self.language_configuration.store(Arc::new(language_config)) + } + + fn get_individual_language_config_for_filename( + &self, + file_name: &str, + ) -> Option { + self.language_configuration + .load() + .language_config_for_file_name(std::path::Path::new(file_name)) + .map(|config| IndividualLanguageConfiguration { + config: (*config).clone(), + }) + } + + // Update the language config - this does not immediately flush it + // to the actual config. + fn update_individual_language_config(&mut self, config: IndividualLanguageConfiguration) { + // TODO: Try to opportunistically load the ref counts + // of the inner values - if the documents haven't been opened yet, we + // don't need to clone the _whole_ loader. + let mut loader = (*(*self.language_configuration.load())).clone(); + let config = config.config; + + for lconfig in loader.language_configs_mut() { + if &lconfig.language_id == &config.language_id { + if let Some(inner) = Arc::get_mut(lconfig) { + *inner = config; + } else { + *lconfig = Arc::new(config); + } + break; + } + } + } + fn load_config(&self) -> Config { (*self.configuration.load().clone()).clone() } @@ -1743,7 +1797,11 @@ fn get_doc_for_global(engine: &mut Engine, ident: &str) -> Option { /// 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 -fn run_initialization_script(cx: &mut Context, configuration: Arc>>) { +fn run_initialization_script( + cx: &mut Context, + configuration: Arc>>, + language_configuration: Arc>, +) { log::info!("Loading init.scm..."); let helix_module_path = helix_module_file(); @@ -1755,9 +1813,12 @@ fn run_initialization_script(cx: &mut Context, configuration: Arc steel::UnRecovera match event_kind.as_str() { "on-mode-switch" => { register_hook!(move |event: &mut OnModeSwitch<'_, '_>| { - // if enter_engine(|x| x.global_exists(&function_name)) { if let Err(e) = enter_engine(|guard| { let minimized_event = OnModeSwitchEvent { old_mode: event.old_mode, @@ -1913,8 +1973,7 @@ fn register_hook(event_kind: String, callback_fn: SteelVal) -> steel::UnRecovera guard.with_mut_reference(event.cx).consume(|engine, args| { let context = args[0].clone(); engine.update_value("*helix.cx*", context); - - let mut args = vec![minimized_event.into_steelval().unwrap()]; + let mut args = [minimized_event.into_steelval().unwrap()]; // engine.call_function_by_name_with_args(&function_name, args) engine.call_function_with_args_from_mut_slice( rooted.value().clone(), @@ -1924,7 +1983,6 @@ fn register_hook(event_kind: String, callback_fn: SteelVal) -> steel::UnRecovera }) { event.cx.editor.set_error(e.to_string()); } - // } Ok(()) }); @@ -1933,17 +1991,11 @@ fn register_hook(event_kind: String, callback_fn: SteelVal) -> steel::UnRecovera } "post-insert-char" => { register_hook!(move |event: &mut PostInsertChar<'_, '_>| { - // if enter_engine(|x| x.global_exists(&function_name)) { if let Err(e) = enter_engine(|guard| { guard.with_mut_reference(event.cx).consume(|engine, args| { let context = args[0].clone(); engine.update_value("*helix.cx*", context); - - // args.push(event.c.into()); - // engine.call_function_by_name_with_args(&function_name, vec![event.c.into()]) - - let mut args = vec![event.c.into()]; - + let mut args = [event.c.into()]; engine.call_function_with_args_from_mut_slice( rooted.value().clone(), &mut args, @@ -1952,7 +2004,6 @@ fn register_hook(event_kind: String, callback_fn: SteelVal) -> steel::UnRecovera }) { event.cx.editor.set_error(e.to_string()); } - // } Ok(()) }); @@ -1962,36 +2013,66 @@ fn register_hook(event_kind: String, callback_fn: SteelVal) -> steel::UnRecovera // Register hook - on save? "post-command" => { register_hook!(move |event: &mut PostCommand<'_, '_>| { - // if enter_engine(|x| x.global_exists(&function_name)) { if let Err(e) = enter_engine(|guard| { guard.with_mut_reference(event.cx).consume(|engine, args| { let context = args[0].clone(); engine.update_value("*helix.cx*", context); - - let mut args = vec![event.command.name().into_steelval().unwrap()]; - + let mut args = [event.command.name().into_steelval().unwrap()]; engine.call_function_with_args_from_mut_slice( rooted.value().clone(), &mut args, ) - - // args.push(event.command.clone().into_steelval().unwrap()); - // engine.call_function_by_name_with_args( - // &function_name, - // // Name? - // vec![event.command.name().into_steelval().unwrap()], - // ) }) }) { event.cx.editor.set_error(e.to_string()); } - // } Ok(()) }); Ok(SteelVal::Void).into() } + + "document-focus-lost" => { + // TODO: Pass the information from the event in here - the doc id + // is probably the most helpful so that way we can look the document up + // and act accordingly? + register_hook!(move |event: &mut DocumentFocusLost<'_>| { + let cloned_func = rooted.value().clone(); + + let callback = move |editor: &mut Editor, + _compositor: &mut Compositor, + jobs: &mut job::Jobs| { + let mut ctx = Context { + register: None, + count: None, + editor, + callback: Vec::new(), + on_next_key_callback: None, + jobs, + }; + enter_engine(|guard| { + if let Err(e) = guard + .with_mut_reference::(&mut ctx) + .consume(move |engine, args| { + let context = args[0].clone(); + engine.update_value("*helix.cx*", context); + // TODO: Do something with this error! + engine.call_function_with_args(cloned_func.clone(), Vec::new()) + }) + { + present_error_inside_engine_context(&mut ctx, guard, e); + } + }) + }; + job::dispatch_blocking_jobs(callback); + + Ok(()) + }); + + Ok(SteelVal::Void).into() + } + // Unimplemented! // "document-did-change" => { // todo!() diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 3fc08d3f8..125223c5e 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1926,8 +1926,6 @@ impl Editor { // if leaving the view: mode should reset and the cursor should be // within view if prev_id != view_id { - log::info!("Changing focus: {:?}", view_id); - // TODO: Consult map for modes to change given file type? self.enter_normal_mode();