diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index d0455f3c1b..387432557e 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -133,6 +133,7 @@ impl FormatTrigger { } pub struct LocalLspStore { + worktree_store: Model, http_client: Arc, environment: Model, fs: Arc, @@ -1562,6 +1563,247 @@ impl LocalLspStore { failure_reason: None, }) } + fn rebuild_watched_paths_inner<'a>( + &'a self, + language_server_id: LanguageServerId, + watchers: impl Iterator, + cx: &mut ModelContext, + ) -> 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::>(); + + 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, + pattern: String, + }, + Absolute { + path: Arc, + 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, + ) { + 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, + ) { + 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, + ) { + 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 { ::Result: Send, ::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, - ) { - 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, - cx: &mut ModelContext, - ) -> 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::>(); - - 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, - pattern: String, - }, - Absolute { - path: Arc, - 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> { 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, - ) { - 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, - ) { - 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, envelope: TypedEnvelope, @@ -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" => {