From d31bef7fea1aa0cc9455cf082825ccff9614ed5e Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Fri, 3 Dec 2021 10:05:27 +0900 Subject: lsp: Don't panic if init fails We correctly filter out the language server inside Document to ignore it if the capabilities are missing, so this way it'll simply ignore the errored out LSP rather than panicking. --- helix-lsp/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'helix-lsp/src/lib.rs') diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 7fa65928..15cae582 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -337,7 +337,10 @@ impl Registry { }) .await; - value.expect("failed to initialize capabilities"); + if let Err(e) = value { + log::error!("failed to initialize language server: {}", e); + return; + } // next up, notify _client -- cgit v1.2.3-70-g09d2 From 75a8b789d20edf8b2e1d3da75497a9936953de68 Mon Sep 17 00:00:00 2001 From: Matouš Dzivjak Date: Tue, 21 Dec 2021 10:21:45 +0100 Subject: LSP code action commands (#1304) * feat(lsp): codeAction commands * Don't block on command call * Fix lifetime of command execution * Fix lint issues--- helix-lsp/src/client.rs | 14 ++++++++++++- helix-lsp/src/lib.rs | 7 +++++++ helix-term/src/application.rs | 48 +++++++++++++++++++++++++++++++++++-------- helix-term/src/commands.rs | 33 ++++++++++++++++++++++++++--- 4 files changed, 89 insertions(+), 13 deletions(-) (limited to 'helix-lsp/src/lib.rs') diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 271fd9d5..f1de8752 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -202,7 +202,7 @@ impl Client { Ok(result) => Output::Success(Success { jsonrpc: Some(Version::V2), id, - result, + result: serde_json::to_value(result)?, }), Err(error) => Output::Failure(Failure { jsonrpc: Some(Version::V2), @@ -800,4 +800,16 @@ impl Client { let response = self.request::(params).await?; Ok(response.unwrap_or_default()) } + + pub fn command(&self, command: lsp::Command) -> impl Future> { + let params = lsp::ExecuteCommandParams { + command: command.command, + arguments: command.arguments.unwrap_or_default(), + work_done_progress_params: lsp::WorkDoneProgressParams { + work_done_token: None, + }, + }; + + self.call::(params) + } } diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 15cae582..8fb321bc 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -203,6 +203,7 @@ pub mod util { #[derive(Debug, PartialEq, Clone)] pub enum MethodCall { WorkDoneProgressCreate(lsp::WorkDoneProgressCreateParams), + ApplyWorkspaceEdit(lsp::ApplyWorkspaceEditParams), } impl MethodCall { @@ -215,6 +216,12 @@ impl MethodCall { .expect("Failed to parse WorkDoneCreate params"); Self::WorkDoneProgressCreate(params) } + lsp::request::ApplyWorkspaceEdit::METHOD => { + let params: lsp::ApplyWorkspaceEditParams = params + .parse() + .expect("Failed to parse ApplyWorkspaceEdit params"); + Self::ApplyWorkspaceEdit(params) + } _ => { log::warn!("unhandled lsp request: {}", method); return None; diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 3e0b6d59..9a46a7fe 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1,8 +1,12 @@ use helix_core::{merge_toml_values, syntax}; use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap}; use helix_view::{theme, Editor}; +use serde_json::json; -use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui}; +use crate::{ + args::Args, commands::apply_workspace_edit, compositor::Compositor, config::Config, job::Jobs, + ui, +}; use log::{error, warn}; @@ -530,14 +534,6 @@ impl Application { Call::MethodCall(helix_lsp::jsonrpc::MethodCall { method, params, id, .. }) => { - let language_server = match self.editor.language_servers.get_by_id(server_id) { - Some(language_server) => language_server, - None => { - warn!("can't find language server with id `{}`", server_id); - return; - } - }; - let call = match MethodCall::parse(&method, params) { Some(call) => call, None => { @@ -567,8 +563,42 @@ impl Application { if spinner.is_stopped() { spinner.start(); } + let language_server = + match self.editor.language_servers.get_by_id(server_id) { + Some(language_server) => language_server, + None => { + warn!("can't find language server with id `{}`", server_id); + return; + } + }; + tokio::spawn(language_server.reply(id, Ok(serde_json::Value::Null))); } + MethodCall::ApplyWorkspaceEdit(params) => { + apply_workspace_edit( + &mut self.editor, + helix_lsp::OffsetEncoding::Utf8, + ¶ms.edit, + ); + + let language_server = + match self.editor.language_servers.get_by_id(server_id) { + Some(language_server) => language_server, + None => { + warn!("can't find language server with id `{}`", server_id); + return; + } + }; + + tokio::spawn(language_server.reply( + id, + Ok(json!(lsp::ApplyWorkspaceEditResponse { + applied: true, + failure_reason: None, + failed_change: None, + })), + )); + } } } e => unreachable!("{:?}", e), diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 297b49a1..ee6a5989 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3277,12 +3277,19 @@ pub fn code_action(cx: &mut Context) { move |editor, code_action, _action| match code_action { lsp::CodeActionOrCommand::Command(command) => { log::debug!("code action command: {:?}", command); - editor.set_error(String::from("Handling code action command is not implemented yet, see https://github.com/helix-editor/helix/issues/183")); + execute_lsp_command(editor, command.clone()); } lsp::CodeActionOrCommand::CodeAction(code_action) => { log::debug!("code action: {:?}", code_action); if let Some(ref workspace_edit) = code_action.edit { - apply_workspace_edit(editor, offset_encoding, workspace_edit) + log::debug!("edit: {:?}", workspace_edit); + apply_workspace_edit(editor, offset_encoding, workspace_edit); + } + + // if code action provides both edit and command first the edit + // should be applied and then the command + if let Some(command) = &code_action.command { + execute_lsp_command(editor, command.clone()); } } }, @@ -3293,6 +3300,26 @@ pub fn code_action(cx: &mut Context) { ) } +pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) { + let (_view, doc) = current!(editor); + + let language_server = match doc.language_server() { + Some(language_server) => language_server, + None => return, + }; + + // the command is executed on the server and communicated back + // to the client asynchronously using workspace edits + let command_future = language_server.command(cmd); + tokio::spawn(async move { + let res = command_future.await; + + if let Err(e) = res { + log::error!("execute LSP command: {}", e); + } + }); +} + pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> { use lsp::ResourceOp; use std::fs; @@ -3346,7 +3373,7 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> { } } -fn apply_workspace_edit( +pub fn apply_workspace_edit( editor: &mut Editor, offset_encoding: OffsetEncoding, workspace_edit: &lsp::WorkspaceEdit, -- cgit v1.2.3-70-g09d2 From 8a019b423f5d51bcfc107da9cb50aa90eacff19d Mon Sep 17 00:00:00 2001 From: Alexis Mousset Date: Fri, 31 Dec 2021 09:06:54 +0100 Subject: Detect workspace root using language markers (#1370) * Detect workspace root using language markers * Avoid allocating root_markers * Update helix-core/src/lib.rs Co-authored-by: Blaž Hrastnik * Update helix-core/src/lib.rs Co-authored-by: Kirawi <67773714+kirawi@users.noreply.github.com> Co-authored-by: Blaž Hrastnik Co-authored-by: Kirawi <67773714+kirawi@users.noreply.github.com>--- helix-core/src/lib.rs | 32 ++++++++++++++++++++++++++------ helix-lsp/src/client.rs | 6 +++++- helix-lsp/src/lib.rs | 1 + helix-term/src/commands.rs | 3 ++- languages.toml | 2 +- 5 files changed, 35 insertions(+), 9 deletions(-) (limited to 'helix-lsp/src/lib.rs') diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 1c78e7c0..7fd23b97 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -39,8 +39,14 @@ pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option { line.chars().position(|ch| !ch.is_whitespace()) } -/// Find `.git` root. -pub fn find_root(root: Option<&str>) -> Option { +/// Find project root. +/// +/// Order of detection: +/// * Top-most folder containing a root marker in current git repository +/// * Git repostory root if no marker detected +/// * Top-most folder containing a root marker if not git repository detected +/// * Current working directory as fallback +pub fn find_root(root: Option<&str>, root_markers: &[String]) -> Option { let current_dir = std::env::current_dir().expect("unable to determine current directory"); let root = match root { @@ -52,16 +58,30 @@ pub fn find_root(root: Option<&str>) -> Option { current_dir.join(root) } } - None => current_dir, + None => current_dir.clone(), }; + let mut top_marker = None; for ancestor in root.ancestors() { - // TODO: also use defined roots if git isn't found + for marker in root_markers { + if ancestor.join(marker).exists() { + top_marker = Some(ancestor); + break; + } + } + // don't go higher than repo if ancestor.join(".git").is_dir() { - return Some(ancestor.to_path_buf()); + // Use workspace if detected from marker + return Some(top_marker.unwrap_or(ancestor).to_path_buf()); } } - None + + // In absence of git repo, use workspace if detected + if top_marker.is_some() { + top_marker.map(|a| a.to_path_buf()) + } else { + Some(current_dir) + } } pub fn runtime_dir() -> std::path::PathBuf { diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 43804daa..c80f70b5 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -31,6 +31,7 @@ pub struct Client { pub(crate) capabilities: OnceCell, offset_encoding: OffsetEncoding, config: Option, + root_markers: Vec, } impl Client { @@ -39,6 +40,7 @@ impl Client { cmd: &str, args: &[String], config: Option, + root_markers: Vec, id: usize, ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc)> { let process = Command::new(cmd) @@ -68,6 +70,7 @@ impl Client { capabilities: OnceCell::new(), offset_encoding: OffsetEncoding::Utf8, config, + root_markers, }; Ok((client, server_rx, initialize_notify)) @@ -225,7 +228,8 @@ impl Client { pub(crate) async fn initialize(&self) -> Result { // TODO: delay any requests that are triggered prior to initialize - let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok()); + let root = find_root(None, &self.root_markers) + .and_then(|root| lsp::Url::from_file_path(root).ok()); if self.config.is_some() { log::info!("Using custom LSP config: {}", self.config.as_ref().unwrap()); diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 8fb321bc..1eb1c151 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -326,6 +326,7 @@ impl Registry { &config.command, &config.args, language_config.config.clone(), + language_config.roots.clone(), id, )?; self.incoming.push(UnboundedReceiverStream::new(incoming)); diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6259ecb2..ed728115 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3154,7 +3154,8 @@ fn command_mode(cx: &mut Context) { } fn file_picker(cx: &mut Context) { - let root = find_root(None).unwrap_or_else(|| PathBuf::from("./")); + // We don't specify language markers, root will be the root of the current git repo + let root = find_root(None, &[]).unwrap_or_else(|| PathBuf::from("./")); let picker = ui::file_picker(root, &cx.editor.config); cx.push_layer(Box::new(picker)); } diff --git a/languages.toml b/languages.toml index 3d9bac7b..f088a3aa 100644 --- a/languages.toml +++ b/languages.toml @@ -3,7 +3,7 @@ name = "rust" scope = "source.rust" injection-regex = "rust" file-types = ["rs"] -roots = [] +roots = ["Cargo.toml", "Cargo.lock"] auto-format = true comment-token = "//" language-server = { command = "rust-analyzer" } -- cgit v1.2.3-70-g09d2 From ea095ca5fb5d0227ba778f12d128c978576ae55b Mon Sep 17 00:00:00 2001 From: Kirawi Date: Sun, 2 Jan 2022 21:26:17 -0500 Subject: Optimize lsp_pos_to_pos (#1423) lines().count() is slow compared to len_lines()--- helix-lsp/src/lib.rs | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) (limited to 'helix-lsp/src/lib.rs') diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 1eb1c151..109546d0 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -66,39 +66,26 @@ pub mod util { pos: lsp::Position, offset_encoding: OffsetEncoding, ) -> Option { - let max_line = doc.lines().count().saturating_sub(1); let pos_line = pos.line as usize; - let pos_line = if pos_line > max_line { + if pos_line > doc.len_lines() - 1 { return None; - } else { - pos_line - }; + } + match offset_encoding { OffsetEncoding::Utf8 => { - let max_char = doc - .line_to_char(max_line) - .checked_add(doc.line(max_line).len_chars())?; let line = doc.line_to_char(pos_line); let pos = line.checked_add(pos.character as usize)?; - if pos <= max_char { + if pos <= doc.len_chars() { Some(pos) } else { None } } OffsetEncoding::Utf16 => { - let max_char = doc - .line_to_char(max_line) - .checked_add(doc.line(max_line).len_chars())?; - let max_cu = doc.char_to_utf16_cu(max_char); let line = doc.line_to_char(pos_line); let line_start = doc.char_to_utf16_cu(line); let pos = line_start.checked_add(pos.character as usize)?; - if pos <= max_cu { - Some(doc.utf16_cu_to_char(pos)) - } else { - None - } + doc.try_utf16_cu_to_char(pos).ok() } } } -- cgit v1.2.3-70-g09d2