aboutsummaryrefslogtreecommitdiff
path: root/helix-term
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term')
-rw-r--r--helix-term/src/commands/lsp.rs8
-rw-r--r--helix-term/src/commands/typed.rs78
-rw-r--r--helix-term/src/ui/mod.rs39
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());