mirror of https://github.com/helix-editor/helix
start working on language configuration
parent
a9d5557a3b
commit
90488cbdc5
|
@ -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
|
||||
}
|
||||
|
|
|
@ -282,6 +282,7 @@ impl Application {
|
|||
crate::commands::ScriptingEngine::run_initialization_script(
|
||||
&mut cx,
|
||||
app.config.clone(),
|
||||
app.syn_loader.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>>,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
@ -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,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.
|
||||
guard.update_value(
|
||||
"*helix.config*",
|
||||
HelixConfiguration { configuration }
|
||||
HelixConfiguration {
|
||||
configuration,
|
||||
language_configuration,
|
||||
}
|
||||
.into_steelval()
|
||||
.unwrap(),
|
||||
);
|
||||
|
@ -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!()
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue