Avoid redundant FS scans when LSPs changed watched files (#2663)
Release Notes: - Fixed a performance problem that could occur when a language server requested to watch a set of files (preview only).
This commit is contained in:
commit
1ae5261024
4 changed files with 136 additions and 90 deletions
|
@ -388,6 +388,7 @@ struct FakeFsState {
|
||||||
event_txs: Vec<smol::channel::Sender<Vec<fsevent::Event>>>,
|
event_txs: Vec<smol::channel::Sender<Vec<fsevent::Event>>>,
|
||||||
events_paused: bool,
|
events_paused: bool,
|
||||||
buffered_events: Vec<fsevent::Event>,
|
buffered_events: Vec<fsevent::Event>,
|
||||||
|
metadata_call_count: usize,
|
||||||
read_dir_call_count: usize,
|
read_dir_call_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -538,6 +539,7 @@ impl FakeFs {
|
||||||
buffered_events: Vec::new(),
|
buffered_events: Vec::new(),
|
||||||
events_paused: false,
|
events_paused: false,
|
||||||
read_dir_call_count: 0,
|
read_dir_call_count: 0,
|
||||||
|
metadata_call_count: 0,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -774,10 +776,16 @@ impl FakeFs {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How many `read_dir` calls have been issued.
|
||||||
pub fn read_dir_call_count(&self) -> usize {
|
pub fn read_dir_call_count(&self) -> usize {
|
||||||
self.state.lock().read_dir_call_count
|
self.state.lock().read_dir_call_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How many `metadata` calls have been issued.
|
||||||
|
pub fn metadata_call_count(&self) -> usize {
|
||||||
|
self.state.lock().metadata_call_count
|
||||||
|
}
|
||||||
|
|
||||||
async fn simulate_random_delay(&self) {
|
async fn simulate_random_delay(&self) {
|
||||||
self.executor
|
self.executor
|
||||||
.upgrade()
|
.upgrade()
|
||||||
|
@ -1098,7 +1106,8 @@ impl Fs for FakeFs {
|
||||||
async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
|
async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
|
||||||
self.simulate_random_delay().await;
|
self.simulate_random_delay().await;
|
||||||
let path = normalize_path(path);
|
let path = normalize_path(path);
|
||||||
let state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
|
state.metadata_call_count += 1;
|
||||||
if let Some((mut entry, _)) = state.try_read_path(&path, false) {
|
if let Some((mut entry, _)) = state.try_read_path(&path, false) {
|
||||||
let is_symlink = entry.lock().is_symlink();
|
let is_symlink = entry.lock().is_symlink();
|
||||||
if is_symlink {
|
if is_symlink {
|
||||||
|
|
|
@ -596,6 +596,8 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let prev_read_dir_count = fs.read_dir_call_count();
|
||||||
|
|
||||||
// Keep track of the FS events reported to the language server.
|
// Keep track of the FS events reported to the language server.
|
||||||
let fake_server = fake_servers.next().await.unwrap();
|
let fake_server = fake_servers.next().await.unwrap();
|
||||||
let file_changes = Arc::new(Mutex::new(Vec::new()));
|
let file_changes = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
@ -607,6 +609,12 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
||||||
register_options: serde_json::to_value(
|
register_options: serde_json::to_value(
|
||||||
lsp::DidChangeWatchedFilesRegistrationOptions {
|
lsp::DidChangeWatchedFilesRegistrationOptions {
|
||||||
watchers: vec![
|
watchers: vec![
|
||||||
|
lsp::FileSystemWatcher {
|
||||||
|
glob_pattern: lsp::GlobPattern::String(
|
||||||
|
"/the-root/Cargo.toml".to_string(),
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
},
|
||||||
lsp::FileSystemWatcher {
|
lsp::FileSystemWatcher {
|
||||||
glob_pattern: lsp::GlobPattern::String(
|
glob_pattern: lsp::GlobPattern::String(
|
||||||
"/the-root/src/*.{rs,c}".to_string(),
|
"/the-root/src/*.{rs,c}".to_string(),
|
||||||
|
@ -638,6 +646,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
||||||
|
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
assert_eq!(mem::take(&mut *file_changes.lock()), &[]);
|
assert_eq!(mem::take(&mut *file_changes.lock()), &[]);
|
||||||
|
assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 4);
|
||||||
|
|
||||||
// Now the language server has asked us to watch an ignored directory path,
|
// Now the language server has asked us to watch an ignored directory path,
|
||||||
// so we recursively load it.
|
// so we recursively load it.
|
||||||
|
|
|
@ -3071,17 +3071,20 @@ impl BackgroundScanner {
|
||||||
|
|
||||||
path_prefix = self.path_prefixes_to_scan_rx.recv().fuse() => {
|
path_prefix = self.path_prefixes_to_scan_rx.recv().fuse() => {
|
||||||
let Ok(path_prefix) = path_prefix else { break };
|
let Ok(path_prefix) = path_prefix else { break };
|
||||||
|
log::trace!("adding path prefix {:?}", path_prefix);
|
||||||
|
|
||||||
self.forcibly_load_paths(&[path_prefix.clone()]).await;
|
let did_scan = self.forcibly_load_paths(&[path_prefix.clone()]).await;
|
||||||
|
if did_scan {
|
||||||
|
let abs_path =
|
||||||
|
{
|
||||||
|
let mut state = self.state.lock();
|
||||||
|
state.path_prefixes_to_scan.insert(path_prefix.clone());
|
||||||
|
state.snapshot.abs_path.join(&path_prefix)
|
||||||
|
};
|
||||||
|
|
||||||
let abs_path =
|
if let Some(abs_path) = self.fs.canonicalize(&abs_path).await.log_err() {
|
||||||
{
|
self.process_events(vec![abs_path]).await;
|
||||||
let mut state = self.state.lock();
|
}
|
||||||
state.path_prefixes_to_scan.insert(path_prefix.clone());
|
|
||||||
state.snapshot.abs_path.join(path_prefix)
|
|
||||||
};
|
|
||||||
if let Some(abs_path) = self.fs.canonicalize(&abs_path).await.log_err() {
|
|
||||||
self.process_events(vec![abs_path]).await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3097,10 +3100,13 @@ impl BackgroundScanner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_scan_request(&self, request: ScanRequest, scanning: bool) -> bool {
|
async fn process_scan_request(&self, mut request: ScanRequest, scanning: bool) -> bool {
|
||||||
log::debug!("rescanning paths {:?}", request.relative_paths);
|
log::debug!("rescanning paths {:?}", request.relative_paths);
|
||||||
|
|
||||||
let root_path = self.forcibly_load_paths(&request.relative_paths).await;
|
request.relative_paths.sort_unstable();
|
||||||
|
self.forcibly_load_paths(&request.relative_paths).await;
|
||||||
|
|
||||||
|
let root_path = self.state.lock().snapshot.abs_path.clone();
|
||||||
let root_canonical_path = match self.fs.canonicalize(&root_path).await {
|
let root_canonical_path = match self.fs.canonicalize(&root_path).await {
|
||||||
Ok(path) => path,
|
Ok(path) => path,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -3108,10 +3114,9 @@ impl BackgroundScanner {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let abs_paths = request
|
let abs_paths = request
|
||||||
.relative_paths
|
.relative_paths
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|path| {
|
.map(|path| {
|
||||||
if path.file_name().is_some() {
|
if path.file_name().is_some() {
|
||||||
root_canonical_path.join(path)
|
root_canonical_path.join(path)
|
||||||
|
@ -3120,12 +3125,19 @@ impl BackgroundScanner {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
self.reload_entries_for_paths(root_path, root_canonical_path, abs_paths, None)
|
|
||||||
.await;
|
self.reload_entries_for_paths(
|
||||||
|
root_path,
|
||||||
|
root_canonical_path,
|
||||||
|
&request.relative_paths,
|
||||||
|
abs_paths,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
self.send_status_update(scanning, Some(request.done))
|
self.send_status_update(scanning, Some(request.done))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_events(&mut self, abs_paths: Vec<PathBuf>) {
|
async fn process_events(&mut self, mut abs_paths: Vec<PathBuf>) {
|
||||||
log::debug!("received fs events {:?}", abs_paths);
|
log::debug!("received fs events {:?}", abs_paths);
|
||||||
|
|
||||||
let root_path = self.state.lock().snapshot.abs_path.clone();
|
let root_path = self.state.lock().snapshot.abs_path.clone();
|
||||||
|
@ -3137,25 +3149,61 @@ impl BackgroundScanner {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (scan_job_tx, scan_job_rx) = channel::unbounded();
|
let mut relative_paths = Vec::with_capacity(abs_paths.len());
|
||||||
let paths = self
|
let mut unloaded_relative_paths = Vec::new();
|
||||||
.reload_entries_for_paths(
|
abs_paths.sort_unstable();
|
||||||
|
abs_paths.dedup_by(|a, b| a.starts_with(&b));
|
||||||
|
abs_paths.retain(|abs_path| {
|
||||||
|
let snapshot = &self.state.lock().snapshot;
|
||||||
|
{
|
||||||
|
let relative_path: Arc<Path> =
|
||||||
|
if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) {
|
||||||
|
path.into()
|
||||||
|
} else {
|
||||||
|
log::error!(
|
||||||
|
"ignoring event {abs_path:?} outside of root path {root_canonical_path:?}",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| {
|
||||||
|
snapshot
|
||||||
|
.entry_for_path(parent)
|
||||||
|
.map_or(false, |entry| entry.kind == EntryKind::Dir)
|
||||||
|
});
|
||||||
|
if !parent_dir_is_loaded {
|
||||||
|
log::debug!("ignoring event {relative_path:?} within unloaded directory");
|
||||||
|
unloaded_relative_paths.push(relative_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
relative_paths.push(relative_path);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if !relative_paths.is_empty() {
|
||||||
|
let (scan_job_tx, scan_job_rx) = channel::unbounded();
|
||||||
|
self.reload_entries_for_paths(
|
||||||
root_path,
|
root_path,
|
||||||
root_canonical_path,
|
root_canonical_path,
|
||||||
|
&relative_paths,
|
||||||
abs_paths,
|
abs_paths,
|
||||||
Some(scan_job_tx.clone()),
|
Some(scan_job_tx.clone()),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
drop(scan_job_tx);
|
drop(scan_job_tx);
|
||||||
self.scan_dirs(false, scan_job_rx).await;
|
self.scan_dirs(false, scan_job_rx).await;
|
||||||
|
|
||||||
let (scan_job_tx, scan_job_rx) = channel::unbounded();
|
let (scan_job_tx, scan_job_rx) = channel::unbounded();
|
||||||
self.update_ignore_statuses(scan_job_tx).await;
|
self.update_ignore_statuses(scan_job_tx).await;
|
||||||
self.scan_dirs(false, scan_job_rx).await;
|
self.scan_dirs(false, scan_job_rx).await;
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
state.reload_repositories(&paths, self.fs.as_ref());
|
relative_paths.extend(unloaded_relative_paths);
|
||||||
|
state.reload_repositories(&relative_paths, self.fs.as_ref());
|
||||||
state.snapshot.completed_scan_id = state.snapshot.scan_id;
|
state.snapshot.completed_scan_id = state.snapshot.scan_id;
|
||||||
for (_, entry_id) in mem::take(&mut state.removed_entry_ids) {
|
for (_, entry_id) in mem::take(&mut state.removed_entry_ids) {
|
||||||
state.scanned_dirs.remove(&entry_id);
|
state.scanned_dirs.remove(&entry_id);
|
||||||
|
@ -3165,12 +3213,11 @@ impl BackgroundScanner {
|
||||||
self.send_status_update(false, None);
|
self.send_status_update(false, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn forcibly_load_paths(&self, paths: &[Arc<Path>]) -> Arc<Path> {
|
async fn forcibly_load_paths(&self, paths: &[Arc<Path>]) -> bool {
|
||||||
let root_path;
|
|
||||||
let (scan_job_tx, mut scan_job_rx) = channel::unbounded();
|
let (scan_job_tx, mut scan_job_rx) = channel::unbounded();
|
||||||
{
|
{
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
root_path = state.snapshot.abs_path.clone();
|
let root_path = state.snapshot.abs_path.clone();
|
||||||
for path in paths {
|
for path in paths {
|
||||||
for ancestor in path.ancestors() {
|
for ancestor in path.ancestors() {
|
||||||
if let Some(entry) = state.snapshot.entry_for_path(ancestor) {
|
if let Some(entry) = state.snapshot.entry_for_path(ancestor) {
|
||||||
|
@ -3201,8 +3248,8 @@ impl BackgroundScanner {
|
||||||
while let Some(job) = scan_job_rx.next().await {
|
while let Some(job) = scan_job_rx.next().await {
|
||||||
self.scan_dir(&job).await.log_err();
|
self.scan_dir(&job).await.log_err();
|
||||||
}
|
}
|
||||||
self.state.lock().paths_to_scan.clear();
|
|
||||||
root_path
|
mem::take(&mut self.state.lock().paths_to_scan).len() > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn scan_dirs(
|
async fn scan_dirs(
|
||||||
|
@ -3475,7 +3522,7 @@ impl BackgroundScanner {
|
||||||
.expect("channel is unbounded");
|
.expect("channel is unbounded");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::debug!("defer scanning directory {:?} {:?}", entry.path, entry.kind);
|
log::debug!("defer scanning directory {:?}", entry.path);
|
||||||
entry.kind = EntryKind::UnloadedDir;
|
entry.kind = EntryKind::UnloadedDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3490,26 +3537,10 @@ impl BackgroundScanner {
|
||||||
&self,
|
&self,
|
||||||
root_abs_path: Arc<Path>,
|
root_abs_path: Arc<Path>,
|
||||||
root_canonical_path: PathBuf,
|
root_canonical_path: PathBuf,
|
||||||
mut abs_paths: Vec<PathBuf>,
|
relative_paths: &[Arc<Path>],
|
||||||
|
abs_paths: Vec<PathBuf>,
|
||||||
scan_queue_tx: Option<Sender<ScanJob>>,
|
scan_queue_tx: Option<Sender<ScanJob>>,
|
||||||
) -> Vec<Arc<Path>> {
|
) {
|
||||||
let mut event_paths = Vec::<Arc<Path>>::with_capacity(abs_paths.len());
|
|
||||||
abs_paths.sort_unstable();
|
|
||||||
abs_paths.dedup_by(|a, b| a.starts_with(&b));
|
|
||||||
abs_paths.retain(|abs_path| {
|
|
||||||
if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) {
|
|
||||||
event_paths.push(path.into());
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
log::error!(
|
|
||||||
"unexpected event {:?} for root path {:?}",
|
|
||||||
abs_path,
|
|
||||||
root_canonical_path
|
|
||||||
);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let metadata = futures::future::join_all(
|
let metadata = futures::future::join_all(
|
||||||
abs_paths
|
abs_paths
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -3538,30 +3569,15 @@ impl BackgroundScanner {
|
||||||
// Remove any entries for paths that no longer exist or are being recursively
|
// Remove any entries for paths that no longer exist or are being recursively
|
||||||
// refreshed. Do this before adding any new entries, so that renames can be
|
// refreshed. Do this before adding any new entries, so that renames can be
|
||||||
// detected regardless of the order of the paths.
|
// detected regardless of the order of the paths.
|
||||||
for (path, metadata) in event_paths.iter().zip(metadata.iter()) {
|
for (path, metadata) in relative_paths.iter().zip(metadata.iter()) {
|
||||||
if matches!(metadata, Ok(None)) || doing_recursive_update {
|
if matches!(metadata, Ok(None)) || doing_recursive_update {
|
||||||
log::trace!("remove path {:?}", path);
|
log::trace!("remove path {:?}", path);
|
||||||
state.remove_path(path);
|
state.remove_path(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (path, metadata) in event_paths.iter().zip(metadata.iter()) {
|
for (path, metadata) in relative_paths.iter().zip(metadata.iter()) {
|
||||||
if let (Some(parent), true) = (path.parent(), doing_recursive_update) {
|
|
||||||
if state
|
|
||||||
.snapshot
|
|
||||||
.entry_for_path(parent)
|
|
||||||
.map_or(true, |entry| entry.kind != EntryKind::Dir)
|
|
||||||
{
|
|
||||||
log::debug!(
|
|
||||||
"ignoring event {path:?} within unloaded directory {:?}",
|
|
||||||
parent
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let abs_path: Arc<Path> = root_abs_path.join(&path).into();
|
let abs_path: Arc<Path> = root_abs_path.join(&path).into();
|
||||||
|
|
||||||
match metadata {
|
match metadata {
|
||||||
Ok(Some((metadata, canonical_path))) => {
|
Ok(Some((metadata, canonical_path))) => {
|
||||||
let ignore_stack = state
|
let ignore_stack = state
|
||||||
|
@ -3624,12 +3640,10 @@ impl BackgroundScanner {
|
||||||
|
|
||||||
util::extend_sorted(
|
util::extend_sorted(
|
||||||
&mut state.changed_paths,
|
&mut state.changed_paths,
|
||||||
event_paths.iter().cloned(),
|
relative_paths.iter().cloned(),
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
Ord::cmp,
|
Ord::cmp,
|
||||||
);
|
);
|
||||||
|
|
||||||
event_paths
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_repo_path(&self, path: &Path, snapshot: &mut LocalSnapshot) -> Option<()> {
|
fn remove_repo_path(&self, path: &Path, snapshot: &mut LocalSnapshot) -> Option<()> {
|
||||||
|
@ -3760,25 +3774,22 @@ impl BackgroundScanner {
|
||||||
|
|
||||||
// Scan any directories that were previously ignored and weren't
|
// Scan any directories that were previously ignored and weren't
|
||||||
// previously scanned.
|
// previously scanned.
|
||||||
if was_ignored
|
if was_ignored && !entry.is_ignored && entry.kind.is_unloaded() {
|
||||||
&& !entry.is_ignored
|
let state = self.state.lock();
|
||||||
&& !entry.is_external
|
if state.should_scan_directory(&entry) {
|
||||||
&& entry.kind == EntryKind::UnloadedDir
|
job.scan_queue
|
||||||
{
|
.try_send(ScanJob {
|
||||||
job.scan_queue
|
abs_path: abs_path.clone(),
|
||||||
.try_send(ScanJob {
|
path: entry.path.clone(),
|
||||||
abs_path: abs_path.clone(),
|
ignore_stack: child_ignore_stack.clone(),
|
||||||
path: entry.path.clone(),
|
scan_queue: job.scan_queue.clone(),
|
||||||
ignore_stack: child_ignore_stack.clone(),
|
ancestor_inodes: state
|
||||||
scan_queue: job.scan_queue.clone(),
|
.snapshot
|
||||||
ancestor_inodes: self
|
.ancestor_inodes_for_path(&entry.path),
|
||||||
.state
|
is_external: false,
|
||||||
.lock()
|
})
|
||||||
.snapshot
|
.unwrap();
|
||||||
.ancestor_inodes_for_path(&entry.path),
|
}
|
||||||
is_external: false,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
job.ignore_queue
|
job.ignore_queue
|
||||||
|
|
|
@ -454,6 +454,10 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
|
||||||
"b1.js": "b1",
|
"b1.js": "b1",
|
||||||
"b2.js": "b2",
|
"b2.js": "b2",
|
||||||
},
|
},
|
||||||
|
"c": {
|
||||||
|
"c1.js": "c1",
|
||||||
|
"c2.js": "c2",
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"two": {
|
"two": {
|
||||||
|
@ -521,6 +525,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
|
||||||
(Path::new("one/node_modules/b"), true),
|
(Path::new("one/node_modules/b"), true),
|
||||||
(Path::new("one/node_modules/b/b1.js"), true),
|
(Path::new("one/node_modules/b/b1.js"), true),
|
||||||
(Path::new("one/node_modules/b/b2.js"), true),
|
(Path::new("one/node_modules/b/b2.js"), true),
|
||||||
|
(Path::new("one/node_modules/c"), true),
|
||||||
(Path::new("two"), false),
|
(Path::new("two"), false),
|
||||||
(Path::new("two/x.js"), false),
|
(Path::new("two/x.js"), false),
|
||||||
(Path::new("two/y.js"), false),
|
(Path::new("two/y.js"), false),
|
||||||
|
@ -564,6 +569,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
|
||||||
(Path::new("one/node_modules/b"), true),
|
(Path::new("one/node_modules/b"), true),
|
||||||
(Path::new("one/node_modules/b/b1.js"), true),
|
(Path::new("one/node_modules/b/b1.js"), true),
|
||||||
(Path::new("one/node_modules/b/b2.js"), true),
|
(Path::new("one/node_modules/b/b2.js"), true),
|
||||||
|
(Path::new("one/node_modules/c"), true),
|
||||||
(Path::new("two"), false),
|
(Path::new("two"), false),
|
||||||
(Path::new("two/x.js"), false),
|
(Path::new("two/x.js"), false),
|
||||||
(Path::new("two/y.js"), false),
|
(Path::new("two/y.js"), false),
|
||||||
|
@ -578,6 +584,17 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
|
||||||
// Only the newly-expanded directory is scanned.
|
// Only the newly-expanded directory is scanned.
|
||||||
assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 1);
|
assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// No work happens when files and directories change within an unloaded directory.
|
||||||
|
let prev_fs_call_count = fs.read_dir_call_count() + fs.metadata_call_count();
|
||||||
|
fs.create_dir("/root/one/node_modules/c/lib".as_ref())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
assert_eq!(
|
||||||
|
fs.read_dir_call_count() + fs.metadata_call_count() - prev_fs_call_count,
|
||||||
|
0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue