diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index dbdd885b2..8b14b0b87 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -31,7 +31,9 @@ pub struct Client { pub(crate) capabilities: OnceCell, offset_encoding: OffsetEncoding, config: Option, - root_markers: Vec, + root_path: Option, + root_uri: Option, + workspace_folders: Vec, } impl Client { @@ -40,7 +42,7 @@ impl Client { cmd: &str, args: &[String], config: Option, - root_markers: Vec, + root_markers: &[String], id: usize, ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc)> { // Resolve path to the binary @@ -65,6 +67,27 @@ impl Client { let (server_rx, server_tx, initialize_notify) = Transport::start(reader, writer, stderr, id); + let root_path = find_root(None, root_markers); + + let root_uri = root_path + .clone() + .and_then(|root| lsp::Url::from_file_path(root).ok()); + + // TODO: support multiple workspace folders + let workspace_folders = root_uri + .clone() + .map(|root| { + vec![lsp::WorkspaceFolder { + name: root + .path_segments() + .and_then(|segments| segments.last()) + .map(|basename| basename.to_string()) + .unwrap_or_default(), + uri: root, + }] + }) + .unwrap_or_default(); + let client = Self { id, _process: process, @@ -73,7 +96,10 @@ impl Client { capabilities: OnceCell::new(), offset_encoding: OffsetEncoding::Utf8, config, - root_markers, + + root_path, + root_uri, + workspace_folders, }; Ok((client, server_rx, initialize_notify)) @@ -117,6 +143,10 @@ impl Client { self.config.as_ref() } + pub fn workspace_folders(&self) -> &[lsp::WorkspaceFolder] { + &self.workspace_folders + } + /// Execute a RPC request on the language server. async fn request(&self, params: R::Params) -> Result where @@ -234,13 +264,6 @@ impl Client { // ------------------------------------------------------------------------------------------- pub(crate) async fn initialize(&self) -> Result { - // TODO: delay any requests that are triggered prior to initialize - let root_path = find_root(None, &self.root_markers); - - let root_uri = root_path - .clone() - .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()); } @@ -248,10 +271,14 @@ impl Client { #[allow(deprecated)] let params = lsp::InitializeParams { process_id: Some(std::process::id()), + workspace_folders: Some(self.workspace_folders.clone()), // root_path is obsolete, but some clients like pyright still use it so we specify both. // clients will prefer _uri if possible - root_path: root_path.and_then(|path| path.to_str().map(|path| path.to_owned())), - root_uri, + root_path: self + .root_path + .clone() + .and_then(|path| path.to_str().map(|path| path.to_owned())), + root_uri: self.root_uri.clone(), initialization_options: self.config.clone(), capabilities: lsp::ClientCapabilities { workspace: Some(lsp::WorkspaceClientCapabilities { @@ -259,6 +286,7 @@ impl Client { did_change_configuration: Some(lsp::DynamicRegistrationClientCapabilities { dynamic_registration: Some(false), }), + workspace_folders: Some(true), ..Default::default() }), text_document: Some(lsp::TextDocumentClientCapabilities { @@ -314,7 +342,6 @@ impl Client { ..Default::default() }, trace: None, - workspace_folders: None, client_info: None, locale: None, // TODO }; diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 389dfb4dd..767481367 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -191,6 +191,7 @@ pub mod util { pub enum MethodCall { WorkDoneProgressCreate(lsp::WorkDoneProgressCreateParams), ApplyWorkspaceEdit(lsp::ApplyWorkspaceEditParams), + WorkspaceFolders, WorkspaceConfiguration(lsp::ConfigurationParams), } @@ -210,6 +211,7 @@ impl MethodCall { .expect("Failed to parse ApplyWorkspaceEdit params"); Self::ApplyWorkspaceEdit(params) } + lsp::request::WorkspaceFoldersRequest::METHOD => Self::WorkspaceFolders, lsp::request::WorkspaceConfiguration::METHOD => { let params: lsp::ConfigurationParams = params .parse() @@ -320,7 +322,7 @@ impl Registry { &config.command, &config.args, language_config.config.clone(), - language_config.roots.clone(), + &language_config.roots, id, )?; self.incoming.push(UnboundedReceiverStream::new(incoming)); diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 09b1ff619..ddf9e8d6d 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -601,7 +601,7 @@ impl Application { } }; - match call { + let reply = match call { MethodCall::WorkDoneProgressCreate(params) => { self.lsp_progress.create(server_id, params.token); @@ -613,16 +613,8 @@ 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))); + Ok(serde_json::Value::Null) } MethodCall::ApplyWorkspaceEdit(params) => { apply_workspace_edit( @@ -631,33 +623,19 @@ impl Application { ¶ms.edit, ); + Ok(json!(lsp::ApplyWorkspaceEditResponse { + applied: true, + failure_reason: None, + failed_change: None, + })) + } + MethodCall::WorkspaceFolders => { 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; - } - }; + self.editor.language_servers.get_by_id(server_id).unwrap(); - tokio::spawn(language_server.reply( - id, - Ok(json!(lsp::ApplyWorkspaceEditResponse { - applied: true, - failure_reason: None, - failed_change: None, - })), - )); + Ok(json!(language_server.workspace_folders())) } MethodCall::WorkspaceConfiguration(params) => { - 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 result: Vec<_> = params .items .iter() @@ -668,7 +646,12 @@ impl Application { let doc = self.editor.document_by_path(path)?; doc.language_config()?.config.as_ref()? } - None => language_server.config()?, + None => self + .editor + .language_servers + .get_by_id(server_id) + .unwrap() + .config()?, }; if let Some(section) = item.section.as_ref() { for part in section.split('.') { @@ -678,9 +661,19 @@ impl Application { Some(config) }) .collect(); - tokio::spawn(language_server.reply(id, Ok(json!(result)))); + Ok(json!(result)) } - } + }; + + 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, reply)); } Call::Invalid { id } => log::error!("LSP invalid method call id={:?}", id), }