pull/13617/merge
Xubai Wang 2025-06-16 16:18:53 +02:00 committed by GitHub
commit 675fe454c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 59 additions and 16 deletions

View File

@ -142,7 +142,7 @@ These are the available options for a language server.
| `config` | Language server initialization options | | `config` | Language server initialization options |
| `timeout` | The maximum time a request to the language server may take, in seconds. Defaults to `20` | | `timeout` | The maximum time a request to the language server may take, in seconds. Defaults to `20` |
| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` | | `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
| `required-root-patterns` | A list of `glob` patterns to look for in the working directory. The language server is started if at least one of them is found. | | `required-root-patterns` | A list of `glob` patterns to look for in the working directory. The language server is started if at least one of them is found. Patterns can be negated with `!` prefix (`\\!` for escape). |
A `format` sub-table within `config` can be used to pass extra formatting options to A `format` sub-table within `config` can be used to pass extra formatting options to
[Document Formatting Requests](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting). [Document Formatting Requests](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting).

View File

@ -75,6 +75,7 @@
lld lld
cargo-flamegraph cargo-flamegraph
rust-bin.nightly.latest.rust-analyzer rust-bin.nightly.latest.rust-analyzer
rust-bin.nightly.latest.clippy
] ]
++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) cargo-tarpaulin) ++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) cargo-tarpaulin)
++ (lib.optional stdenv.isLinux lldb) ++ (lib.optional stdenv.isLinux lldb)

View File

@ -374,20 +374,41 @@ where
serializer.end() serializer.end()
} }
fn deserialize_required_root_patterns<'de, D>(deserializer: D) -> Result<Option<GlobSet>, D::Error> /// Deserialize required-root-patterns with globset.
///
/// This function returns a tuple of positive globset and negative globset.
fn deserialize_required_root_patterns<'de, D>(
deserializer: D,
) -> Result<(GlobSet, GlobSet), D::Error>
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
let patterns = Vec::<String>::deserialize(deserializer)?; let patterns = Vec::<String>::deserialize(deserializer)?;
if patterns.is_empty() {
return Ok(None); let mut positive_builder = globset::GlobSetBuilder::new();
} let mut negative_builder = globset::GlobSetBuilder::new();
let mut builder = globset::GlobSetBuilder::new();
for pattern in patterns { for pattern in patterns {
let glob = globset::Glob::new(&pattern).map_err(serde::de::Error::custom)?; // negative pattern
builder.add(glob); if let Some(stripped) = pattern.strip_prefix("!") {
let glob = globset::Glob::new(stripped).map_err(serde::de::Error::custom)?;
negative_builder.add(glob);
}
// escaped pattern
else if let Some(_stripped) = pattern.strip_prefix("\\!") {
let glob = globset::Glob::new(&pattern[1..]).map_err(serde::de::Error::custom)?;
positive_builder.add(glob);
}
// rest is positive
else {
let glob = globset::Glob::new(&pattern).map_err(serde::de::Error::custom)?;
positive_builder.add(glob);
}
} }
builder.build().map(Some).map_err(serde::de::Error::custom)
let positive_globset = positive_builder.build().map_err(serde::de::Error::custom)?;
let negative_globset = negative_builder.build().map_err(serde::de::Error::custom)?;
Ok((positive_globset, negative_globset))
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -403,12 +424,13 @@ 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,
/// Positive and negative patterns.
#[serde( #[serde(
default, default,
skip_serializing, skip_serializing,
deserialize_with = "deserialize_required_root_patterns" deserialize_with = "deserialize_required_root_patterns"
)] )]
pub required_root_patterns: Option<GlobSet>, pub required_root_patterns: (GlobSet, GlobSet),
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]

View File

@ -888,16 +888,36 @@ fn start_client(
let root_path = root.clone().unwrap_or_else(|| workspace.clone()); 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 root_uri = root.and_then(|root| lsp::Url::from_file_path(root).ok());
if let Some(globset) = &ls_config.required_root_patterns { // match negative and positive root patterns
if !root_path let (positive_globset, negative_globset) = &ls_config.required_root_patterns;
.read_dir()? let file_names: Vec<_> = root_path
.flatten() .read_dir()?
.map(|entry| entry.file_name()) .flatten()
.any(|entry| globset.is_match(entry)) .map(|entry| entry.file_name())
.collect();
// negative
if !negative_globset.is_empty() {
if let Some(occurence) = file_names
.iter()
.find(|name| negative_globset.is_match(name))
{ {
log::debug!(
"negative root pattern {} met for {}",
occurence.to_string_lossy(),
name
);
return Err(StartupError::NoRequiredRootFound); return Err(StartupError::NoRequiredRootFound);
} }
} }
// positive
if !positive_globset.is_empty()
&& !file_names
.iter()
.any(|name| positive_globset.is_match(name))
{
log::debug!("none of positive root patterns found for {}", name);
return Err(StartupError::NoRequiredRootFound);
}
let (client, incoming, initialize_notify) = Client::start( let (client, incoming, initialize_notify) = Client::start(
&ls_config.command, &ls_config.command,