@@ -133,6 +133,7 @@ impl FormatTrigger {
}
pub struct LocalLspStore {
+ worktree_store: Model<WorktreeStore>,
http_client: Arc<dyn HttpClient>,
environment: Model<ProjectEnvironment>,
fs: Arc<dyn Fs>,
@@ -1562,6 +1563,247 @@ impl LocalLspStore {
failure_reason: None,
})
}
+ fn rebuild_watched_paths_inner<'a>(
+ &'a self,
+ language_server_id: LanguageServerId,
+ watchers: impl Iterator<Item = &'a FileSystemWatcher>,
+ cx: &mut ModelContext<LspStore>,
+ ) -> LanguageServerWatchedPathsBuilder {
+ let worktrees = self
+ .worktree_store
+ .read(cx)
+ .worktrees()
+ .filter_map(|worktree| {
+ self.language_servers_for_worktree(worktree.read(cx).id())
+ .find(|server| server.server_id() == language_server_id)
+ .map(|_| worktree)
+ })
+ .collect::<Vec<_>>();
+
+ let mut worktree_globs = HashMap::default();
+ let mut abs_globs = HashMap::default();
+ log::trace!(
+ "Processing new watcher paths for language server with id {}",
+ language_server_id
+ );
+
+ enum PathToWatch {
+ Worktree {
+ literal_prefix: Arc<Path>,
+ pattern: String,
+ },
+ Absolute {
+ path: Arc<Path>,
+ pattern: String,
+ },
+ }
+ for watcher in watchers {
+ let mut found_host = false;
+ for worktree in &worktrees {
+ let glob_is_inside_worktree = worktree.update(cx, |tree, _| {
+ if let Some(worktree_root_path) = tree.abs_path().to_str() {
+ let path_to_watch = match &watcher.glob_pattern {
+ lsp::GlobPattern::String(s) => {
+ match s.strip_prefix(worktree_root_path) {
+ Some(relative) => {
+ let pattern = relative
+ .strip_prefix(std::path::MAIN_SEPARATOR)
+ .unwrap_or(relative)
+ .to_owned();
+ let literal_prefix = glob_literal_prefix(&pattern);
+
+ let literal_prefix = Arc::from(PathBuf::from(
+ literal_prefix
+ .strip_prefix(std::path::MAIN_SEPARATOR)
+ .unwrap_or(literal_prefix),
+ ));
+ PathToWatch::Worktree {
+ literal_prefix,
+ pattern,
+ }
+ }
+ None => {
+ let path = glob_literal_prefix(s);
+ let glob = &s[path.len()..];
+ let pattern = glob
+ .strip_prefix(std::path::MAIN_SEPARATOR)
+ .unwrap_or(glob)
+ .to_owned();
+ let path = if Path::new(path).components().next().is_none()
+ {
+ Arc::from(Path::new(worktree_root_path))
+ } else {
+ PathBuf::from(path).into()
+ };
+
+ PathToWatch::Absolute { path, pattern }
+ }
+ }
+ }
+ lsp::GlobPattern::Relative(rp) => {
+ let Ok(mut base_uri) = match &rp.base_uri {
+ lsp::OneOf::Left(workspace_folder) => &workspace_folder.uri,
+ lsp::OneOf::Right(base_uri) => base_uri,
+ }
+ .to_file_path() else {
+ return false;
+ };
+
+ match base_uri.strip_prefix(worktree_root_path) {
+ Ok(relative) => {
+ let mut literal_prefix = relative.to_owned();
+ literal_prefix.push(glob_literal_prefix(&rp.pattern));
+
+ PathToWatch::Worktree {
+ literal_prefix: literal_prefix.into(),
+ pattern: rp.pattern.clone(),
+ }
+ }
+ Err(_) => {
+ let path = glob_literal_prefix(&rp.pattern);
+ let glob = &rp.pattern[path.len()..];
+ let pattern = glob
+ .strip_prefix(std::path::MAIN_SEPARATOR)
+ .unwrap_or(glob)
+ .to_owned();
+ base_uri.push(path);
+
+ let path = if base_uri.components().next().is_none() {
+ Arc::from(Path::new("/"))
+ } else {
+ base_uri.into()
+ };
+ PathToWatch::Absolute { path, pattern }
+ }
+ }
+ }
+ };
+ match path_to_watch {
+ PathToWatch::Worktree {
+ literal_prefix,
+ pattern,
+ } => {
+ if let Some((tree, glob)) =
+ tree.as_local_mut().zip(Glob::new(&pattern).log_err())
+ {
+ tree.add_path_prefix_to_scan(literal_prefix);
+ worktree_globs
+ .entry(tree.id())
+ .or_insert_with(GlobSetBuilder::new)
+ .add(glob);
+ } else {
+ return false;
+ }
+ }
+ PathToWatch::Absolute { path, pattern } => {
+ if let Some(glob) = Glob::new(&pattern).log_err() {
+ abs_globs
+ .entry(path)
+ .or_insert_with(GlobSetBuilder::new)
+ .add(glob);
+ }
+ }
+ }
+ return true;
+ }
+ false
+ });
+ if glob_is_inside_worktree {
+ log::trace!(
+ "Watcher pattern `{}` has been attached to the worktree at `{}`",
+ serde_json::to_string(&watcher.glob_pattern).unwrap(),
+ worktree.read(cx).abs_path().display()
+ );
+ found_host = true;
+ }
+ }
+ if !found_host {
+ log::error!(
+ "Watcher pattern `{}` has not been attached to any worktree or absolute path",
+ serde_json::to_string(&watcher.glob_pattern).unwrap()
+ )
+ }
+ }
+
+ let mut watch_builder = LanguageServerWatchedPathsBuilder::default();
+ for (worktree_id, builder) in worktree_globs {
+ if let Ok(globset) = builder.build() {
+ watch_builder.watch_worktree(worktree_id, globset);
+ }
+ }
+ for (abs_path, builder) in abs_globs {
+ if let Ok(globset) = builder.build() {
+ watch_builder.watch_abs_path(abs_path, globset);
+ }
+ }
+ watch_builder
+ }
+
+ fn rebuild_watched_paths(
+ &mut self,
+ language_server_id: LanguageServerId,
+ cx: &mut ModelContext<LspStore>,
+ ) {
+ let Some(watchers) = self
+ .language_server_watcher_registrations
+ .get(&language_server_id)
+ else {
+ return;
+ };
+
+ let watch_builder =
+ self.rebuild_watched_paths_inner(language_server_id, watchers.values().flatten(), cx);
+ let watcher = watch_builder.build(self.fs.clone(), language_server_id, cx);
+ self.language_server_watched_paths
+ .insert(language_server_id, watcher);
+
+ cx.notify();
+ }
+
+ fn on_lsp_did_change_watched_files(
+ &mut self,
+ language_server_id: LanguageServerId,
+ registration_id: &str,
+ params: DidChangeWatchedFilesRegistrationOptions,
+ cx: &mut ModelContext<LspStore>,
+ ) {
+ let registrations = self
+ .language_server_watcher_registrations
+ .entry(language_server_id)
+ .or_default();
+
+ registrations.insert(registration_id.to_string(), params.watchers);
+
+ self.rebuild_watched_paths(language_server_id, cx);
+ }
+
+ fn on_lsp_unregister_did_change_watched_files(
+ &mut self,
+ language_server_id: LanguageServerId,
+ registration_id: &str,
+ cx: &mut ModelContext<LspStore>,
+ ) {
+ let registrations = self
+ .language_server_watcher_registrations
+ .entry(language_server_id)
+ .or_default();
+
+ if registrations.remove(registration_id).is_some() {
+ log::info!(
+ "language server {}: unregistered workspace/DidChangeWatchedFiles capability with id {}",
+ language_server_id,
+ registration_id
+ );
+ } else {
+ log::warn!(
+ "language server {}: failed to unregister workspace/DidChangeWatchedFiles capability with id {}. not registered.",
+ language_server_id,
+ registration_id
+ );
+ }
+
+ self.rebuild_watched_paths(language_server_id, cx);
+ }
}
#[derive(Debug)]
@@ -1577,7 +1819,7 @@ pub struct RemoteLspStore {
}
#[allow(clippy::large_enum_variant)]
-pub enum LspStoreMode {
+pub(crate) enum LspStoreMode {
Local(LocalLspStore), // ssh host and collab host
Remote(RemoteLspStore), // collab guest
}
@@ -1777,6 +2019,7 @@ impl LspStore {
};
Self {
mode: LspStoreMode::Local(LocalLspStore {
+ worktree_store: worktree_store.clone(),
supplementary_language_servers: Default::default(),
languages: languages.clone(),
language_server_ids: Default::default(),
@@ -2214,8 +2457,6 @@ impl LspStore {
<R::LspRequest as lsp::request::Request>::Result: Send,
<R::LspRequest as lsp::request::Request>::Params: Send,
{
- let buffer = buffer_handle.read(cx);
-
if let Some((upstream_client, upstream_project_id)) = self.upstream_client() {
return self.send_lsp_proto_request(
buffer_handle,
@@ -2225,7 +2466,7 @@ impl LspStore {
cx,
);
}
-
+ let buffer = buffer_handle.read(cx);
let language_server = match server {
LanguageServerToQuery::Primary => {
match self
@@ -5027,208 +5268,6 @@ impl LspStore {
});
}
- fn rebuild_watched_paths(
- &mut self,
- language_server_id: LanguageServerId,
- cx: &mut ModelContext<Self>,
- ) {
- let Some(watchers) = self.as_local().and_then(|local| {
- local
- .language_server_watcher_registrations
- .get(&language_server_id)
- }) else {
- return;
- };
-
- let watch_builder =
- self.rebuild_watched_paths_inner(language_server_id, watchers.values().flatten(), cx);
- let Some(local_lsp_store) = self.as_local_mut() else {
- return;
- };
- let watcher = watch_builder.build(local_lsp_store.fs.clone(), language_server_id, cx);
- local_lsp_store
- .language_server_watched_paths
- .insert(language_server_id, watcher);
-
- cx.notify();
- }
- fn rebuild_watched_paths_inner<'a>(
- &'a self,
- language_server_id: LanguageServerId,
- watchers: impl Iterator<Item = &'a FileSystemWatcher>,
- cx: &mut ModelContext<Self>,
- ) -> LanguageServerWatchedPathsBuilder {
- let worktrees = self
- .worktree_store
- .read(cx)
- .worktrees()
- .filter_map(|worktree| {
- self.as_local()?
- .language_servers_for_worktree(worktree.read(cx).id())
- .find(|server| server.server_id() == language_server_id)
- .map(|_| worktree)
- })
- .collect::<Vec<_>>();
-
- let mut worktree_globs = HashMap::default();
- let mut abs_globs = HashMap::default();
- log::trace!(
- "Processing new watcher paths for language server with id {}",
- language_server_id
- );
-
- enum PathToWatch {
- Worktree {
- literal_prefix: Arc<Path>,
- pattern: String,
- },
- Absolute {
- path: Arc<Path>,
- pattern: String,
- },
- }
- for watcher in watchers {
- let mut found_host = false;
- for worktree in &worktrees {
- let glob_is_inside_worktree = worktree.update(cx, |tree, _| {
- if let Some(worktree_root_path) = tree.abs_path().to_str() {
- let path_to_watch = match &watcher.glob_pattern {
- lsp::GlobPattern::String(s) => {
- match s.strip_prefix(worktree_root_path) {
- Some(relative) => {
- let pattern = relative
- .strip_prefix(std::path::MAIN_SEPARATOR)
- .unwrap_or(relative)
- .to_owned();
- let literal_prefix = glob_literal_prefix(&pattern);
-
- let literal_prefix = Arc::from(PathBuf::from(
- literal_prefix
- .strip_prefix(std::path::MAIN_SEPARATOR)
- .unwrap_or(literal_prefix),
- ));
- PathToWatch::Worktree {
- literal_prefix,
- pattern,
- }
- }
- None => {
- let path = glob_literal_prefix(s);
- let glob = &s[path.len()..];
- let pattern = glob
- .strip_prefix(std::path::MAIN_SEPARATOR)
- .unwrap_or(glob)
- .to_owned();
- let path = if Path::new(path).components().next().is_none()
- {
- Arc::from(Path::new(worktree_root_path))
- } else {
- PathBuf::from(path).into()
- };
-
- PathToWatch::Absolute { path, pattern }
- }
- }
- }
- lsp::GlobPattern::Relative(rp) => {
- let Ok(mut base_uri) = match &rp.base_uri {
- lsp::OneOf::Left(workspace_folder) => &workspace_folder.uri,
- lsp::OneOf::Right(base_uri) => base_uri,
- }
- .to_file_path() else {
- return false;
- };
-
- match base_uri.strip_prefix(worktree_root_path) {
- Ok(relative) => {
- let mut literal_prefix = relative.to_owned();
- literal_prefix.push(glob_literal_prefix(&rp.pattern));
-
- PathToWatch::Worktree {
- literal_prefix: literal_prefix.into(),
- pattern: rp.pattern.clone(),
- }
- }
- Err(_) => {
- let path = glob_literal_prefix(&rp.pattern);
- let glob = &rp.pattern[path.len()..];
- let pattern = glob
- .strip_prefix(std::path::MAIN_SEPARATOR)
- .unwrap_or(glob)
- .to_owned();
- base_uri.push(path);
-
- let path = if base_uri.components().next().is_none() {
- Arc::from(Path::new("/"))
- } else {
- base_uri.into()
- };
- PathToWatch::Absolute { path, pattern }
- }
- }
- }
- };
- match path_to_watch {
- PathToWatch::Worktree {
- literal_prefix,
- pattern,
- } => {
- if let Some((tree, glob)) =
- tree.as_local_mut().zip(Glob::new(&pattern).log_err())
- {
- tree.add_path_prefix_to_scan(literal_prefix);
- worktree_globs
- .entry(tree.id())
- .or_insert_with(GlobSetBuilder::new)
- .add(glob);
- } else {
- return false;
- }
- }
- PathToWatch::Absolute { path, pattern } => {
- if let Some(glob) = Glob::new(&pattern).log_err() {
- abs_globs
- .entry(path)
- .or_insert_with(GlobSetBuilder::new)
- .add(glob);
- }
- }
- }
- return true;
- }
- false
- });
- if glob_is_inside_worktree {
- log::trace!(
- "Watcher pattern `{}` has been attached to the worktree at `{}`",
- serde_json::to_string(&watcher.glob_pattern).unwrap(),
- worktree.read(cx).abs_path().display()
- );
- found_host = true;
- }
- }
- if !found_host {
- log::error!(
- "Watcher pattern `{}` has not been attached to any worktree or absolute path",
- serde_json::to_string(&watcher.glob_pattern).unwrap()
- )
- }
- }
-
- let mut watch_builder = LanguageServerWatchedPathsBuilder::default();
- for (worktree_id, builder) in worktree_globs {
- if let Ok(globset) = builder.build() {
- watch_builder.watch_worktree(worktree_id, globset);
- }
- }
- for (abs_path, builder) in abs_globs {
- if let Ok(globset) = builder.build() {
- watch_builder.watch_abs_path(abs_path, globset);
- }
- }
- watch_builder
- }
-
pub fn language_server_for_id(&self, id: LanguageServerId) -> Option<Arc<LanguageServer>> {
if let Some(local_lsp_store) = self.as_local() {
if let Some(LanguageServerState::Running { server, .. }) =
@@ -5419,55 +5458,6 @@ impl LspStore {
})
}
- fn on_lsp_did_change_watched_files(
- &mut self,
- language_server_id: LanguageServerId,
- registration_id: &str,
- params: DidChangeWatchedFilesRegistrationOptions,
- cx: &mut ModelContext<Self>,
- ) {
- if let Some(local) = self.as_local_mut() {
- let registrations = local
- .language_server_watcher_registrations
- .entry(language_server_id)
- .or_default();
-
- registrations.insert(registration_id.to_string(), params.watchers);
-
- self.rebuild_watched_paths(language_server_id, cx);
- }
- }
-
- fn on_lsp_unregister_did_change_watched_files(
- &mut self,
- language_server_id: LanguageServerId,
- registration_id: &str,
- cx: &mut ModelContext<Self>,
- ) {
- if let Some(local) = self.as_local_mut() {
- let registrations = local
- .language_server_watcher_registrations
- .entry(language_server_id)
- .or_default();
-
- if registrations.remove(registration_id).is_some() {
- log::info!(
- "language server {}: unregistered workspace/DidChangeWatchedFiles capability with id {}",
- language_server_id,
- registration_id
- );
- } else {
- log::warn!(
- "language server {}: failed to unregister workspace/DidChangeWatchedFiles capability with id {}. not registered.",
- language_server_id,
- registration_id
- );
- }
-
- self.rebuild_watched_paths(language_server_id, cx);
- }
- }
-
pub async fn handle_resolve_completion_documentation(
this: Model<Self>,
envelope: TypedEnvelope<proto::ResolveCompletionDocumentation>,
@@ -6658,9 +6648,10 @@ impl LspStore {
if let Some(options) = reg.register_options {
let options = serde_json::from_value(options)?;
this.update(&mut cx, |this, cx| {
- this.on_lsp_did_change_watched_files(
+ this.as_local_mut()?.on_lsp_did_change_watched_files(
server_id, ®.id, options, cx,
);
+ Some(())
})?;
}
}
@@ -6760,9 +6751,11 @@ impl LspStore {
match unreg.method.as_str() {
"workspace/didChangeWatchedFiles" => {
this.update(&mut cx, |this, cx| {
- this.on_lsp_unregister_did_change_watched_files(
- server_id, &unreg.id, cx,
- );
+ this.as_local_mut()?
+ .on_lsp_unregister_did_change_watched_files(
+ server_id, &unreg.id, cx,
+ );
+ Some(())
})?;
}
"textDocument/rename" => {