start working on language configuration

pull/8675/merge^2
Matt Paras 2025-01-20 21:18:50 -08:00
parent a9d5557a3b
commit 90488cbdc5
5 changed files with 183 additions and 52 deletions

View File

@ -174,7 +174,40 @@ pub struct LanguageConfiguration {
pub persistent_diagnostic_sources: Vec<String>, pub persistent_diagnostic_sources: Vec<String>,
} }
#[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 { pub enum FileType {
/// The extension of the file, either the `Path::extension` or the full /// The extension of the file, either the `Path::extension` or the full
/// filename if the file does not have an extension. /// filename if the file does not have an extension.
@ -376,7 +409,7 @@ enum LanguageServerFeatureConfiguration {
Simple(String), Simple(String),
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Clone)]
pub struct LanguageServerFeatures { pub struct LanguageServerFeatures {
pub name: String, pub name: String,
pub only: HashSet<LanguageServerFeature>, pub only: HashSet<LanguageServerFeature>,
@ -455,7 +488,8 @@ where
builder.build().map(Some).map_err(serde::de::Error::custom) 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")] #[serde(rename_all = "kebab-case")]
pub struct LanguageServerConfiguration { pub struct LanguageServerConfiguration {
pub command: String, pub command: String,
@ -540,7 +574,7 @@ pub struct DebuggerQuirks {
pub absolute_paths: bool, pub absolute_paths: bool,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct IndentationConfiguration { pub struct IndentationConfiguration {
#[serde(deserialize_with = "deserialize_tab_width")] #[serde(deserialize_with = "deserialize_tab_width")]
@ -859,7 +893,8 @@ pub struct SoftWrap {
pub wrap_at_text_width: Option<bool>, pub wrap_at_text_width: Option<bool>,
} }
#[derive(Debug)] // TODO: Remove clone once the configuration API is decided
#[derive(Debug, Clone)]
struct FileTypeGlob { struct FileTypeGlob {
glob: globset::Glob, glob: globset::Glob,
language_id: usize, 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 { struct FileTypeGlobMatcher {
matcher: globset::GlobSet, matcher: globset::GlobSet,
file_types: Vec<FileTypeGlob>, file_types: Vec<FileTypeGlob>,
@ -901,18 +937,16 @@ impl FileTypeGlobMatcher {
} }
// Expose loader as Lazy<> global since it's always static? // Expose loader as Lazy<> global since it's always static?
// TODO: Remove clone once the configuration API is decided
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Loader { pub struct Loader {
// highlight_names ? // highlight_names ?
language_configs: Vec<Arc<LanguageConfiguration>>, language_configs: Vec<Arc<LanguageConfiguration>>,
language_config_ids_by_extension: HashMap<String, usize>, // Vec<usize> language_config_ids_by_extension: HashMap<String, usize>, // Vec<usize>
language_config_ids_glob_matcher: FileTypeGlobMatcher, language_config_ids_glob_matcher: FileTypeGlobMatcher,
language_config_ids_by_shebang: HashMap<String, usize>, language_config_ids_by_shebang: HashMap<String, usize>,
language_server_configs: HashMap<String, LanguageServerConfiguration>, language_server_configs: HashMap<String, LanguageServerConfiguration>,
scopes: Arc<ArcSwap<Vec<String>>>,
scopes: ArcSwap<Vec<String>>,
} }
pub type LoaderError = globset::Error; pub type LoaderError = globset::Error;
@ -952,7 +986,8 @@ impl Loader {
language_config_ids_glob_matcher: FileTypeGlobMatcher::new(file_type_globs)?, language_config_ids_glob_matcher: FileTypeGlobMatcher::new(file_type_globs)?,
language_config_ids_by_shebang, language_config_ids_by_shebang,
language_server_configs: config.language_server, 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() self.language_configs.iter()
} }
pub fn language_configs_mut(
&mut self,
) -> impl Iterator<Item = &mut Arc<LanguageConfiguration>> {
self.language_configs.iter_mut()
}
pub fn language_server_configs(&self) -> &HashMap<String, LanguageServerConfiguration> { pub fn language_server_configs(&self) -> &HashMap<String, LanguageServerConfiguration> {
&self.language_server_configs &self.language_server_configs
} }

View File

@ -282,6 +282,7 @@ impl Application {
crate::commands::ScriptingEngine::run_initialization_script( crate::commands::ScriptingEngine::run_initialization_script(
&mut cx, &mut cx,
app.config.clone(), app.config.clone(),
app.syn_loader.clone(),
); );
} }

View File

@ -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 helix_view::{document::Mode, input::KeyEvent};
use std::{borrow::Cow, sync::Arc}; use std::{borrow::Cow, sync::Arc};
@ -64,9 +65,17 @@ impl ScriptingEngine {
pub fn run_initialization_script( pub fn run_initialization_script(
cx: &mut Context, cx: &mut Context,
configuration: Arc<ArcSwapAny<Arc<Config>>>, configuration: Arc<ArcSwapAny<Arc<Config>>>,
language_configuration: Arc<ArcSwap<syntax::Loader>>,
) { ) {
for kind in PLUGIN_PRECEDENCE { 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, &self,
_cx: &mut Context, _cx: &mut Context,
_configuration: Arc<ArcSwapAny<Arc<Config>>>, _configuration: Arc<ArcSwapAny<Arc<Config>>>,
_language_configuration: Arc<ArcSwap<syntax::Loader>>,
) { ) {
} }

View File

@ -1,11 +1,11 @@
use arc_swap::ArcSwapAny; use arc_swap::{ArcSwap, ArcSwapAny};
use crossterm::event::{Event, KeyCode, KeyModifiers}; use crossterm::event::{Event, KeyCode, KeyModifiers};
use helix_core::{ use helix_core::{
diagnostic::Severity, diagnostic::Severity,
extensions::steel_implementations::{rope_module, SteelRopeSlice}, extensions::steel_implementations::{rope_module, SteelRopeSlice},
find_workspace, graphemes, find_workspace, graphemes,
shellwords::Shellwords, shellwords::Shellwords,
syntax::{AutoPairConfig, SoftWrap}, syntax::{self, AutoPairConfig, IndentationConfiguration, LanguageConfiguration, SoftWrap},
Range, Selection, Tendril, Range, Selection, Tendril,
}; };
use helix_event::register_hook; use helix_event::register_hook;
@ -17,6 +17,7 @@ use helix_view::{
GutterConfig, IndentGuidesConfig, LineEndingConfig, LineNumber, LspConfig, SearchConfig, GutterConfig, IndentGuidesConfig, LineEndingConfig, LineNumber, LspConfig, SearchConfig,
SmartTabConfig, StatusLineConfig, TerminalConfig, WhitespaceConfig, SmartTabConfig, StatusLineConfig, TerminalConfig, WhitespaceConfig,
}, },
events::DocumentFocusLost,
extension::document_id_to_usize, extension::document_id_to_usize,
input::KeyEvent, input::KeyEvent,
theme::Color, theme::Color,
@ -876,10 +877,6 @@ fn languages_api(engine: &mut Engine, generate_sources: bool) {
todo!() todo!()
} }
// fn test(ctx: &mut Context) {
// ctx.editor.syn_loader.load()
// }
// TODO: // TODO:
// This isn't the best API since it pretty much requires deserializing // This isn't the best API since it pretty much requires deserializing
// the whole theme model each time. While its not _horrible_, it is // the whole theme model each time. While its not _horrible_, it is
@ -1106,8 +1103,9 @@ impl super::PluginSystem for SteelScriptingEngine {
&self, &self,
cx: &mut Context, cx: &mut Context,
configuration: Arc<ArcSwapAny<Arc<Config>>>, configuration: Arc<ArcSwapAny<Arc<Config>>>,
language_configuration: Arc<ArcSwap<syntax::Loader>>,
) { ) {
run_initialization_script(cx, configuration); run_initialization_script(cx, configuration, language_configuration);
} }
fn handle_keymap_event( fn handle_keymap_event(
@ -1431,15 +1429,71 @@ pub fn steel_init_file() -> PathBuf {
preferred_config_path("init.scm") preferred_config_path("init.scm")
} }
#[derive(Clone)]
struct HelixConfiguration { struct HelixConfiguration {
configuration: Arc<ArcSwapAny<Arc<Config>>>, configuration: Arc<ArcSwapAny<Arc<Config>>>,
language_configuration: Arc<ArcSwap<helix_core::syntax::Loader>>,
}
#[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 HelixConfiguration {}
// impl Custom for LineNumber {} // impl Custom for LineNumber {}
impl HelixConfiguration { 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<IndividualLanguageConfiguration> {
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 { fn load_config(&self) -> Config {
(*self.configuration.load().clone()).clone() (*self.configuration.load().clone()).clone()
} }
@ -1743,7 +1797,11 @@ fn get_doc_for_global(engine: &mut Engine, ident: &str) -> Option<String> {
/// Run the initialization script located at `$helix_config/init.scm` /// 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 /// 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<ArcSwapAny<Arc<Config>>>) { fn run_initialization_script(
cx: &mut Context,
configuration: Arc<ArcSwapAny<Arc<Config>>>,
language_configuration: Arc<ArcSwap<syntax::Loader>>,
) {
log::info!("Loading init.scm..."); log::info!("Loading init.scm...");
let helix_module_path = helix_module_file(); let helix_module_path = helix_module_file();
@ -1755,7 +1813,10 @@ fn run_initialization_script(cx: &mut Context, configuration: Arc<ArcSwapAny<Arc
// now we can just access it and signal a refresh of the config when we need to. // now we can just access it and signal a refresh of the config when we need to.
guard.update_value( guard.update_value(
"*helix.config*", "*helix.config*",
HelixConfiguration { configuration } HelixConfiguration {
configuration,
language_configuration,
}
.into_steelval() .into_steelval()
.unwrap(), .unwrap(),
); );
@ -1903,7 +1964,6 @@ fn register_hook(event_kind: String, callback_fn: SteelVal) -> steel::UnRecovera
match event_kind.as_str() { match event_kind.as_str() {
"on-mode-switch" => { "on-mode-switch" => {
register_hook!(move |event: &mut OnModeSwitch<'_, '_>| { register_hook!(move |event: &mut OnModeSwitch<'_, '_>| {
// if enter_engine(|x| x.global_exists(&function_name)) {
if let Err(e) = enter_engine(|guard| { if let Err(e) = enter_engine(|guard| {
let minimized_event = OnModeSwitchEvent { let minimized_event = OnModeSwitchEvent {
old_mode: event.old_mode, 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| { guard.with_mut_reference(event.cx).consume(|engine, args| {
let context = args[0].clone(); let context = args[0].clone();
engine.update_value("*helix.cx*", context); engine.update_value("*helix.cx*", context);
let mut args = [minimized_event.into_steelval().unwrap()];
let mut args = vec![minimized_event.into_steelval().unwrap()];
// engine.call_function_by_name_with_args(&function_name, args) // engine.call_function_by_name_with_args(&function_name, args)
engine.call_function_with_args_from_mut_slice( engine.call_function_with_args_from_mut_slice(
rooted.value().clone(), 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()); event.cx.editor.set_error(e.to_string());
} }
// }
Ok(()) Ok(())
}); });
@ -1933,17 +1991,11 @@ fn register_hook(event_kind: String, callback_fn: SteelVal) -> steel::UnRecovera
} }
"post-insert-char" => { "post-insert-char" => {
register_hook!(move |event: &mut PostInsertChar<'_, '_>| { register_hook!(move |event: &mut PostInsertChar<'_, '_>| {
// if enter_engine(|x| x.global_exists(&function_name)) {
if let Err(e) = enter_engine(|guard| { if let Err(e) = enter_engine(|guard| {
guard.with_mut_reference(event.cx).consume(|engine, args| { guard.with_mut_reference(event.cx).consume(|engine, args| {
let context = args[0].clone(); let context = args[0].clone();
engine.update_value("*helix.cx*", context); engine.update_value("*helix.cx*", context);
let mut args = [event.c.into()];
// 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()];
engine.call_function_with_args_from_mut_slice( engine.call_function_with_args_from_mut_slice(
rooted.value().clone(), rooted.value().clone(),
&mut args, &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()); event.cx.editor.set_error(e.to_string());
} }
// }
Ok(()) Ok(())
}); });
@ -1962,36 +2013,66 @@ fn register_hook(event_kind: String, callback_fn: SteelVal) -> steel::UnRecovera
// Register hook - on save? // Register hook - on save?
"post-command" => { "post-command" => {
register_hook!(move |event: &mut PostCommand<'_, '_>| { register_hook!(move |event: &mut PostCommand<'_, '_>| {
// if enter_engine(|x| x.global_exists(&function_name)) {
if let Err(e) = enter_engine(|guard| { if let Err(e) = enter_engine(|guard| {
guard.with_mut_reference(event.cx).consume(|engine, args| { guard.with_mut_reference(event.cx).consume(|engine, args| {
let context = args[0].clone(); let context = args[0].clone();
engine.update_value("*helix.cx*", context); engine.update_value("*helix.cx*", context);
let mut args = [event.command.name().into_steelval().unwrap()];
let mut args = vec![event.command.name().into_steelval().unwrap()];
engine.call_function_with_args_from_mut_slice( engine.call_function_with_args_from_mut_slice(
rooted.value().clone(), rooted.value().clone(),
&mut args, &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()); event.cx.editor.set_error(e.to_string());
} }
// }
Ok(()) Ok(())
}); });
Ok(SteelVal::Void).into() 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::<Context, Context>(&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! // Unimplemented!
// "document-did-change" => { // "document-did-change" => {
// todo!() // todo!()

View File

@ -1926,8 +1926,6 @@ impl Editor {
// if leaving the view: mode should reset and the cursor should be // if leaving the view: mode should reset and the cursor should be
// within view // within view
if prev_id != view_id { if prev_id != view_id {
log::info!("Changing focus: {:?}", view_id);
// TODO: Consult map for modes to change given file type? // TODO: Consult map for modes to change given file type?
self.enter_normal_mode(); self.enter_normal_mode();