Added required-root-patterns for situational lsp activation using globbing

pull/8696/head
ontley 2023-11-02 11:09:47 +01:00
parent a069b92897
commit dfcd842726
3 changed files with 77 additions and 35 deletions

View File

@ -371,6 +371,9 @@ pub struct LanguageServerConfiguration {
pub config: Option<serde_json::Value>, pub config: Option<serde_json::Value>,
#[serde(default = "default_timeout")] #[serde(default = "default_timeout")]
pub timeout: u64, pub timeout: u64,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub required_root_patterns: Vec<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]

View File

@ -174,12 +174,11 @@ impl Client {
args: &[String], args: &[String],
config: Option<Value>, config: Option<Value>,
server_environment: HashMap<String, String>, server_environment: HashMap<String, String>,
root_markers: &[String], root_path: PathBuf,
manual_roots: &[PathBuf], root_uri: Option<lsp::Url>,
id: usize, id: usize,
name: String, name: String,
req_timeout: u64, req_timeout: u64,
doc_path: Option<&std::path::PathBuf>,
) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> { ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
// Resolve path to the binary // Resolve path to the binary
let cmd = which::which(cmd).map_err(|err| anyhow::anyhow!(err))?; let cmd = which::which(cmd).map_err(|err| anyhow::anyhow!(err))?;
@ -203,22 +202,6 @@ impl Client {
let (server_rx, server_tx, initialize_notify) = let (server_rx, server_tx, initialize_notify) =
Transport::start(reader, writer, stderr, id, name.clone()); Transport::start(reader, writer, stderr, id, name.clone());
let (workspace, workspace_is_cwd) = find_workspace();
let workspace = path::get_normalized_path(&workspace);
let root = find_lsp_workspace(
doc_path
.and_then(|x| x.parent().and_then(|x| x.to_str()))
.unwrap_or("."),
root_markers,
manual_roots,
&workspace,
workspace_is_cwd,
);
// `root_uri` and `workspace_folder` can be empty in case there is no workspace
// `root_url` can not, use `workspace` as a fallback
let root_path = root.clone().unwrap_or_else(|| workspace.clone());
let root_uri = root.and_then(|root| lsp::Url::from_file_path(root).ok());
let workspace_folders = root_uri let workspace_folders = root_uri
.clone() .clone()

View File

@ -672,7 +672,7 @@ impl Registry {
doc_path: Option<&std::path::PathBuf>, doc_path: Option<&std::path::PathBuf>,
root_dirs: &[PathBuf], root_dirs: &[PathBuf],
enable_snippets: bool, enable_snippets: bool,
) -> Result<Arc<Client>> { ) -> Result<Option<Arc<Client>>> {
let config = self let config = self
.syn_loader .syn_loader
.language_server_configs() .language_server_configs()
@ -680,7 +680,7 @@ impl Registry {
.ok_or_else(|| anyhow::anyhow!("Language server '{name}' not defined"))?; .ok_or_else(|| anyhow::anyhow!("Language server '{name}' not defined"))?;
let id = self.counter; let id = self.counter;
self.counter += 1; self.counter += 1;
let NewClient(client, incoming) = start_client( if let Some(NewClient(client, incoming)) = start_client(
id, id,
name, name,
ls_config, ls_config,
@ -688,9 +688,12 @@ impl Registry {
doc_path, doc_path,
root_dirs, root_dirs,
enable_snippets, enable_snippets,
)?; )? {
self.incoming.push(UnboundedReceiverStream::new(incoming)); self.incoming.push(UnboundedReceiverStream::new(incoming));
Ok(client) Ok(Some(client))
} else {
Ok(None)
}
} }
/// If this method is called, all documents that have a reference to language servers used by the language config have to refresh their language servers, /// If this method is called, all documents that have a reference to language servers used by the language config have to refresh their language servers,
@ -715,8 +718,8 @@ impl Registry {
root_dirs, root_dirs,
enable_snippets, enable_snippets,
) { ) {
Ok(client) => client, Ok(client) => client?,
error => return Some(error), Err(error) => return Some(Err(error)),
}; };
let old_clients = self let old_clients = self
.inner .inner
@ -756,13 +759,13 @@ impl Registry {
root_dirs: &'a [PathBuf], root_dirs: &'a [PathBuf],
enable_snippets: bool, enable_snippets: bool,
) -> impl Iterator<Item = (LanguageServerName, Result<Arc<Client>>)> + 'a { ) -> impl Iterator<Item = (LanguageServerName, Result<Arc<Client>>)> + 'a {
language_config.language_servers.iter().map( language_config.language_servers.iter().filter_map(
move |LanguageServerFeatures { name, .. }| { move |LanguageServerFeatures { name, .. }| {
if let Some(clients) = self.inner.get(name) { if let Some(clients) = self.inner.get(name) {
if let Some((_, client)) = clients.iter().enumerate().find(|(i, client)| { if let Some((_, client)) = clients.iter().enumerate().find(|(i, client)| {
client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0) client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
}) { }) {
return (name.to_owned(), Ok(client.clone())); return Some((name.to_owned(), Ok(client.clone())));
} }
} }
match self.start_client( match self.start_client(
@ -773,13 +776,14 @@ impl Registry {
enable_snippets, enable_snippets,
) { ) {
Ok(client) => { Ok(client) => {
let client = client?;
self.inner self.inner
.entry(name.to_owned()) .entry(name.to_owned())
.or_default() .or_default()
.push(client.clone()); .push(client.clone());
(name.clone(), Ok(client)) Some((name.clone(), Ok(client)))
} }
Err(err) => (name.to_owned(), Err(err)), Err(err) => Some((name.to_owned(), Err(err))),
} }
}, },
) )
@ -880,18 +884,70 @@ fn start_client(
doc_path: Option<&std::path::PathBuf>, doc_path: Option<&std::path::PathBuf>,
root_dirs: &[PathBuf], root_dirs: &[PathBuf],
enable_snippets: bool, enable_snippets: bool,
) -> Result<NewClient> { ) -> Result<Option<NewClient>> {
let (workspace, workspace_is_cwd) = helix_loader::find_workspace();
let workspace = path::get_normalized_path(&workspace);
let root = find_lsp_workspace(
doc_path
.and_then(|x| x.parent().and_then(|x| x.to_str()))
.unwrap_or("."),
&config.roots,
config.workspace_lsp_roots.as_deref().unwrap_or(root_dirs),
&workspace,
workspace_is_cwd,
);
// `root_uri` and `workspace_folder` can be empty in case there is no workspace
// `root_url` can not, use `workspace` as a fallback
let root_path = root.clone().unwrap_or_else(|| workspace.clone());
let root_uri = root.and_then(|root| lsp::Url::from_file_path(root).ok());
let mut globset = globset::GlobSetBuilder::new();
let required_root_patterns = &ls_config.required_root_patterns;
if !required_root_patterns.is_empty() {
for required_root_pattern in required_root_patterns {
match globset::Glob::new(required_root_pattern) {
Ok(glob) => {
globset.add(glob);
}
Err(err) => {
log::warn!(
"Failed to build glob '{}' for language server '{}'",
required_root_pattern,
name
);
log::warn!("{}", err);
}
};
}
match globset.build() {
Ok(glob) => {
if !root_path
.read_dir()?
.filter_map(|e| e.ok())
.map(|e| e.file_name())
.any(|filename| glob.is_match(filename))
{
return Ok(None);
}
}
Err(err) => {
log::warn!("Failed to build globset for language server {name}");
log::warn!("{}", err);
}
};
}
let (client, incoming, initialize_notify) = Client::start( let (client, incoming, initialize_notify) = Client::start(
&ls_config.command, &ls_config.command,
&ls_config.args, &ls_config.args,
ls_config.config.clone(), ls_config.config.clone(),
ls_config.environment.clone(), ls_config.environment.clone(),
&config.roots, root_path,
config.workspace_lsp_roots.as_deref().unwrap_or(root_dirs), root_uri,
id, id,
name, name,
ls_config.timeout, ls_config.timeout,
doc_path,
)?; )?;
let client = Arc::new(client); let client = Arc::new(client);
@ -923,7 +979,7 @@ fn start_client(
initialize_notify.notify_one(); initialize_notify.notify_one();
}); });
Ok(NewClient(client, incoming)) Ok(Some(NewClient(client, incoming)))
} }
/// Find an LSP workspace of a file using the following mechanism: /// Find an LSP workspace of a file using the following mechanism: