diff options
Diffstat (limited to 'helix-term/src')
-rw-r--r-- | helix-term/src/commands/lsp.rs | 8 | ||||
-rw-r--r-- | helix-term/src/commands/typed.rs | 78 | ||||
-rw-r--r-- | helix-term/src/ui/mod.rs | 39 |
3 files changed, 125 insertions, 0 deletions
diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 5498fc83..c149e62b 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -603,6 +603,14 @@ 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 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 2f387bfd..c6810f05 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1052,6 +1052,77 @@ fn update( } } +fn lsp_workspace_command( + cx: &mut compositor::Context, + args: &[Cow<str>], + 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(()); + } + }; + if args.is_empty() { + let commands = options + .commands + .iter() + .map(|command| helix_lsp::lsp::Command { + title: command.clone(), + command: command.clone(), + arguments: None, + }) + .collect::<Vec<_>>(); + 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(()) +} + fn lsp_restart( cx: &mut compositor::Context, _args: &[Cow<str>], @@ -1988,6 +2059,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ completer: None, }, TypableCommand { + name: "lsp-workspace-command", + aliases: &[], + doc: "Open workspace command picker", + fun: lsp_workspace_command, + completer: Some(completers::lsp_workspace_command), + }, + TypableCommand { name: "lsp-restart", aliases: &[], doc: "Restarts the Language Server that is in use by the current doc", diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index f99dea0b..cca9e9bf 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<Completion> { + 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<Completion> { filename_impl(editor, input, |entry| { let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir()); |