diff options
Diffstat (limited to 'helix-lsp')
-rw-r--r-- | helix-lsp/src/client.rs | 12 | ||||
-rw-r--r-- | helix-lsp/src/lib.rs | 56 |
2 files changed, 61 insertions, 7 deletions
diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index f93e5826..34e4c346 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -1,22 +1,22 @@ use crate::{ - jsonrpc, + find_root, jsonrpc, transport::{Payload, Transport}, Call, Error, OffsetEncoding, Result, }; -use helix_core::{find_root, ChangeSet, Rope}; +use helix_core::{ChangeSet, Rope}; use helix_loader::{self, VERSION_AND_GIT_HASH}; use lsp::PositionEncodingKind; use lsp_types as lsp; use serde::Deserialize; use serde_json::Value; -use std::collections::HashMap; use std::future::Future; use std::process::Stdio; use std::sync::{ atomic::{AtomicU64, Ordering}, Arc, }; +use std::{collections::HashMap, path::PathBuf}; use tokio::{ io::{BufReader, BufWriter}, process::{Child, Command}, @@ -49,6 +49,7 @@ impl Client { config: Option<Value>, server_environment: HashMap<String, String>, root_markers: &[String], + manual_roots: &[PathBuf], id: usize, req_timeout: u64, doc_path: Option<&std::path::PathBuf>, @@ -77,8 +78,11 @@ impl Client { Transport::start(reader, writer, stderr, id); let root_path = find_root( - doc_path.and_then(|x| x.parent().and_then(|x| x.to_str())), + doc_path + .and_then(|x| x.parent().and_then(|x| x.to_str())) + .unwrap_or("."), root_markers, + manual_roots, ); let root_uri = lsp::Url::from_file_path(root_path.clone()).ok(); diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 5609a624..e4b00946 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -10,11 +10,15 @@ pub use lsp::{Position, Url}; pub use lsp_types as lsp; use futures_util::stream::select_all::SelectAll; -use helix_core::syntax::{LanguageConfiguration, LanguageServerConfiguration}; +use helix_core::{ + find_workspace, + syntax::{LanguageConfiguration, LanguageServerConfiguration}, +}; use tokio::sync::mpsc::UnboundedReceiver; use std::{ collections::{hash_map::Entry, HashMap}, + path::PathBuf, sync::{ atomic::{AtomicUsize, Ordering}, Arc, @@ -641,6 +645,7 @@ impl Registry { &mut self, language_config: &LanguageConfiguration, doc_path: Option<&std::path::PathBuf>, + root_dirs: &[PathBuf], ) -> Result<Option<Arc<Client>>> { let config = match &language_config.language_server { Some(config) => config, @@ -656,7 +661,7 @@ impl Registry { let id = self.counter.fetch_add(1, Ordering::Relaxed); let NewClientResult(client, incoming) = - start_client(id, language_config, config, doc_path)?; + start_client(id, language_config, config, doc_path, root_dirs)?; self.incoming.push(UnboundedReceiverStream::new(incoming)); let (_, old_client) = entry.insert((id, client.clone())); @@ -684,6 +689,7 @@ impl Registry { &mut self, language_config: &LanguageConfiguration, doc_path: Option<&std::path::PathBuf>, + root_dirs: &[PathBuf], ) -> Result<Option<Arc<Client>>> { let config = match &language_config.language_server { Some(config) => config, @@ -697,7 +703,7 @@ impl Registry { let id = self.counter.fetch_add(1, Ordering::Relaxed); let NewClientResult(client, incoming) = - start_client(id, language_config, config, doc_path)?; + start_client(id, language_config, config, doc_path, root_dirs)?; self.incoming.push(UnboundedReceiverStream::new(incoming)); entry.insert((id, client.clone())); @@ -798,6 +804,7 @@ fn start_client( config: &LanguageConfiguration, ls_config: &LanguageServerConfiguration, doc_path: Option<&std::path::PathBuf>, + root_dirs: &[PathBuf], ) -> Result<NewClientResult> { let (client, incoming, initialize_notify) = Client::start( &ls_config.command, @@ -805,6 +812,7 @@ fn start_client( config.config.clone(), ls_config.environment.clone(), &config.roots, + config.workspace_lsp_roots.as_deref().unwrap_or(root_dirs), id, ls_config.timeout, doc_path, @@ -842,6 +850,48 @@ fn start_client( Ok(NewClientResult(client, incoming)) } +/// Find an LSP root of a file using the following mechansim: +/// * start at `file` (either an absolute path or relative to CWD) +/// * find the top most directory containing a root_marker +/// * inside the current workspace +/// * stop the search at the first root_dir that contains `file` or the workspace (obtained from `helix_core::find_workspace`) +/// * root_dirs only apply inside the workspace. For files outside of the workspace they are ignored +/// * outside the current workspace: keep searching to the top of the file hiearchy +pub fn find_root(file: &str, root_markers: &[String], root_dirs: &[PathBuf]) -> PathBuf { + let file = std::path::Path::new(file); + let workspace = find_workspace(); + let file = if file.is_absolute() { + file.to_path_buf() + } else { + let current_dir = std::env::current_dir().expect("unable to determine current directory"); + current_dir.join(file) + }; + + let inside_workspace = file.strip_prefix(&workspace).is_ok(); + + let mut top_marker = None; + for ancestor in file.ancestors() { + if root_markers + .iter() + .any(|marker| ancestor.join(marker).exists()) + { + top_marker = Some(ancestor); + } + + if inside_workspace + && (ancestor == workspace + || root_dirs + .iter() + .any(|root_dir| root_dir == ancestor.strip_prefix(&workspace).unwrap())) + { + return top_marker.unwrap_or(ancestor).to_owned(); + } + } + + // If no root was found use the workspace as a fallback + workspace +} + #[cfg(test)] mod tests { use super::{lsp, util::*, OffsetEncoding}; |