fs: Replace GlobalWatcher with an OsWatcher that contains one backend (#63898)
Before, `GlobalWatcher` held a native backend and a polling backend, and its methods dispatched to one or the other. After this PR, we have `OsWatcher` which wraps a single watch backend, and `RealFs` and `FakeFs` hold a native `OsWatcher` and a polling `OsWatcher`. This moves the split closer to the place where we decide which backend should be used, and lets us clean up some indirection. A nice bonus is that the "is this backend recursive" data exists in exactly one place. Release Notes: - N/A
This commit is contained in:
1 parent
102a6c5d8a
commit
7f03a472b9
4 files changed
+280
-351
No files matched your search
+25
-10
@@ -446,7 +446,8 @@ pub struct RealFs {
|
||||
this: std::sync::Weak<Self>,
|
||||
bundled_git_binary_path: Option<PathBuf>,
|
||||
executor: BackgroundExecutor,
|
||||
global_watcher: Arc<fs_watcher::GlobalWatcher>,
|
||||
native_watcher: Arc<fs_watcher::OsWatcher>,
|
||||
poll_watcher: Arc<fs_watcher::OsWatcher>,
|
||||
next_job_id: Arc<AtomicUsize>,
|
||||
job_event_subscribers: Arc<Mutex<Vec<JobEventSender>>>,
|
||||
trash: Arc<Mutex<SlotMap<TrashId, TrashedEntry>>>,
|
||||
@@ -556,7 +557,14 @@ impl RealFs {
|
||||
Arc::new_cyclic(|this| Self {
|
||||
this: this.clone(),
|
||||
bundled_git_binary_path: git_binary_path,
|
||||
global_watcher: fs_watcher::GlobalWatcher::new(executor.clone()),
|
||||
native_watcher: fs_watcher::OsWatcher::new(
|
||||
fs_watcher::OsWatcherKind::Native,
|
||||
executor.clone(),
|
||||
),
|
||||
poll_watcher: fs_watcher::OsWatcher::new(
|
||||
fs_watcher::OsWatcherKind::Poll,
|
||||
executor.clone(),
|
||||
),
|
||||
executor,
|
||||
next_job_id: Arc::new(AtomicUsize::new(0)),
|
||||
job_event_subscribers: Arc::new(Mutex::new(Vec::new())),
|
||||
@@ -1164,7 +1172,7 @@ impl Fs for RealFs {
|
||||
}
|
||||
|
||||
fn start_native_watcher(&self) -> Result<()> {
|
||||
self.global_watcher.ensure_native_watcher()
|
||||
self.native_watcher.ensure_backend()
|
||||
}
|
||||
|
||||
fn path_exists(&self, path: &Path) -> bool {
|
||||
@@ -1193,7 +1201,8 @@ impl Fs for RealFs {
|
||||
.expect("RealFs is only constructed inside an Arc");
|
||||
fs_watcher::watch(
|
||||
this,
|
||||
self.global_watcher.clone(),
|
||||
self.native_watcher.clone(),
|
||||
self.poll_watcher.clone(),
|
||||
self.executor.clone(),
|
||||
path,
|
||||
latency,
|
||||
@@ -1419,7 +1428,8 @@ pub struct FakeFs {
|
||||
// Use an unfair lock to ensure tests are deterministic.
|
||||
state: Arc<Mutex<FakeFsState>>,
|
||||
executor: gpui::BackgroundExecutor,
|
||||
global_watcher: Arc<fs_watcher::GlobalWatcher>,
|
||||
native_watcher: Arc<fs_watcher::OsWatcher>,
|
||||
poll_watcher: Arc<fs_watcher::OsWatcher>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
@@ -1443,7 +1453,7 @@ struct FakeFsState {
|
||||
|
||||
/// The kernel's side of file watching, as far as the real watcher code above
|
||||
/// notify can tell: the paths the backend has registered and the callback that
|
||||
/// delivers events for them into the `GlobalWatcher`.
|
||||
/// delivers events for them into the native `OsWatcher`.
|
||||
#[cfg(feature = "test-support")]
|
||||
#[derive(Default)]
|
||||
struct FakeWatches {
|
||||
@@ -1810,19 +1820,23 @@ impl FakeFs {
|
||||
remove_dir_errors: Default::default(),
|
||||
case_sensitive: true,
|
||||
}));
|
||||
let global_watcher = fs_watcher::GlobalWatcher::with_native_backend(
|
||||
let native_watcher = fs_watcher::OsWatcher::with_backend(
|
||||
fs_watcher::OsWatcherKind::Native,
|
||||
executor.clone(),
|
||||
Some(Box::new(FakeWatchBackend {
|
||||
state: state.clone(),
|
||||
})),
|
||||
);
|
||||
state.lock().watches.event_sink = Some(Box::new(global_watcher.native_event_sink()));
|
||||
state.lock().watches.event_sink = Some(Box::new(native_watcher.event_sink()));
|
||||
let poll_watcher =
|
||||
fs_watcher::OsWatcher::new(fs_watcher::OsWatcherKind::Poll, executor.clone());
|
||||
|
||||
let this = Arc::new_cyclic(|this| Self {
|
||||
this: this.clone(),
|
||||
executor: executor.clone(),
|
||||
state,
|
||||
global_watcher,
|
||||
native_watcher,
|
||||
poll_watcher,
|
||||
});
|
||||
|
||||
executor.spawn({
|
||||
@@ -3331,7 +3345,8 @@ impl Fs for FakeFs {
|
||||
// own, so a real debounce would stall every test until `advance_clock`.
|
||||
let (events, watcher) = fs_watcher::watch(
|
||||
self.this.upgrade().unwrap(),
|
||||
self.global_watcher.clone(),
|
||||
self.native_watcher.clone(),
|
||||
self.poll_watcher.clone(),
|
||||
self.executor.clone(),
|
||||
path,
|
||||
Duration::ZERO,
|
||||
|
||||
+214
-310
File diff suppressed because it is too large.
Load diff
@@ -77,13 +77,16 @@ pub fn suggest_on_worktree_updated(
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
let cli_auto_open = workspace.open_in_dev_container();
|
||||
if workspace.open_in_dev_container() {
|
||||
open_dev_container_from_cli(workspace, window, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
let devcontainer_updated = updated_entries.iter().any(|(path, _, _)| {
|
||||
path.as_ref() == devcontainer_dir_path() || path.as_ref() == devcontainer_json_path()
|
||||
});
|
||||
|
||||
if !devcontainer_updated && !cli_auto_open {
|
||||
if !devcontainer_updated {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -97,35 +100,7 @@ pub fn suggest_on_worktree_updated(
|
||||
return;
|
||||
}
|
||||
|
||||
let has_configs = !find_configs_in_snapshot(worktree).is_empty();
|
||||
|
||||
if cli_auto_open {
|
||||
workspace.set_open_in_dev_container(false);
|
||||
let task = cx.spawn_in(window, async move |workspace, cx| {
|
||||
let scans_complete =
|
||||
workspace.update(cx, |workspace, cx| workspace.worktree_scans_complete(cx))?;
|
||||
scans_complete.await;
|
||||
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
let has_configs = workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.worktrees(cx)
|
||||
.any(|wt| !find_configs_in_snapshot(wt.read(cx)).is_empty());
|
||||
if has_configs {
|
||||
cx.on_next_frame(window, move |_workspace, window, cx| {
|
||||
window.dispatch_action(Box::new(zed_actions::OpenDevContainer), cx);
|
||||
});
|
||||
} else {
|
||||
log::warn!("--dev-container: no devcontainer configuration found in project");
|
||||
}
|
||||
})
|
||||
});
|
||||
workspace.set_dev_container_task(task);
|
||||
return;
|
||||
}
|
||||
|
||||
if !has_configs {
|
||||
if find_configs_in_snapshot(worktree).is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -193,3 +168,32 @@ pub fn suggest_on_worktree_updated(
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn open_dev_container_from_cli(
|
||||
workspace: &mut Workspace,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
workspace.set_open_in_dev_container(false);
|
||||
let task = cx.spawn_in(window, async move |workspace, cx| {
|
||||
let scans_complete =
|
||||
workspace.update(cx, |workspace, cx| workspace.worktree_scans_complete(cx))?;
|
||||
scans_complete.await;
|
||||
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
let has_configs = workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.worktrees(cx)
|
||||
.any(|worktree| !find_configs_in_snapshot(worktree.read(cx)).is_empty());
|
||||
if has_configs {
|
||||
cx.on_next_frame(window, move |_workspace, window, cx| {
|
||||
window.dispatch_action(Box::new(zed_actions::OpenDevContainer), cx);
|
||||
});
|
||||
} else {
|
||||
log::warn!("--dev-container: no devcontainer configuration found in project");
|
||||
}
|
||||
})
|
||||
});
|
||||
workspace.set_dev_container_task(task);
|
||||
}
|
||||
@@ -545,6 +545,12 @@ pub fn init(cx: &mut App) {
|
||||
let Some(window) = window else {
|
||||
return;
|
||||
};
|
||||
// A workspace opened with `--dev-container` has its worktrees scanning
|
||||
// before this observer runs, so their update events can't be relied on
|
||||
// to trigger the auto-open.
|
||||
if workspace.open_in_dev_container() {
|
||||
dev_container_suggest::open_dev_container_from_cli(workspace, window, cx);
|
||||
}
|
||||
cx.subscribe_in(
|
||||
workspace.project(),
|
||||
window,
|
||||
|
||||
Reference in new issue
Block a user