From d962e06e91b997813092cc5e49a800cc9d4f0ee1 Mon Sep 17 00:00:00 2001 From: Roland Kovacs Date: Tue, 5 Apr 2022 02:56:14 +0200 Subject: [PATCH] Add runtime language configuration (#1794) (#1866) * Add runtime language configuration (#1794) * Add set-language typable command to change the language of current buffer. * Add completer for available language options. * Update set-language to refresh language server as well * Add language id based config lookup on `syntax::Loader`. * Add `Document::set_language3` to set programming language based on language id. * Update `Editor::refresh_language_server` to try language detection only if language is not already set. * Remove language detection from Editor::refresh_language_server * Move document language detection to where the scratch buffer is saved. * Rename Document::set_language3 to Document::set_language_by_language_id. * Remove unnecessary clone in completers::language --- book/src/generated/typable-cmd.md | 1 + helix-core/src/syntax.rs | 11 +++++++++++ helix-term/src/commands/typed.rs | 26 ++++++++++++++++++++++++++ helix-term/src/ui/mod.rs | 21 +++++++++++++++++++++ helix-view/src/document.rs | 11 +++++++++++ helix-view/src/editor.rs | 1 - 6 files changed, 70 insertions(+), 1 deletion(-) diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index 85507b19a..f9261a756 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -53,6 +53,7 @@ | `:hsplit-new`, `:hnew` | Open a scratch buffer in a horizontal split. | | `:tutor` | Open the tutorial. | | `:goto`, `:g` | Go to line number. | +| `:set-language`, `:lang` | Set the language of current buffer. | | `:set-option`, `:set` | Set a config option at runtime | | `:sort` | Sort ranges in selection. | | `:rsort` | Sort ranges in selection in reverse order. | diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index e736b3708..bb0073e1c 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -504,6 +504,13 @@ impl Loader { .cloned() } + pub fn language_config_for_language_id(&self, id: &str) -> Option> { + self.language_configs + .iter() + .find(|config| config.language_id == id) + .cloned() + } + pub fn language_configuration_for_injection_string( &self, string: &str, @@ -529,6 +536,10 @@ impl Loader { None } + pub fn language_configs(&self) -> impl Iterator> { + self.language_configs.iter() + } + pub fn set_scopes(&self, scopes: Vec) { self.scopes.store(Arc::new(scopes)); diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 4c0447938..8f74adb62 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -217,6 +217,7 @@ fn write_impl(cx: &mut compositor::Context, path: Option<&Cow>) -> anyhow:: if path.is_some() { let id = doc.id(); + doc.detect_language(cx.editor.syn_loader.clone()); let _ = cx.editor.refresh_language_server(id); } Ok(()) @@ -931,6 +932,24 @@ fn setting( Ok(()) } +/// Change the language of the current buffer at runtime. +fn language( + cx: &mut compositor::Context, + args: &[Cow], + _event: PromptEvent, +) -> anyhow::Result<()> { + if args.len() != 1 { + anyhow::bail!("Bad arguments. Usage: `:set-language language`"); + } + + let doc = doc_mut!(cx.editor); + doc.set_language_by_language_id(&args[0], cx.editor.syn_loader.clone()); + + let id = doc.id(); + cx.editor.refresh_language_server(id); + Ok(()) +} + fn sort( cx: &mut compositor::Context, args: &[Cow], @@ -1408,6 +1427,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: goto_line_number, completer: None, }, + TypableCommand { + name: "set-language", + aliases: &["lang"], + doc: "Set the language of current buffer.", + fun: language, + completer: Some(completers::language), + }, TypableCommand { name: "set-option", aliases: &["set"], diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index a90debdb6..2dca870ba 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -298,6 +298,27 @@ pub mod completers { }) } + pub fn language(editor: &Editor, input: &str) -> Vec { + let matcher = Matcher::default(); + + let mut matches: Vec<_> = editor + .syn_loader + .language_configs() + .filter_map(|config| { + matcher + .fuzzy_match(&config.language_id, input) + .map(|score| (&config.language_id, score)) + }) + .collect(); + + matches.sort_unstable_by_key(|(_language, score)| Reverse(*score)); + + matches + .into_iter() + .map(|(language, _score)| ((0..), language.clone().into())) + .collect() + } + pub fn directory(_editor: &Editor, input: &str) -> Vec { filename_impl(input, |entry| { let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir()); diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index c9c1e5028..5d739af58 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -607,6 +607,17 @@ impl Document { self.set_language(language_config, Some(config_loader)); } + /// Set the programming language for the file if you know the language but don't have the + /// [`syntax::LanguageConfiguration`] for it. + pub fn set_language_by_language_id( + &mut self, + language_id: &str, + config_loader: Arc, + ) { + let language_config = config_loader.language_config_for_language_id(language_id); + self.set_language(language_config, Some(config_loader)); + } + /// Set the LSP. pub fn set_language_server(&mut self, language_server: Option>) { self.language_server = language_server; diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 9a2b4297f..c4e9ec283 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -448,7 +448,6 @@ impl Editor { /// Refreshes the language server for a given document pub fn refresh_language_server(&mut self, doc_id: DocumentId) -> Option<()> { let doc = self.documents.get_mut(&doc_id)?; - doc.detect_language(self.syn_loader.clone()); Self::launch_language_server(&mut self.language_servers, doc) }