diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index 87ccb92971..068bf7158d 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -123,7 +123,7 @@ impl PromptBuilder { if params.fs.is_dir(parent_dir).await { let (mut changes, _watcher) = params.fs.watch(parent_dir, Duration::from_secs(1)).await; while let Some(changed_paths) = changes.next().await { - if changed_paths.iter().any(|p| p == &templates_dir) { + if changed_paths.iter().any(|p| &p.path == &templates_dir) { let mut log_message = format!("Prompt template overrides directory detected at {}", templates_dir.display()); if let Ok(target) = params.fs.read_link(&templates_dir).await { log_message.push_str(" -> "); @@ -162,18 +162,18 @@ impl PromptBuilder { let mut combined_changes = futures::stream::select(changes, parent_changes); while let Some(changed_paths) = combined_changes.next().await { - if changed_paths.iter().any(|p| p == &templates_dir) { + if changed_paths.iter().any(|p| &p.path == &templates_dir) { if !params.fs.is_dir(&templates_dir).await { log::info!("Prompt template overrides directory removed. Restoring built-in prompt templates."); Self::register_built_in_templates(&mut handlebars.lock()).log_err(); break; } } - for changed_path in changed_paths { - if changed_path.starts_with(&templates_dir) && changed_path.extension().map_or(false, |ext| ext == "hbs") { - log::info!("Reloading prompt template override: {}", changed_path.display()); - if let Some(content) = params.fs.load(&changed_path).await.log_err() { - let file_name = changed_path.file_stem().unwrap().to_string_lossy(); + for event in changed_paths { + if event.path.starts_with(&templates_dir) && event.path.extension().map_or(false, |ext| ext == "hbs") { + log::info!("Reloading prompt template override: {}", event.path.display()); + if let Some(content) = params.fs.load(&event.path).await.log_err() { + let file_name = event.path.file_stem().unwrap().to_string_lossy(); handlebars.lock().register_template_string(&file_name, content).log_err(); } } diff --git a/crates/extension/src/extension_store.rs b/crates/extension/src/extension_store.rs index c650b5ff5b..2558dca93e 100644 --- a/crates/extension/src/extension_store.rs +++ b/crates/extension/src/extension_store.rs @@ -369,9 +369,9 @@ impl ExtensionStore { let installed_dir = this.installed_dir.clone(); async move { let (mut paths, _) = fs.watch(&installed_dir, FS_WATCH_LATENCY).await; - while let Some(paths) = paths.next().await { - for path in paths { - let Ok(event_path) = path.strip_prefix(&installed_dir) else { + while let Some(events) = paths.next().await { + for event in events { + let Ok(event_path) = event.path.strip_prefix(&installed_dir) else { continue; }; diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index a262ee1e80..a463773e7e 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -45,6 +45,25 @@ pub trait Watcher: Send + Sync { fn remove(&self, path: &Path) -> Result<()>; } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum PathEventKind { + Removed, + Created, + Changed, +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PathEvent { + pub path: PathBuf, + pub kind: Option, +} + +impl From for PathBuf { + fn from(event: PathEvent) -> Self { + event.path + } +} + #[async_trait::async_trait] pub trait Fs: Send + Sync { async fn create_dir(&self, path: &Path) -> Result<()>; @@ -92,7 +111,7 @@ pub trait Fs: Send + Sync { path: &Path, latency: Duration, ) -> ( - Pin>>>, + Pin>>>, Arc, ); @@ -469,17 +488,38 @@ impl Fs for RealFs { path: &Path, latency: Duration, ) -> ( - Pin>>>, + Pin>>>, Arc, ) { - use fsevent::EventStream; + use fsevent::{EventStream, StreamFlags}; let (tx, rx) = smol::channel::unbounded(); let (stream, handle) = EventStream::new(&[path], latency); std::thread::spawn(move || { stream.run(move |events| { - smol::block_on(tx.send(events.into_iter().map(|event| event.path).collect())) - .is_ok() + smol::block_on( + tx.send( + events + .into_iter() + .map(|event| { + let kind = if event.flags.contains(StreamFlags::ITEM_REMOVED) { + Some(PathEventKind::Removed) + } else if event.flags.contains(StreamFlags::ITEM_CREATED) { + Some(PathEventKind::Created) + } else if event.flags.contains(StreamFlags::ITEM_MODIFIED) { + Some(PathEventKind::Changed) + } else { + None + }; + PathEvent { + path: event.path, + kind, + } + }) + .collect(), + ), + ) + .is_ok() }); }); @@ -498,32 +538,46 @@ impl Fs for RealFs { path: &Path, latency: Duration, ) -> ( - Pin>>>, + Pin>>>, Arc, ) { + use notify::EventKind; use parking_lot::Mutex; let (tx, rx) = smol::channel::unbounded(); - let pending_paths: Arc>> = Default::default(); + let pending_paths: Arc>> = Default::default(); let root_path = path.to_path_buf(); watcher::global(|g| { let tx = tx.clone(); let pending_paths = pending_paths.clone(); g.add(move |event: ¬ify::Event| { + let kind = match event.kind { + EventKind::Create(_) => Some(PathEventKind::Created), + EventKind::Modify(_) => Some(PathEventKind::Changed), + EventKind::Remove(_) => Some(PathEventKind::Removed), + _ => None, + }; let mut paths = event .paths .iter() - .filter(|path| path.starts_with(&root_path)) - .cloned() + .filter_map(|path| { + path.starts_with(&root_path).then(|| PathEvent { + path: path.clone(), + kind, + }) + }) .collect::>(); + if !paths.is_empty() { paths.sort(); let mut pending_paths = pending_paths.lock(); if pending_paths.is_empty() { tx.try_send(()).ok(); } - util::extend_sorted(&mut *pending_paths, paths, usize::MAX, PathBuf::cmp); + util::extend_sorted(&mut *pending_paths, paths, usize::MAX, |a, b| { + a.path.cmp(&b.path) + }); } }) }) @@ -561,10 +615,10 @@ impl Fs for RealFs { path: &Path, _latency: Duration, ) -> ( - Pin>>>, + Pin>>>, Arc, ) { - use notify::Watcher; + use notify::{EventKind, Watcher}; let (tx, rx) = smol::channel::unbounded(); @@ -572,7 +626,21 @@ impl Fs for RealFs { let tx = tx.clone(); move |event: Result| { if let Some(event) = event.log_err() { - tx.try_send(event.paths).ok(); + let kind = match event.kind { + EventKind::Create(_) => Some(PathEventKind::Created), + EventKind::Modify(_) => Some(PathEventKind::Changed), + EventKind::Remove(_) => Some(PathEventKind::Removed), + _ => None, + }; + + tx.try_send( + event + .paths + .into_iter() + .map(|path| PathEvent { path, kind }) + .collect::>(), + ) + .ok(); } } }) @@ -682,9 +750,9 @@ struct FakeFsState { root: Arc>, next_inode: u64, next_mtime: SystemTime, - event_txs: Vec>>, + event_txs: Vec>>, events_paused: bool, - buffered_events: Vec, + buffered_events: Vec, metadata_call_count: usize, read_dir_call_count: usize, } @@ -793,11 +861,14 @@ impl FakeFsState { fn emit_event(&mut self, paths: I) where - I: IntoIterator, + I: IntoIterator)>, T: Into, { self.buffered_events - .extend(paths.into_iter().map(Into::into)); + .extend(paths.into_iter().map(|(path, kind)| PathEvent { + path: path.into(), + kind, + })); if !self.events_paused { self.flush_events(self.buffered_events.len()); @@ -872,7 +943,7 @@ impl FakeFs { Ok(()) }) .unwrap(); - state.emit_event([path.to_path_buf()]); + state.emit_event([(path.to_path_buf(), None)]); } pub async fn insert_file(&self, path: impl AsRef, content: Vec) { @@ -895,7 +966,7 @@ impl FakeFs { } }) .unwrap(); - state.emit_event([path]); + state.emit_event([(path, None)]); } fn write_file_internal(&self, path: impl AsRef, content: Vec) -> Result<()> { @@ -910,18 +981,24 @@ impl FakeFs { mtime, content, })); - state.write_path(path, move |entry| { - match entry { - btree_map::Entry::Vacant(e) => { - e.insert(file); - } - btree_map::Entry::Occupied(mut e) => { - *e.get_mut() = file; + let mut kind = None; + state.write_path(path, { + let kind = &mut kind; + move |entry| { + match entry { + btree_map::Entry::Vacant(e) => { + *kind = Some(PathEventKind::Created); + e.insert(file); + } + btree_map::Entry::Occupied(mut e) => { + *kind = Some(PathEventKind::Changed); + *e.get_mut() = file; + } } + Ok(()) } - Ok(()) })?; - state.emit_event([path]); + state.emit_event([(path, kind)]); Ok(()) } @@ -1030,7 +1107,7 @@ impl FakeFs { f(&mut repo_state); if emit_git_event { - state.emit_event([dot_git]); + state.emit_event([(dot_git, None)]); } } else { panic!("not a directory"); @@ -1081,7 +1158,7 @@ impl FakeFs { self.state.lock().emit_event( statuses .iter() - .map(|(path, _)| dot_git.parent().unwrap().join(path)), + .map(|(path, _)| (dot_git.parent().unwrap().join(path), None)), ); } @@ -1251,7 +1328,7 @@ impl Fs for FakeFs { state.next_inode += 1; state.write_path(&cur_path, |entry| { entry.or_insert_with(|| { - created_dirs.push(cur_path.clone()); + created_dirs.push((cur_path.clone(), Some(PathEventKind::Created))); Arc::new(Mutex::new(FakeFsEntry::Dir { inode, mtime, @@ -1263,7 +1340,7 @@ impl Fs for FakeFs { })? } - self.state.lock().emit_event(&created_dirs); + self.state.lock().emit_event(created_dirs); Ok(()) } @@ -1279,10 +1356,12 @@ impl Fs for FakeFs { mtime, content: Vec::new(), })); + let mut kind = Some(PathEventKind::Created); state.write_path(path, |entry| { match entry { btree_map::Entry::Occupied(mut e) => { if options.overwrite { + kind = Some(PathEventKind::Changed); *e.get_mut() = file; } else if !options.ignore_if_exists { return Err(anyhow!("path already exists: {}", path.display())); @@ -1294,7 +1373,7 @@ impl Fs for FakeFs { } Ok(()) })?; - state.emit_event([path]); + state.emit_event([(path, kind)]); Ok(()) } @@ -1313,7 +1392,8 @@ impl Fs for FakeFs { } }) .unwrap(); - state.emit_event([path]); + state.emit_event([(path, None)]); + Ok(()) } @@ -1388,7 +1468,10 @@ impl Fs for FakeFs { }) .unwrap(); - state.emit_event(&[old_path, new_path]); + state.emit_event([ + (old_path, Some(PathEventKind::Removed)), + (new_path, Some(PathEventKind::Created)), + ]); Ok(()) } @@ -1403,9 +1486,11 @@ impl Fs for FakeFs { state.next_mtime += Duration::from_nanos(1); let source_entry = state.read_path(&source)?; let content = source_entry.lock().file_content(&source)?.clone(); + let mut kind = Some(PathEventKind::Created); let entry = state.write_path(&target, |e| match e { btree_map::Entry::Occupied(e) => { if options.overwrite { + kind = Some(PathEventKind::Changed); Ok(Some(e.get().clone())) } else if !options.ignore_if_exists { return Err(anyhow!("{target:?} already exists")); @@ -1425,7 +1510,7 @@ impl Fs for FakeFs { if let Some(entry) = entry { entry.lock().set_file_content(&target, content)?; } - state.emit_event(&[target]); + state.emit_event([(target, kind)]); Ok(()) } @@ -1462,7 +1547,7 @@ impl Fs for FakeFs { e.remove(); } } - state.emit_event(&[path]); + state.emit_event([(path, Some(PathEventKind::Removed))]); Ok(()) } @@ -1491,7 +1576,7 @@ impl Fs for FakeFs { e.remove(); } } - state.emit_event(&[path]); + state.emit_event([(path, Some(PathEventKind::Removed))]); Ok(()) } @@ -1632,7 +1717,7 @@ impl Fs for FakeFs { path: &Path, _: Duration, ) -> ( - Pin>>>, + Pin>>>, Arc, ) { self.simulate_random_delay().await; @@ -1642,7 +1727,9 @@ impl Fs for FakeFs { let executor = self.executor.clone(); ( Box::pin(futures::StreamExt::filter(rx, move |events| { - let result = events.iter().any(|evt_path| evt_path.starts_with(&path)); + let result = events + .iter() + .any(|evt_path| evt_path.path.starts_with(&path)); let executor = executor.clone(); async move { executor.simulate_random_delay().await; diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 26ebb18456..a56651785a 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -22,8 +22,8 @@ use futures::{ }; use globset::{Glob, GlobSet, GlobSetBuilder}; use gpui::{ - AppContext, AsyncAppContext, Entity, EventEmitter, Model, ModelContext, PromptLevel, Task, - WeakModel, + AppContext, AsyncAppContext, Context, Entity, EventEmitter, Model, ModelContext, PromptLevel, + Task, WeakModel, }; use http_client::HttpClient; use itertools::Itertools; @@ -58,7 +58,7 @@ use std::{ convert::TryInto, ffi::OsStr, iter, mem, - ops::Range, + ops::{ControlFlow, Range}, path::{self, Path, PathBuf}, process::Stdio, str, @@ -103,7 +103,7 @@ pub struct LspStore { language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>, language_server_statuses: BTreeMap, last_workspace_edits_by_language_server: HashMap, - language_server_watched_paths: HashMap>, + language_server_watched_paths: HashMap>, language_server_watcher_registrations: HashMap>>, active_entry: Option, @@ -3227,6 +3227,38 @@ impl LspStore { .map(|(key, value)| (*key, value)) } + fn lsp_notify_abs_paths_changed( + &mut self, + server_id: LanguageServerId, + changes: Vec, + ) { + maybe!({ + let server = self.language_server_for_id(server_id)?; + let changes = changes + .into_iter() + .filter_map(|event| { + let typ = match event.kind? { + PathEventKind::Created => lsp::FileChangeType::CREATED, + PathEventKind::Removed => lsp::FileChangeType::DELETED, + PathEventKind::Changed => lsp::FileChangeType::CHANGED, + }; + Some(lsp::FileEvent { + uri: lsp::Url::from_file_path(&event.path).ok()?, + typ, + }) + }) + .collect::>(); + if !changes.is_empty() { + server + .notify::( + lsp::DidChangeWatchedFilesParams { changes }, + ) + .log_err(); + } + Some(()) + }); + } + fn rebuild_watched_paths( &mut self, language_server_id: LanguageServerId, @@ -3239,62 +3271,176 @@ impl LspStore { return; }; - let watched_paths = self - .language_server_watched_paths - .entry(language_server_id) - .or_default(); + 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 + ); - let mut builders = HashMap::default(); + 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::>(); + + enum PathToWatch { + Worktree { + literal_prefix: Arc, + pattern: String, + }, + Absolute { + path: Arc, + pattern: String, + }, + } for watcher in watchers.values().flatten() { - for worktree in self.worktree_store.read(cx).worktrees().collect::>() { + let mut found_host = false; + for worktree in &worktrees { let glob_is_inside_worktree = worktree.update(cx, |tree, _| { - if let Some(abs_path) = tree.abs_path().to_str() { - let relative_glob_pattern = match &watcher.glob_pattern { - lsp::GlobPattern::String(s) => Some( - s.strip_prefix(abs_path) - .unwrap_or(s) - .strip_prefix(std::path::MAIN_SEPARATOR) - .unwrap_or(s), - ), + 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("/")) + } else { + PathBuf::from(path).into() + }; + + PathToWatch::Absolute { path, pattern } + } + } + } lsp::GlobPattern::Relative(rp) => { - let base_uri = match &rp.base_uri { + 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; }; - base_uri.to_file_path().ok().and_then(|file_path| { - (file_path.to_str() == Some(abs_path)) - .then_some(rp.pattern.as_str()) - }) + + 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 } + } + } } }; - if let Some(relative_glob_pattern) = relative_glob_pattern { - let literal_prefix = glob_literal_prefix(relative_glob_pattern); - tree.as_local_mut() - .unwrap() - .add_path_prefix_to_scan(Path::new(literal_prefix).into()); - if let Some(glob) = Glob::new(relative_glob_pattern).log_err() { - builders - .entry(tree.id()) - .or_insert_with(GlobSetBuilder::new) - .add(glob); + 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; } + return true; } false }); if glob_is_inside_worktree { - break; + 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() + ) + } } - watched_paths.clear(); - for (worktree_id, builder) in builders { + let mut watch_builder = LanguageServerWatchedPathsBuilder::default(); + for (worktree_id, builder) in worktree_globs { if let Ok(globset) = builder.build() { - watched_paths.insert(worktree_id, globset); + 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); + } + } + let watcher = watch_builder.build(self.fs.clone(), language_server_id, cx); + self.language_server_watched_paths + .insert(language_server_id, watcher); cx.notify(); } @@ -5427,7 +5573,7 @@ impl LspStore { } } - pub fn update_local_worktree_language_servers( + pub(super) fn update_local_worktree_language_servers( &mut self, worktree_handle: &Model, changes: &[(Arc, ProjectEntryId, PathChange)], @@ -5456,7 +5602,7 @@ impl LspStore { if let Some(watched_paths) = self .language_server_watched_paths .get(&server_id) - .and_then(|paths| paths.get(&worktree_id)) + .and_then(|paths| paths.read(cx).worktree_paths.get(&worktree_id)) { let params = lsp::DidChangeWatchedFilesParams { changes: changes @@ -5991,6 +6137,102 @@ pub enum LanguageServerToQuery { Other(LanguageServerId), } +#[derive(Default)] +struct LanguageServerWatchedPaths { + worktree_paths: HashMap, + abs_paths: HashMap, (GlobSet, Task<()>)>, +} + +#[derive(Default)] +struct LanguageServerWatchedPathsBuilder { + worktree_paths: HashMap, + abs_paths: HashMap, GlobSet>, +} + +impl LanguageServerWatchedPathsBuilder { + fn watch_worktree(&mut self, worktree_id: WorktreeId, glob_set: GlobSet) { + self.worktree_paths.insert(worktree_id, glob_set); + } + fn watch_abs_path(&mut self, path: Arc, glob_set: GlobSet) { + self.abs_paths.insert(path, glob_set); + } + fn build( + self, + fs: Arc, + language_server_id: LanguageServerId, + cx: &mut ModelContext, + ) -> Model { + let project = cx.weak_model(); + + cx.new_model(|cx| { + let this_id = cx.entity_id(); + const LSP_ABS_PATH_OBSERVE: Duration = Duration::from_millis(100); + let abs_paths = self + .abs_paths + .into_iter() + .map(|(abs_path, globset)| { + let task = cx.spawn({ + let abs_path = abs_path.clone(); + let fs = fs.clone(); + + let lsp_store = project.clone(); + |_, mut cx| async move { + maybe!(async move { + let mut push_updates = + fs.watch(&abs_path, LSP_ABS_PATH_OBSERVE).await; + while let Some(update) = push_updates.0.next().await { + let action = lsp_store + .update(&mut cx, |this, cx| { + let Some(watcher) = this + .language_server_watched_paths + .get(&language_server_id) + else { + return ControlFlow::Break(()); + }; + if watcher.entity_id() != this_id { + // This watcher is no longer registered on the project, which means that we should + // cease operations. + return ControlFlow::Break(()); + } + let (globs, _) = watcher + .read(cx) + .abs_paths + .get(&abs_path) + .expect( + "Watched abs path is not registered with a watcher", + ); + let matching_entries = update + .into_iter() + .filter(|event| globs.is_match(&event.path)) + .collect::>(); + this.lsp_notify_abs_paths_changed( + language_server_id, + matching_entries, + ); + ControlFlow::Continue(()) + }) + .ok()?; + + if action.is_break() { + break; + } + } + Some(()) + }) + .await; + } + }); + (abs_path, (globset, task)) + }) + .collect(); + LanguageServerWatchedPaths { + worktree_paths: self.worktree_paths, + abs_paths, + } + }) + } +} + struct LspBufferSnapshot { version: i32, snapshot: TextBufferSnapshot, @@ -6094,7 +6336,9 @@ impl DiagnosticSummary { } fn glob_literal_prefix(glob: &str) -> &str { - let mut literal_end = 0; + 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; @@ -6106,6 +6350,7 @@ fn glob_literal_prefix(glob: &str) -> &str { literal_end += part.len(); } } + let literal_end = literal_end.min(glob.len()); &glob[..literal_end] } diff --git a/crates/snippet_provider/src/lib.rs b/crates/snippet_provider/src/lib.rs index 0a3643ac35..17d615866a 100644 --- a/crates/snippet_provider/src/lib.rs +++ b/crates/snippet_provider/src/lib.rs @@ -169,7 +169,12 @@ impl SnippetProvider { let (mut entries, _) = watcher.await; while let Some(entries) = entries.next().await { - process_updates(this.clone(), entries, cx.clone()).await?; + process_updates( + this.clone(), + entries.into_iter().map(|event| event.path).collect(), + cx.clone(), + ) + .await?; } Ok(()) }) diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index 0ed9625f51..33ec580fa9 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -7,7 +7,7 @@ use ::ignore::gitignore::{Gitignore, GitignoreBuilder}; use anyhow::{anyhow, Context as _, Result}; use clock::ReplicaId; use collections::{HashMap, HashSet, VecDeque}; -use fs::{copy_recursive, Fs, RemoveOptions, Watcher}; +use fs::{copy_recursive, Fs, PathEvent, RemoveOptions, Watcher}; use futures::{ channel::{ mpsc::{self, UnboundedSender}, @@ -1048,7 +1048,11 @@ impl LocalWorktree { watcher, }; - scanner.run(events).await; + scanner + .run(Box::pin( + events.map(|events| events.into_iter().map(Into::into).collect()), + )) + .await; } }); let scan_state_updater = cx.spawn(|this, mut cx| async move { @@ -3512,7 +3516,7 @@ enum BackgroundScannerPhase { } impl BackgroundScanner { - async fn run(&mut self, mut fs_events_rx: Pin>>>) { + async fn run(&mut self, mut fs_events_rx: Pin>>>) { use futures::FutureExt as _; // If the worktree root does not contain a git repository, then find @@ -3593,7 +3597,8 @@ impl BackgroundScanner { while let Poll::Ready(Some(more_paths)) = futures::poll!(fs_events_rx.next()) { paths.extend(more_paths); } - self.process_events(paths).await; + self.process_events(paths.into_iter().map(Into::into).collect()) + .await; } // Continue processing events until the worktree is dropped. @@ -3634,7 +3639,7 @@ impl BackgroundScanner { while let Poll::Ready(Some(more_paths)) = futures::poll!(fs_events_rx.next()) { paths.extend(more_paths); } - self.process_events(paths.clone()).await; + self.process_events(paths.into_iter().map(Into::into).collect()).await; } } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 0801da6174..401eaf30a4 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -1153,13 +1153,13 @@ fn watch_themes(fs: Arc, cx: &mut AppContext) { .await; while let Some(paths) = events.next().await { - for path in paths { - if fs.metadata(&path).await.ok().flatten().is_some() { + for event in paths { + if fs.metadata(&event.path).await.ok().flatten().is_some() { if let Some(theme_registry) = cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err() { if let Some(()) = theme_registry - .load_user_theme(&path, fs.clone()) + .load_user_theme(&event.path, fs.clone()) .await .log_err() { @@ -1188,8 +1188,10 @@ fn watch_languages(fs: Arc, languages: Arc, cx: &m cx.spawn(|_| async move { let (mut events, _) = fs.watch(path.as_path(), Duration::from_millis(100)).await; while let Some(event) = events.next().await { - let has_language_file = event.iter().any(|path| { - path.extension() + let has_language_file = event.iter().any(|event| { + event + .path + .extension() .map(|ext| ext.to_string_lossy().as_ref() == "scm") .unwrap_or(false) });