From 5ce8f8842bc8d5321cd07b8d9c11c7850d145722 Mon Sep 17 00:00:00 2001 From: MDeiml Date: Fri, 22 Jul 2022 00:40:09 +0200 Subject: [PATCH 1/3] Add workspace command picker --- helix-lsp/src/client.rs | 3 +++ helix-term/src/commands.rs | 1 + helix-term/src/commands/lsp.rs | 36 ++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 0b443ccf402d..af3c4b57d17b 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -298,6 +298,9 @@ impl Client { dynamic_registration: Some(false), ..Default::default() }), + execute_command: Some(lsp::DynamicRegistrationClientCapabilities { + dynamic_registration: Some(false), + }), ..Default::default() }), text_document: Some(lsp::TextDocumentClientCapabilities { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 48bd9e572a63..084b4838719a 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -268,6 +268,7 @@ impl MappableCommand { file_picker, "Open file picker", file_picker_in_current_directory, "Open file picker at current working directory", code_action, "Perform code action", + workspace_command_picker, "Open workspace command picker", buffer_picker, "Open buffer picker", jumplist_picker, "Open jumplist picker", symbol_picker, "Open symbol picker", diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 3c72cd2a5442..cfb79a3f7ebd 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -603,6 +603,42 @@ pub fn code_action(cx: &mut Context) { }, ) } + +impl ui::menu::Item for lsp::Command { + type Data = (); + fn label(&self, _data: &Self::Data) -> Spans { + self.title.as_str().into() + } +} + +pub fn workspace_command_picker(cx: &mut Context) { + let (_, doc) = current!(cx.editor); + + let language_server = language_server!(cx.editor, doc); + + let options = match &language_server.capabilities().execute_command_provider { + Some(options) => options, + None => return, + }; + let commands = options + .commands + .iter() + .map(|command| lsp::Command { + title: command.clone(), + command: command.clone(), + arguments: None, + }) + .collect::>(); + cx.callback = Some(Box::new( + |compositor: &mut Compositor, _cx: &mut compositor::Context| { + let picker = ui::Picker::new(commands, (), |cx, command, _action| { + execute_lsp_command(cx.editor, command.clone()); + }); + compositor.push(Box::new(overlayed(picker))) + }, + )); +} + pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) { let doc = doc!(editor); let language_server = language_server!(editor, doc); From 865cd1bc11c50fa2c92ce631b73623b47d0a3c73 Mon Sep 17 00:00:00 2001 From: MDeiml Date: Sat, 29 Oct 2022 14:02:30 +0200 Subject: [PATCH 2/3] Make command typable --- book/src/generated/typable-cmd.md | 1 + helix-term/src/commands.rs | 1 - helix-term/src/commands/lsp.rs | 28 --------------- helix-term/src/commands/typed.rs | 59 +++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 29 deletions(-) diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index f858ba7255df..b7496d338c4e 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -45,6 +45,7 @@ | `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. | | `:reload` | Discard changes and reload from the source file. | | `:update` | Write changes only if the file has been modified. | +| `:lsp-workspace-command` | Open workspace command picker | | `:lsp-restart` | Restarts the Language Server that is in use by the current doc | | `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. | | `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 084b4838719a..48bd9e572a63 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -268,7 +268,6 @@ impl MappableCommand { file_picker, "Open file picker", file_picker_in_current_directory, "Open file picker at current working directory", code_action, "Perform code action", - workspace_command_picker, "Open workspace command picker", buffer_picker, "Open buffer picker", jumplist_picker, "Open jumplist picker", symbol_picker, "Open symbol picker", diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index cfb79a3f7ebd..fd5dcd99a567 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -611,34 +611,6 @@ impl ui::menu::Item for lsp::Command { } } -pub fn workspace_command_picker(cx: &mut Context) { - let (_, doc) = current!(cx.editor); - - let language_server = language_server!(cx.editor, doc); - - let options = match &language_server.capabilities().execute_command_provider { - Some(options) => options, - None => return, - }; - let commands = options - .commands - .iter() - .map(|command| lsp::Command { - title: command.clone(), - command: command.clone(), - arguments: None, - }) - .collect::>(); - cx.callback = Some(Box::new( - |compositor: &mut Compositor, _cx: &mut compositor::Context| { - let picker = ui::Picker::new(commands, (), |cx, command, _action| { - execute_lsp_command(cx.editor, command.clone()); - }); - compositor.push(Box::new(overlayed(picker))) - }, - )); -} - pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) { let doc = doc!(editor); let language_server = language_server!(editor, doc); diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 0cf75ada91e7..b5e9b47127d6 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1052,6 +1052,58 @@ fn update( } } +fn lsp_workspace_command( + cx: &mut compositor::Context, + _args: &[Cow], + event: PromptEvent, +) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + } + + let (_, doc) = current!(cx.editor); + + let language_server = match doc.language_server() { + Some(language_server) => language_server, + None => { + cx.editor + .set_status("Language server not active for current buffer"); + return Ok(()); + } + }; + + let options = match &language_server.capabilities().execute_command_provider { + Some(options) => options, + None => { + cx.editor + .set_status("Workspace commands are not supported for this language server"); + return Ok(()); + } + }; + let commands = options + .commands + .iter() + .map(|command| helix_lsp::lsp::Command { + title: command.clone(), + command: command.clone(), + arguments: None, + }) + .collect::>(); + let callback = async move { + let call: job::Callback = Callback::EditorCompositor(Box::new( + move |_editor: &mut Editor, compositor: &mut Compositor| { + let picker = ui::Picker::new(commands, (), |cx, command, _action| { + execute_lsp_command(cx.editor, command.clone()); + }); + compositor.push(Box::new(overlayed(picker))) + }, + )); + Ok(call) + }; + cx.jobs.callback(callback); + Ok(()) +} + fn lsp_restart( cx: &mut compositor::Context, _args: &[Cow], @@ -1987,6 +2039,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: update, completer: None, }, + TypableCommand { + name: "lsp-workspace-command", + aliases: &[], + doc: "Open workspace command picker", + fun: lsp_workspace_command, + completer: None, + }, TypableCommand { name: "lsp-restart", aliases: &[], From 183822f8e6668347ee6cc7eec7ca58624d5e9ba0 Mon Sep 17 00:00:00 2001 From: MDeiml Date: Tue, 1 Nov 2022 16:03:59 +0100 Subject: [PATCH 3/3] Add optional argument to lsp-workspace-command --- helix-term/src/commands/typed.rs | 65 +++++++++++++++++++++----------- helix-term/src/ui/mod.rs | 39 +++++++++++++++++++ 2 files changed, 81 insertions(+), 23 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index b5e9b47127d6..6b65daa17d79 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1054,7 +1054,7 @@ fn update( fn lsp_workspace_command( cx: &mut compositor::Context, - _args: &[Cow], + args: &[Cow], event: PromptEvent, ) -> anyhow::Result<()> { if event != PromptEvent::Validate { @@ -1080,27 +1080,46 @@ fn lsp_workspace_command( return Ok(()); } }; - let commands = options - .commands - .iter() - .map(|command| helix_lsp::lsp::Command { - title: command.clone(), - command: command.clone(), - arguments: None, - }) - .collect::>(); - let callback = async move { - let call: job::Callback = Callback::EditorCompositor(Box::new( - move |_editor: &mut Editor, compositor: &mut Compositor| { - let picker = ui::Picker::new(commands, (), |cx, command, _action| { - execute_lsp_command(cx.editor, command.clone()); - }); - compositor.push(Box::new(overlayed(picker))) - }, - )); - Ok(call) - }; - cx.jobs.callback(callback); + if args.is_empty() { + let commands = options + .commands + .iter() + .map(|command| helix_lsp::lsp::Command { + title: command.clone(), + command: command.clone(), + arguments: None, + }) + .collect::>(); + let callback = async move { + let call: job::Callback = Callback::EditorCompositor(Box::new( + move |_editor: &mut Editor, compositor: &mut Compositor| { + let picker = ui::Picker::new(commands, (), |cx, command, _action| { + execute_lsp_command(cx.editor, command.clone()); + }); + compositor.push(Box::new(overlayed(picker))) + }, + )); + Ok(call) + }; + cx.jobs.callback(callback); + } else { + let command = args.join(" "); + if options.commands.iter().any(|c| c == &command) { + execute_lsp_command( + cx.editor, + helix_lsp::lsp::Command { + title: command.clone(), + arguments: None, + command, + }, + ); + } else { + cx.editor.set_status(format!( + "`{command}` is not supported for this language server" + )); + return Ok(()); + } + } Ok(()) } @@ -2044,7 +2063,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ aliases: &[], doc: "Open workspace command picker", fun: lsp_workspace_command, - completer: None, + completer: Some(completers::lsp_workspace_command), }, TypableCommand { name: "lsp-restart", diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index f99dea0b8dc3..cca9e9bf0b68 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -390,6 +390,45 @@ pub mod completers { .collect() } + pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec { + let matcher = Matcher::default(); + + let (_, doc) = current_ref!(editor); + + let language_server = match doc.language_server() { + Some(language_server) => language_server, + None => { + return vec![]; + } + }; + + let options = match &language_server.capabilities().execute_command_provider { + Some(options) => options, + None => { + return vec![]; + } + }; + + let mut matches: Vec<_> = options + .commands + .iter() + .filter_map(|command| { + matcher + .fuzzy_match(command, input) + .map(|score| (command, score)) + }) + .collect(); + + matches.sort_unstable_by(|(command1, score1), (command2, score2)| { + (Reverse(*score1), command1).cmp(&(Reverse(*score2), command2)) + }); + + matches + .into_iter() + .map(|(command, _score)| ((0..), command.clone().into())) + .collect() + } + pub fn directory(editor: &Editor, input: &str) -> Vec { filename_impl(editor, input, |entry| { let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());