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>,
}
#[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<LanguageServerFeature>,
@ -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<bool>,
}
#[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<FileTypeGlob>,
@ -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<Arc<LanguageConfiguration>>,
language_config_ids_by_extension: HashMap<String, usize>, // Vec<usize>
language_config_ids_glob_matcher: FileTypeGlobMatcher,
language_config_ids_by_shebang: HashMap<String, usize>,
language_server_configs: HashMap<String, LanguageServerConfiguration>,
scopes: ArcSwap<Vec<String>>,
scopes: Arc<ArcSwap<Vec<String>>>,
}
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<Item = &mut Arc<LanguageConfiguration>> {
self.language_configs.iter_mut()
}
pub fn language_server_configs(&self) -> &HashMap<String, LanguageServerConfiguration> {
&self.language_server_configs
}

View File

@ -282,6 +282,7 @@ impl Application {
crate::commands::ScriptingEngine::run_initialization_script(
&mut cx,
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 std::{borrow::Cow, sync::Arc};
@ -64,9 +65,17 @@ impl ScriptingEngine {
pub fn run_initialization_script(
cx: &mut Context,
configuration: Arc<ArcSwapAny<Arc<Config>>>,
language_configuration: Arc<ArcSwap<syntax::Loader>>,
) {
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<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 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<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(
@ -1431,15 +1429,71 @@ pub fn steel_init_file() -> PathBuf {
preferred_config_path("init.scm")
}
#[derive(Clone)]
struct HelixConfiguration {
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 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<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 {
(*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`
/// 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...");
let helix_module_path = helix_module_file();
@ -1755,9 +1813,12 @@ 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.
guard.update_value(
"*helix.config*",
HelixConfiguration { configuration }
.into_steelval()
.unwrap(),
HelixConfiguration {
configuration,
language_configuration,
}
.into_steelval()
.unwrap(),
);
let res = guard.run_with_reference(
@ -1903,7 +1964,6 @@ fn register_hook(event_kind: String, callback_fn: SteelVal) -> 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::<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!
// "document-did-change" => {
// todo!()

View File

@ -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();