@@ -79,7 +79,8 @@ use std::{
};
use text::{Anchor, BufferId, LineEnding, OffsetRangeExt};
use util::{
- debug_panic, defer, maybe, merge_json_value_into, post_inc, ResultExt, TryFutureExt as _,
+ debug_panic, defer, maybe, merge_json_value_into, paths::SanitizedPath, post_inc, ResultExt,
+ TryFutureExt as _,
};
pub use fs::*;
@@ -2546,112 +2547,119 @@ impl LocalLspStore {
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,
- }
+ let worktree_root_path = tree.abs_path();
+ let path_to_watch = match &watcher.glob_pattern {
+ lsp::GlobPattern::String(s) => {
+ let watcher_path = SanitizedPath::from(s);
+ match watcher_path.as_path().strip_prefix(&worktree_root_path) {
+ Ok(relative) => {
+ let pattern = relative.to_string_lossy().to_string();
+ let literal_prefix = glob_literal_prefix(relative).into();
+
+ 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()
- };
+ }
+ Err(_) => {
+ let path = glob_literal_prefix(watcher_path.as_path());
+ let pattern = watcher_path
+ .as_path()
+ .strip_prefix(&path)
+ .map(|p| p.to_string_lossy().to_string())
+ .unwrap_or_else(|e| {
+ debug_panic!(
+ "Failed to strip prefix for string pattern: {}, with prefix: {}, with error: {}",
+ s,
+ path.display(),
+ e
+ );
+ watcher_path.as_path().to_string_lossy().to_string()
+ });
+ let path = if path.components().next().is_none() {
+ worktree_root_path.clone()
+ } else {
+ path.into()
+ };
- PathToWatch::Absolute { path, pattern }
- }
+ 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,
+ }
+ 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(Path::new(&rp.pattern)));
+
+ PathToWatch::Worktree {
+ literal_prefix: literal_prefix.into(),
+ pattern: rp.pattern.clone(),
+ }
}
- .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));
+ Err(_) => {
+ let path = glob_literal_prefix(Path::new(&rp.pattern));
+ let pattern = Path::new(&rp.pattern)
+ .strip_prefix(&path)
+ .map(|p| p.to_string_lossy().to_string())
+ .unwrap_or_else(|e| {
+ debug_panic!(
+ "Failed to strip prefix for relative pattern: {}, with prefix: {}, with error: {}",
+ rp.pattern,
+ path.display(),
+ e
+ );
+ rp.pattern.clone()
+ });
+ base_uri.push(path);
- 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 }
- }
+ let path = if base_uri.components().next().is_none() {
+ debug_panic!("base_uri is empty, {}", base_uri.display());
+ worktree_root_path.clone()
+ } 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;
- }
+ }
+ };
+ 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);
- }
+ }
+ 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
+ true
});
if glob_is_inside_worktree {
log::trace!(
@@ -8319,23 +8327,13 @@ impl DiagnosticSummary {
}
}
-fn glob_literal_prefix(glob: &str) -> &str {
- let is_absolute = glob.starts_with(path::MAIN_SEPARATOR);
-
- let mut literal_end = is_absolute as usize;
- for (i, part) in glob.split(path::MAIN_SEPARATOR).enumerate() {
- if part.contains(['*', '?', '{', '}']) {
- break;
- } else {
- if i > 0 {
- // Account for separator prior to this part
- literal_end += path::MAIN_SEPARATOR.len_utf8();
- }
- literal_end += part.len();
- }
- }
- let literal_end = literal_end.min(glob.len());
- &glob[..literal_end]
+fn glob_literal_prefix(glob: &Path) -> PathBuf {
+ glob.components()
+ .take_while(|component| match component {
+ path::Component::Normal(part) => !part.to_string_lossy().contains(['*', '?', '{', '}']),
+ _ => true,
+ })
+ .collect()
}
pub struct SshLspAdapter {
@@ -8715,8 +8713,34 @@ fn include_text(server: &lsp::LanguageServer) -> Option<bool> {
#[cfg(test)]
#[test]
fn test_glob_literal_prefix() {
- assert_eq!(glob_literal_prefix("**/*.js"), "");
- assert_eq!(glob_literal_prefix("node_modules/**/*.js"), "node_modules");
- assert_eq!(glob_literal_prefix("foo/{bar,baz}.js"), "foo");
- assert_eq!(glob_literal_prefix("foo/bar/baz.js"), "foo/bar/baz.js");
+ assert_eq!(glob_literal_prefix(Path::new("**/*.js")), Path::new(""));
+ assert_eq!(
+ glob_literal_prefix(Path::new("node_modules/**/*.js")),
+ Path::new("node_modules")
+ );
+ assert_eq!(
+ glob_literal_prefix(Path::new("foo/{bar,baz}.js")),
+ Path::new("foo")
+ );
+ assert_eq!(
+ glob_literal_prefix(Path::new("foo/bar/baz.js")),
+ Path::new("foo/bar/baz.js")
+ );
+
+ #[cfg(target_os = "windows")]
+ {
+ assert_eq!(glob_literal_prefix(Path::new("**\\*.js")), Path::new(""));
+ assert_eq!(
+ glob_literal_prefix(Path::new("node_modules\\**/*.js")),
+ Path::new("node_modules")
+ );
+ assert_eq!(
+ glob_literal_prefix(Path::new("foo/{bar,baz}.js")),
+ Path::new("foo")
+ );
+ assert_eq!(
+ glob_literal_prefix(Path::new("foo\\bar\\baz.js")),
+ Path::new("foo/bar/baz.js")
+ );
+ }
}
@@ -781,11 +781,19 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
#[gpui::test]
async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppContext) {
+ fn add_root_for_windows(path: &str) -> String {
+ if cfg!(windows) {
+ format!("C:{}", path)
+ } else {
+ path.to_string()
+ }
+ }
+
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
- "/the-root",
+ add_root_for_windows("/the-root"),
json!({
".gitignore": "target\n",
"src": {
@@ -813,7 +821,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
)
.await;
- let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
+ let project = Project::test(fs.clone(), [add_root_for_windows("/the-root").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp(
@@ -829,7 +837,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
// Start the language server by opening a buffer with a compatible file extension.
let _ = project
.update(cx, |project, cx| {
- project.open_local_buffer_with_lsp("/the-root/src/a.rs", cx)
+ project.open_local_buffer_with_lsp(add_root_for_windows("/the-root/src/a.rs"), cx)
})
.await
.unwrap();
@@ -869,21 +877,21 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
lsp::DidChangeWatchedFilesRegistrationOptions {
watchers: vec![
lsp::FileSystemWatcher {
- glob_pattern: lsp::GlobPattern::String(
- "/the-root/Cargo.toml".to_string(),
- ),
+ glob_pattern: lsp::GlobPattern::String(add_root_for_windows(
+ "/the-root/Cargo.toml",
+ )),
kind: None,
},
lsp::FileSystemWatcher {
- glob_pattern: lsp::GlobPattern::String(
- "/the-root/src/*.{rs,c}".to_string(),
- ),
+ glob_pattern: lsp::GlobPattern::String(add_root_for_windows(
+ "/the-root/src/*.{rs,c}",
+ )),
kind: None,
},
lsp::FileSystemWatcher {
- glob_pattern: lsp::GlobPattern::String(
- "/the-root/target/y/**/*.rs".to_string(),
- ),
+ glob_pattern: lsp::GlobPattern::String(add_root_for_windows(
+ "/the-root/target/y/**/*.rs",
+ )),
kind: None,
},
],
@@ -936,21 +944,36 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
// Perform some file system mutations, two of which match the watched patterns,
// and one of which does not.
- fs.create_file("/the-root/src/c.rs".as_ref(), Default::default())
- .await
- .unwrap();
- fs.create_file("/the-root/src/d.txt".as_ref(), Default::default())
- .await
- .unwrap();
- fs.remove_file("/the-root/src/b.rs".as_ref(), Default::default())
- .await
- .unwrap();
- fs.create_file("/the-root/target/x/out/x2.rs".as_ref(), Default::default())
- .await
- .unwrap();
- fs.create_file("/the-root/target/y/out/y2.rs".as_ref(), Default::default())
- .await
- .unwrap();
+ fs.create_file(
+ add_root_for_windows("/the-root/src/c.rs").as_ref(),
+ Default::default(),
+ )
+ .await
+ .unwrap();
+ fs.create_file(
+ add_root_for_windows("/the-root/src/d.txt").as_ref(),
+ Default::default(),
+ )
+ .await
+ .unwrap();
+ fs.remove_file(
+ add_root_for_windows("/the-root/src/b.rs").as_ref(),
+ Default::default(),
+ )
+ .await
+ .unwrap();
+ fs.create_file(
+ add_root_for_windows("/the-root/target/x/out/x2.rs").as_ref(),
+ Default::default(),
+ )
+ .await
+ .unwrap();
+ fs.create_file(
+ add_root_for_windows("/the-root/target/y/out/y2.rs").as_ref(),
+ Default::default(),
+ )
+ .await
+ .unwrap();
// The language server receives events for the FS mutations that match its watch patterns.
cx.executor().run_until_parked();
@@ -958,15 +981,16 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
&*file_changes.lock(),
&[
lsp::FileEvent {
- uri: lsp::Url::from_file_path("/the-root/src/b.rs").unwrap(),
+ uri: lsp::Url::from_file_path(add_root_for_windows("/the-root/src/b.rs")).unwrap(),
typ: lsp::FileChangeType::DELETED,
},
lsp::FileEvent {
- uri: lsp::Url::from_file_path("/the-root/src/c.rs").unwrap(),
+ uri: lsp::Url::from_file_path(add_root_for_windows("/the-root/src/c.rs")).unwrap(),
typ: lsp::FileChangeType::CREATED,
},
lsp::FileEvent {
- uri: lsp::Url::from_file_path("/the-root/target/y/out/y2.rs").unwrap(),
+ uri: lsp::Url::from_file_path(add_root_for_windows("/the-root/target/y/out/y2.rs"))
+ .unwrap(),
typ: lsp::FileChangeType::CREATED,
},
]