From 5c859da4572d892a7df712dde1a0eb37d68f83b7 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 4 May 2023 12:03:10 -0400 Subject: [PATCH 001/168] Only update changed local worktree buffers Co-Authored-By: Antonio Scandurra --- crates/project/src/project.rs | 165 ++++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 67 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b3d432763e..f4b5e728fb 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -122,6 +122,7 @@ pub struct Project { loading_local_worktrees: HashMap, Shared, Arc>>>>, opened_buffers: HashMap, + local_buffer_ids_by_path: HashMap, /// A mapping from a buffer ID to None means that we've started waiting for an ID but haven't finished loading it. /// Used for re-issuing buffer requests when peers temporarily disconnect incomplete_remote_buffers: HashMap>>, @@ -449,6 +450,7 @@ impl Project { incomplete_remote_buffers: Default::default(), loading_buffers_by_path: Default::default(), loading_local_worktrees: Default::default(), + local_buffer_ids_by_path: Default::default(), buffer_snapshots: Default::default(), join_project_response_message_id: 0, client_state: None, @@ -517,6 +519,7 @@ impl Project { shared_buffers: Default::default(), incomplete_remote_buffers: Default::default(), loading_local_worktrees: Default::default(), + local_buffer_ids_by_path: Default::default(), active_entry: None, collaborators: Default::default(), join_project_response_message_id: response.message_id, @@ -1628,6 +1631,18 @@ impl Project { }) .detach(); + if let Some(file) = File::from_dyn(buffer.read(cx).file()) { + if file.is_local { + self.local_buffer_ids_by_path.insert( + ProjectPath { + worktree_id: file.worktree_id(cx), + path: file.path.clone(), + }, + remote_id, + ); + } + } + self.detect_language_for_buffer(buffer, cx); self.register_buffer_with_language_servers(buffer, cx); self.register_buffer_with_copilot(buffer, cx); @@ -4525,7 +4540,7 @@ impl Project { if worktree.read(cx).is_local() { cx.subscribe(worktree, |this, worktree, event, cx| match event { worktree::Event::UpdatedEntries(changes) => { - this.update_local_worktree_buffers(&worktree, cx); + this.update_local_worktree_buffers(&worktree, &changes, cx); this.update_local_worktree_language_servers(&worktree, changes, cx); } worktree::Event::UpdatedGitRepositories(updated_repos) => { @@ -4559,82 +4574,98 @@ impl Project { fn update_local_worktree_buffers( &mut self, worktree_handle: &ModelHandle, + changes: &HashMap, PathChange>, cx: &mut ModelContext, ) { let snapshot = worktree_handle.read(cx).snapshot(); - let mut buffers_to_delete = Vec::new(); let mut renamed_buffers = Vec::new(); + for path in changes.keys() { + let worktree_id = worktree_handle.read(cx).id(); + let project_path = ProjectPath { + worktree_id, + path: path.clone(), + }; - for (buffer_id, buffer) in &self.opened_buffers { - if let Some(buffer) = buffer.upgrade(cx) { - buffer.update(cx, |buffer, cx| { - if let Some(old_file) = File::from_dyn(buffer.file()) { - if old_file.worktree != *worktree_handle { - return; + if let Some(&buffer_id) = self.local_buffer_ids_by_path.get(&project_path) { + if let Some(buffer) = self + .opened_buffers + .get(&buffer_id) + .and_then(|buffer| buffer.upgrade(cx)) + { + buffer.update(cx, |buffer, cx| { + if let Some(old_file) = File::from_dyn(buffer.file()) { + if old_file.worktree != *worktree_handle { + return; + } + + let new_file = + if let Some(entry) = snapshot.entry_for_id(old_file.entry_id) { + File { + is_local: true, + entry_id: entry.id, + mtime: entry.mtime, + path: entry.path.clone(), + worktree: worktree_handle.clone(), + is_deleted: false, + } + } else if let Some(entry) = + snapshot.entry_for_path(old_file.path().as_ref()) + { + File { + is_local: true, + entry_id: entry.id, + mtime: entry.mtime, + path: entry.path.clone(), + worktree: worktree_handle.clone(), + is_deleted: false, + } + } else { + File { + is_local: true, + entry_id: old_file.entry_id, + path: old_file.path().clone(), + mtime: old_file.mtime(), + worktree: worktree_handle.clone(), + is_deleted: true, + } + }; + + let old_path = old_file.abs_path(cx); + if new_file.abs_path(cx) != old_path { + renamed_buffers.push((cx.handle(), old_file.clone())); + self.local_buffer_ids_by_path.remove(&project_path); + self.local_buffer_ids_by_path.insert( + ProjectPath { + worktree_id, + path: path.clone(), + }, + buffer_id, + ); + } + + if new_file != *old_file { + if let Some(project_id) = self.remote_id() { + self.client + .send(proto::UpdateBufferFile { + project_id, + buffer_id: buffer_id as u64, + file: Some(new_file.to_proto()), + }) + .log_err(); + } + + buffer.file_updated(Arc::new(new_file), cx).detach(); + } } - - let new_file = if let Some(entry) = snapshot.entry_for_id(old_file.entry_id) - { - File { - is_local: true, - entry_id: entry.id, - mtime: entry.mtime, - path: entry.path.clone(), - worktree: worktree_handle.clone(), - is_deleted: false, - } - } else if let Some(entry) = - snapshot.entry_for_path(old_file.path().as_ref()) - { - File { - is_local: true, - entry_id: entry.id, - mtime: entry.mtime, - path: entry.path.clone(), - worktree: worktree_handle.clone(), - is_deleted: false, - } - } else { - File { - is_local: true, - entry_id: old_file.entry_id, - path: old_file.path().clone(), - mtime: old_file.mtime(), - worktree: worktree_handle.clone(), - is_deleted: true, - } - }; - - let old_path = old_file.abs_path(cx); - if new_file.abs_path(cx) != old_path { - renamed_buffers.push((cx.handle(), old_file.clone())); - } - - if new_file != *old_file { - if let Some(project_id) = self.remote_id() { - self.client - .send(proto::UpdateBufferFile { - project_id, - buffer_id: *buffer_id as u64, - file: Some(new_file.to_proto()), - }) - .log_err(); - } - - buffer.file_updated(Arc::new(new_file), cx).detach(); - } - } - }); - } else { - buffers_to_delete.push(*buffer_id); + }); + } else { + self.opened_buffers.remove(&buffer_id); + self.local_buffer_ids_by_path.remove(&project_path); + } } } - for buffer_id in buffers_to_delete { - self.opened_buffers.remove(&buffer_id); - } - for (buffer, old_file) in renamed_buffers { self.unregister_buffer_from_language_servers(&buffer, &old_file, cx); self.detect_language_for_buffer(&buffer, cx); From 7f27d72b200cfd6acd2d0fc93de5c58d016b35a6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 16:55:03 +0200 Subject: [PATCH 002/168] Deliver file-system change events in batches in randomized worktree test Co-Authored-By: Julia Risley --- crates/project/src/worktree.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 554304f3d3..cc326690ec 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3944,6 +3944,7 @@ mod tests { let mut snapshots = Vec::new(); let mut mutations_len = operations; + fs.as_fake().pause_events().await; while mutations_len > 1 { randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await; let buffered_event_count = fs.as_fake().buffered_event_count().await; From 48ad3866b7ea01b0eb8662192d8d3ffefbb80ab1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 17:01:11 +0200 Subject: [PATCH 003/168] Randomly mutate worktree in addition to mutating the file-system This ensures that we test the code path that refreshes entries. Co-Authored-By: Julia Risley --- crates/project/src/worktree.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index cc326690ec..bdbfff9a06 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3946,7 +3946,17 @@ mod tests { let mut mutations_len = operations; fs.as_fake().pause_events().await; while mutations_len > 1 { - randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await; + if rng.gen_bool(0.2) { + worktree + .update(cx, |worktree, cx| { + randomly_mutate_worktree(worktree, &mut rng, cx) + }) + .await + .unwrap(); + } else { + randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await; + } + let buffered_event_count = fs.as_fake().buffered_event_count().await; if buffered_event_count > 0 && rng.gen_bool(0.3) { let len = rng.gen_range(0..=buffered_event_count); From 2bc7be9a764b0cd5014fb74d9918e30b82f2c0e5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 May 2023 17:14:33 +0200 Subject: [PATCH 004/168] WIP --- crates/project/src/worktree.rs | 40 ++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index bdbfff9a06..9d03169072 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2399,10 +2399,15 @@ struct BackgroundScanner { status_updates_tx: UnboundedSender, executor: Arc, refresh_requests_rx: channel::Receiver<(Vec, barrier::Sender)>, - prev_state: Mutex<(Snapshot, Vec>)>, + prev_state: Mutex, finished_initial_scan: bool, } +struct BackgroundScannerState { + snapshot: Snapshot, + event_paths: Vec>, +} + impl BackgroundScanner { fn new( snapshot: LocalSnapshot, @@ -2416,7 +2421,10 @@ impl BackgroundScanner { status_updates_tx, executor, refresh_requests_rx, - prev_state: Mutex::new((snapshot.snapshot.clone(), Vec::new())), + prev_state: Mutex::new(BackgroundScannerState { + snapshot: snapshot.snapshot.clone(), + event_paths: Default::default(), + }), snapshot: Mutex::new(snapshot), finished_initial_scan: false, } @@ -2526,7 +2534,12 @@ impl BackgroundScanner { .await { paths.sort_unstable(); - util::extend_sorted(&mut self.prev_state.lock().1, paths, usize::MAX, Ord::cmp); + util::extend_sorted( + &mut self.prev_state.lock().event_paths, + paths, + usize::MAX, + Ord::cmp, + ); } drop(scan_job_tx); self.scan_dirs(false, scan_job_rx).await; @@ -2560,6 +2573,7 @@ impl BackgroundScanner { drop(snapshot); self.send_status_update(false, None); + self.prev_state.lock().event_paths.clear(); } async fn scan_dirs( @@ -2637,14 +2651,18 @@ impl BackgroundScanner { fn send_status_update(&self, scanning: bool, barrier: Option) -> bool { let mut prev_state = self.prev_state.lock(); - let snapshot = self.snapshot.lock().clone(); - let mut old_snapshot = snapshot.snapshot.clone(); - mem::swap(&mut old_snapshot, &mut prev_state.0); - let changed_paths = mem::take(&mut prev_state.1); - let changes = self.build_change_set(&old_snapshot, &snapshot.snapshot, changed_paths); + let new_snapshot = self.snapshot.lock().clone(); + let old_snapshot = mem::replace(&mut prev_state.snapshot, new_snapshot.snapshot.clone()); + + let changes = self.build_change_set( + &old_snapshot, + &new_snapshot.snapshot, + &prev_state.event_paths, + ); + self.status_updates_tx .unbounded_send(ScanState::Updated { - snapshot, + snapshot: new_snapshot, changes, scanning, barrier, @@ -3012,7 +3030,7 @@ impl BackgroundScanner { &self, old_snapshot: &Snapshot, new_snapshot: &Snapshot, - event_paths: Vec>, + event_paths: &[Arc], ) -> HashMap, PathChange> { use PathChange::{Added, AddedOrUpdated, Removed, Updated}; @@ -3022,7 +3040,7 @@ impl BackgroundScanner { let received_before_initialized = !self.finished_initial_scan; for path in event_paths { - let path = PathKey(path); + let path = PathKey(path.clone()); old_paths.seek(&path, Bias::Left, &()); new_paths.seek(&path, Bias::Left, &()); From 0dce5ba7aed61ce488cacb86b3bcc8a59ff115d3 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 10 May 2023 08:15:20 -0700 Subject: [PATCH 005/168] Fix bug with terminal button --- crates/workspace/src/workspace.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2c7836e5df..8a62a28c11 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1554,6 +1554,9 @@ impl Workspace { .map(|ix| (pane.clone(), ix)) }); if let Some((pane, ix)) = result { + if &pane == self.dock_pane() { + Dock::show(self, false, cx); + } pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx)); true } else { From 5de9652a22958d4d24d0fa69cfbb2bf320a7c4aa Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 3 May 2023 16:59:44 -0700 Subject: [PATCH 006/168] Create proof-of-concept SettingStore struct --- Cargo.lock | 1670 ++++++++++++++----------- crates/settings/Cargo.toml | 1 + crates/settings/src/settings.rs | 1 + crates/settings/src/settings_store.rs | 608 +++++++++ crates/util/src/util.rs | 21 + 5 files changed, 1559 insertions(+), 742 deletions(-) create mode 100644 crates/settings/src/settings_store.rs diff --git a/Cargo.lock b/Cargo.lock index eee0873e5b..02370dc98b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ version = "0.1.0" dependencies = [ "auto_update", "editor", - "futures 0.3.25", + "futures 0.3.28", "gpui", "language", "project", @@ -30,7 +30,16 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli", + "gimli 0.26.2", +] + +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli 0.27.2", ] [[package]] @@ -51,7 +60,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.9", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if 1.0.0", "once_cell", "version_check", ] @@ -65,6 +85,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] + [[package]] name = "alacritty_config" version = "0.1.1-dev" @@ -82,7 +111,7 @@ source = "git+https://github.com/zed-industries/alacritty?rev=a51dbe25d67e84d6ed dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -92,7 +121,7 @@ source = "git+https://github.com/zed-industries/alacritty?rev=a51dbe25d67e84d6ed dependencies = [ "alacritty_config", "alacritty_config_derive", - "base64", + "base64 0.13.1", "bitflags", "dirs 4.0.0", "libc", @@ -145,15 +174,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "arrayref" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" @@ -232,9 +261,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" dependencies = [ "async-lock", "async-task", @@ -273,32 +302,31 @@ dependencies = [ [[package]] name = "async-io" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock", "autocfg 1.1.0", + "cfg-if 1.0.0", "concurrent-queue", "futures-lite", - "libc", "log", "parking", "polling", + "rustix 0.37.19", "slab", "socket2", "waker-fn", - "windows-sys 0.42.0", ] [[package]] name = "async-lock" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" dependencies = [ "event-listener", - "futures-lite", ] [[package]] @@ -318,15 +346,15 @@ name = "async-pipe" version = "0.1.3" source = "git+https://github.com/zed-industries/async-pipe-rs?rev=82d00a04211cf4e1236029aa03e6b6ce2a74c553#82d00a04211cf4e1236029aa03e6b6ce2a74c553" dependencies = [ - "futures 0.3.25", + "futures 0.3.28", "log", ] [[package]] name = "async-process" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6381ead98388605d0d9ff86371043b5aa922a3905824244de40dc263a14fcba4" +checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" dependencies = [ "async-io", "async-lock", @@ -335,9 +363,9 @@ dependencies = [ "cfg-if 1.0.0", "event-listener", "futures-lite", - "libc", + "rustix 0.37.19", "signal-hook", - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -348,18 +376,18 @@ checksum = "d7d78656ba01f1b93024b7c3a0467f1608e4be67d725749fdcd7d2c7678fd7a2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "async-recursion" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -372,7 +400,7 @@ dependencies = [ "async-global-executor", "async-io", "async-lock", - "crossbeam-utils 0.8.14", + "crossbeam-utils 0.8.15", "futures-channel", "futures-core", "futures-io", @@ -390,23 +418,24 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", + "pin-project-lite 0.2.9", ] [[package]] name = "async-stream-impl" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -419,7 +448,7 @@ dependencies = [ "filetime", "libc", "pin-project", - "redox_syscall", + "redox_syscall 0.2.16", "xattr", ] @@ -443,13 +472,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.59" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -486,9 +515,9 @@ dependencies = [ [[package]] name = "atomic-waker" -version = "1.0.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" [[package]] name = "atty" @@ -548,9 +577,9 @@ checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" dependencies = [ "async-trait", "axum-core", - "base64", + "base64 0.13.1", "bitflags", - "bytes 1.3.0", + "bytes 1.4.0", "futures-util", "headers", "http", @@ -582,7 +611,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" dependencies = [ "async-trait", - "bytes 1.3.0", + "bytes 1.4.0", "futures-util", "http", "http-body", @@ -598,7 +627,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69034b3b0fd97923eee2ce8a47540edb21e07f48f87f67d44bb4271cec622bdb" dependencies = [ "axum", - "bytes 1.3.0", + "bytes 1.4.0", "futures-util", "http", "mime", @@ -614,16 +643,16 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ - "addr2line", + "addr2line 0.19.0", "cc", "cfg-if 1.0.0", "libc", - "miniz_oxide 0.5.4", - "object 0.29.0", + "miniz_oxide 0.6.2", + "object 0.30.3", "rustc-demangle", ] @@ -637,7 +666,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -647,10 +676,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] -name = "base64ct" -version = "1.5.3" +name = "base64" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bincode" @@ -671,7 +706,7 @@ dependencies = [ "cexpr", "clang-sys", "clap 2.34.0", - "env_logger", + "env_logger 0.9.3", "lazy_static", "lazycell", "log", @@ -707,18 +742,18 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "blocking" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ "async-channel", "async-lock", @@ -726,51 +761,52 @@ dependencies = [ "atomic-waker", "fastrand", "futures-lite", + "log", ] [[package]] name = "borsh" -version = "0.9.3" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" dependencies = [ "borsh-derive", - "hashbrown 0.11.2", + "hashbrown 0.13.2", ] [[package]] name = "borsh-derive" -version = "0.9.3" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", "proc-macro-crate", "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] name = "borsh-derive-internal" -version = "0.9.3" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "borsh-schema-derive-internal" -version = "0.9.3" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -803,45 +839,47 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.17" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" dependencies = [ "memchr", + "serde", ] [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" [[package]] name = "bytecheck" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" +checksum = "13fe11640a23eb24562225322cd3e452b93a3d4091d62fab69c70542fcd17d1f" dependencies = [ "bytecheck_derive", "ptr_meta", + "simdutf8", ] [[package]] name = "bytecheck_derive" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" +checksum = "e31225543cb46f81a7e224762764f4a6a0f097b1db0b175f69e8065efaa42de5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "bytemuck" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" [[package]] name = "byteorder" @@ -861,9 +899,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "call" @@ -874,7 +912,7 @@ dependencies = [ "client", "collections", "fs", - "futures 0.3.25", + "futures 0.3.28", "gpui", "language", "live_kit_client", @@ -894,7 +932,7 @@ checksum = "e54b86398b5852ddd45784b1d9b196b98beb39171821bad4b8b44534a1e87927" dependencies = [ "cap-primitives", "cap-std", - "io-lifetimes", + "io-lifetimes 0.5.3", "winapi 0.3.9", ] @@ -905,13 +943,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb8fca3e81fae1d91a36e9784ca22a39ef623702b5f7904d89dc31f10184a178" dependencies = [ "ambient-authority", - "errno", + "errno 0.2.8", "fs-set-times", "io-extras", - "io-lifetimes", + "io-lifetimes 0.5.3", "ipnet", "maybe-owned", - "rustix", + "rustix 0.33.7", "winapi 0.3.9", "winapi-util", "winx", @@ -935,9 +973,9 @@ checksum = "2247568946095c7765ad2b441a56caffc08027734c634a6d5edda648f04e32eb" dependencies = [ "cap-primitives", "io-extras", - "io-lifetimes", + "io-lifetimes 0.5.3", "ipnet", - "rustix", + "rustix 0.33.7", ] [[package]] @@ -948,7 +986,7 @@ checksum = "c50472b6ebc302af0401fa3fb939694cd8ff00e0d4c9182001e434fc822ab83a" dependencies = [ "cap-primitives", "once_cell", - "rustix", + "rustix 0.33.7", "winx", ] @@ -960,9 +998,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.0.77" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" dependencies = [ "jobserver", ] @@ -990,9 +1028,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.23" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ "iana-time-zone", "js-sys", @@ -1006,9 +1044,9 @@ dependencies = [ [[package]] name = "chunked_transfer" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" +checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" [[package]] name = "cipher" @@ -1021,9 +1059,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.4.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", @@ -1047,9 +1085,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags", @@ -1064,15 +1102,15 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.18" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ - "heck 0.4.0", + "heck 0.4.1", "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1089,7 +1127,7 @@ name = "cli" version = "0.1.0" dependencies = [ "anyhow", - "clap 3.2.23", + "clap 3.2.25", "core-foundation", "core-services", "dirs 3.0.2", @@ -1108,7 +1146,7 @@ dependencies = [ "async-tungstenite", "collections", "db", - "futures 0.3.25", + "futures 0.3.28", "gpui", "image", "lazy_static", @@ -1125,11 +1163,11 @@ dependencies = [ "sum_tree", "tempfile", "thiserror", - "time 0.3.17", + "time 0.3.21", "tiny_http", "url", "util", - "uuid 1.2.2", + "uuid 1.3.2", ] [[package]] @@ -1141,9 +1179,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.49" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" dependencies = [ "cc", ] @@ -1195,18 +1233,18 @@ dependencies = [ "async-tungstenite", "axum", "axum-extra", - "base64", + "base64 0.13.1", "call", - "clap 3.2.23", + "clap 3.2.25", "client", "collections", "ctor", "dashmap", "editor", - "env_logger", + "env_logger 0.9.3", "envy", "fs", - "futures 0.3.25", + "futures 0.3.28", "git", "gpui", "hyper", @@ -1236,7 +1274,7 @@ dependencies = [ "sha-1 0.9.8", "sqlx", "theme", - "time 0.3.17", + "time 0.3.21", "tokio", "tokio-tungstenite", "toml", @@ -1263,7 +1301,7 @@ dependencies = [ "context_menu", "editor", "feedback", - "futures 0.3.25", + "futures 0.3.28", "fuzzy", "gpui", "log", @@ -1299,7 +1337,7 @@ dependencies = [ "collections", "ctor", "editor", - "env_logger", + "env_logger 0.9.3", "fuzzy", "gpui", "picker", @@ -1313,11 +1351,11 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ - "crossbeam-utils 0.8.14", + "crossbeam-utils 0.8.15", ] [[package]] @@ -1342,7 +1380,7 @@ dependencies = [ "collections", "context_menu", "fs", - "futures 0.3.25", + "futures 0.3.28", "gpui", "language", "log", @@ -1366,7 +1404,7 @@ dependencies = [ "context_menu", "copilot", "editor", - "futures 0.3.25", + "futures 0.3.28", "gpui", "settings", "smol", @@ -1445,9 +1483,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] @@ -1472,7 +1510,7 @@ dependencies = [ "cranelift-codegen-shared", "cranelift-entity", "cranelift-isle", - "gimli", + "gimli 0.26.2", "log", "regalloc2", "smallvec", @@ -1550,18 +1588,18 @@ dependencies = [ [[package]] name = "crc" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" [[package]] name = "crc32fast" @@ -1584,35 +1622,35 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.14", + "crossbeam-utils 0.8.15", ] [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.14", + "crossbeam-utils 0.8.15", ] [[package]] name = "crossbeam-epoch" -version = "0.9.13" +version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.14", - "memoffset 0.7.1", + "crossbeam-utils 0.8.15", + "memoffset 0.8.0", "scopeguard", ] @@ -1623,7 +1661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.14", + "crossbeam-utils 0.8.15", ] [[package]] @@ -1639,9 +1677,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if 1.0.0", ] @@ -1673,7 +1711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1693,9 +1731,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.59+curl-7.86.0" +version = "0.4.61+curl-8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cfce34829f448b08f55b7db6d0009e23e2e86a34e8c2b366269bf5799b4a407" +checksum = "14d05c10f541ae6f3bc5b3d923c20001f47db7d5f0b2bc6ad16490133842db79" dependencies = [ "cc", "libc", @@ -1709,9 +1747,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.83" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" dependencies = [ "cc", "cxxbridge-flags", @@ -1721,9 +1759,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.83" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" dependencies = [ "cc", "codespan-reporting", @@ -1731,24 +1769,24 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 2.0.15", ] [[package]] name = "cxxbridge-flags" -version = "1.0.83" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" [[package]] name = "cxxbridge-macro" -version = "1.0.83" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -1761,7 +1799,7 @@ dependencies = [ "hashbrown 0.12.3", "lock_api", "once_cell", - "parking_lot_core 0.9.5", + "parking_lot_core 0.9.7", ] [[package]] @@ -1780,7 +1818,7 @@ dependencies = [ "anyhow", "async-trait", "collections", - "env_logger", + "env_logger 0.9.3", "gpui", "indoc", "lazy_static", @@ -1864,7 +1902,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer 0.10.3", + "block-buffer 0.10.4", "crypto-common", "subtle", ] @@ -1931,9 +1969,9 @@ dependencies = [ [[package]] name = "dotenvy" -version = "0.15.6" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "drag_and_drop" @@ -1957,15 +1995,15 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" [[package]] name = "editor" version = "0.1.0" dependencies = [ - "aho-corasick", + "aho-corasick 0.7.20", "anyhow", "client", "clock", @@ -1975,8 +2013,8 @@ dependencies = [ "ctor", "db", "drag_and_drop", - "env_logger", - "futures 0.3.25", + "env_logger 0.9.3", + "futures 0.3.28", "fuzzy", "git", "glob", @@ -2008,7 +2046,7 @@ dependencies = [ "tree-sitter-html", "tree-sitter-javascript", "tree-sitter-rust", - "tree-sitter-typescript 0.20.2", + "tree-sitter-typescript 0.20.2 (git+https://github.com/tree-sitter/tree-sitter-typescript?rev=5d20856f34315b068c41edaee2ac8a100081d259)", "unindent", "util", "workspace", @@ -2016,15 +2054,15 @@ dependencies = [ [[package]] name = "either" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if 1.0.0", ] @@ -2042,6 +2080,19 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal 0.4.7", + "log", + "regex", + "termcolor", +] + [[package]] name = "envy" version = "0.4.2" @@ -2053,9 +2104,9 @@ dependencies = [ [[package]] name = "erased-serde" -version = "0.3.23" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54558e0ba96fbe24280072642eceb9d7d442e32c7ec0ea9e7ecd7b4ea2cf4e11" +checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569" dependencies = [ "serde", ] @@ -2071,6 +2122,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "errno-dragonfly" version = "0.1.2" @@ -2093,9 +2155,9 @@ dependencies = [ [[package]] name = "euclid" -version = "0.22.7" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b52c2ef4a78da0ba68fbe1fd920627411096d2ac478f7f4c9f3a54ba6705bade" +checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787" dependencies = [ "num-traits", ] @@ -2124,9 +2186,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] @@ -2138,7 +2200,7 @@ dependencies = [ "anyhow", "client", "editor", - "futures 0.3.25", + "futures 0.3.28", "gpui", "human_bytes", "isahc", @@ -2162,11 +2224,11 @@ dependencies = [ [[package]] name = "file-per-thread-logger" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e16290574b39ee41c71aeb90ae960c504ebaf1e2a1c87bd52aa56ed6e1a02f" +checksum = "84f2e425d9790201ba4af4630191feac6dcc98765b118d4d18e91d23c2353866" dependencies = [ - "env_logger", + "env_logger 0.10.0", "log", ] @@ -2176,7 +2238,7 @@ version = "0.1.0" dependencies = [ "ctor", "editor", - "env_logger", + "env_logger 0.9.3", "fuzzy", "gpui", "menu", @@ -2192,14 +2254,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.19" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall", - "windows-sys 0.42.0", + "redox_syscall 0.2.16", + "windows-sys 0.48.0", ] [[package]] @@ -2210,12 +2272,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", - "miniz_oxide 0.6.2", + "miniz_oxide 0.7.1", ] [[package]] @@ -2239,7 +2301,7 @@ dependencies = [ "futures-core", "futures-sink", "pin-project", - "spin 0.9.4", + "spin 0.9.8", ] [[package]] @@ -2336,7 +2398,7 @@ dependencies = [ "async-trait", "collections", "fsevent", - "futures 0.3.25", + "futures 0.3.28", "git2", "gpui", "lazy_static", @@ -2360,8 +2422,8 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df62ee66ee2d532ea8d567b5a3f0d03ecd64636b98bad5be1e93dcc918b92aa" dependencies = [ - "io-lifetimes", - "rustix", + "io-lifetimes 0.5.3", + "rustix 0.33.7", "winapi 0.3.9", ] @@ -2414,9 +2476,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -2429,9 +2491,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -2439,15 +2501,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -2467,15 +2529,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", @@ -2488,32 +2550,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures 0.1.31", "futures-channel", @@ -2548,9 +2610,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -2569,9 +2631,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if 1.0.0", "libc", @@ -2599,6 +2661,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "gimli" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" + [[package]] name = "git" version = "0.1.0" @@ -2607,7 +2675,7 @@ dependencies = [ "async-trait", "clock", "collections", - "futures 0.3.25", + "futures 0.3.28", "git2", "lazy_static", "log", @@ -2640,11 +2708,11 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" dependencies = [ - "aho-corasick", + "aho-corasick 0.7.20", "bstr", "fnv", "log", @@ -2693,11 +2761,11 @@ dependencies = [ "core-text", "ctor", "dhat", - "env_logger", + "env_logger 0.9.3", "etagere", "font-kit", "foreign-types", - "futures 0.3.25", + "futures 0.3.28", "gpui_macros", "image", "itertools", @@ -2726,11 +2794,11 @@ dependencies = [ "smol", "sqlez", "sum_tree", - "time 0.3.17", + "time 0.3.21", "tiny-skia", "usvg", "util", - "uuid 1.2.2", + "uuid 1.3.2", "waker-fn", ] @@ -2740,16 +2808,16 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "h2" -version = "0.3.15" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "fnv", "futures-core", "futures-sink", @@ -2758,7 +2826,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util 0.7.8", "tracing", ] @@ -2768,7 +2836,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash", + "ahash 0.7.6", ] [[package]] @@ -2777,7 +2845,16 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.6", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.3", ] [[package]] @@ -2795,9 +2872,9 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64", + "base64 0.13.1", "bitflags", - "bytes 1.3.0", + "bytes 1.4.0", "headers-core", "http", "httpdate", @@ -2825,9 +2902,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" dependencies = [ "unicode-segmentation", ] @@ -2850,6 +2927,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -2886,11 +2969,11 @@ dependencies = [ [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "fnv", "itoa", ] @@ -2901,7 +2984,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "http", "pin-project-lite 0.2.9", ] @@ -2926,9 +3009,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "human_bytes" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39b528196c838e8b3da8b665e08c30958a6f2ede91d79f2ffcd0d4664b9c64eb" +checksum = "27e2b089f28ad15597b48d8c0a8fe94eeb1c1cb26ca99b6f66ac9582ae10c5e6" [[package]] name = "humantime" @@ -2938,11 +3021,11 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "futures-channel", "futures-core", "futures-util", @@ -2978,7 +3061,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "hyper", "native-tls", "tokio", @@ -2987,16 +3070,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.53" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "winapi 0.3.9", + "windows", ] [[package]] @@ -3021,11 +3104,10 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.18" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" dependencies = [ - "crossbeam-utils 0.8.14", "globset", "lazy_static", "log", @@ -3058,9 +3140,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg 1.1.0", "hashbrown 0.12.3", @@ -3069,9 +3151,9 @@ dependencies = [ [[package]] name = "indoc" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3" +checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" [[package]] name = "install_cli" @@ -3099,7 +3181,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0c937cc9891c12eaa8c63ad347e4a288364b1328b924886970b47a14ab8f8f8" dependencies = [ - "io-lifetimes", + "io-lifetimes 0.5.3", "winapi 0.3.9", ] @@ -3113,6 +3195,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "iovec" version = "0.1.4" @@ -3143,9 +3236,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" [[package]] name = "is-terminal" @@ -3154,11 +3247,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c89a757e762896bdbdfadf2860d0f8b0cea5e363d8cf3e7bdfeb63d1d976352" dependencies = [ "hermit-abi 0.2.6", - "io-lifetimes", - "rustix", + "io-lifetimes 0.5.3", + "rustix 0.33.7", "winapi 0.3.9", ] +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes 1.0.10", + "rustix 0.37.19", + "windows-sys 0.48.0", +] + [[package]] name = "isahc" version = "1.7.2" @@ -3167,7 +3272,7 @@ checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" dependencies = [ "async-channel", "castaway", - "crossbeam-utils 0.8.14", + "crossbeam-utils 0.8.15", "curl", "curl-sys", "encoding_rs", @@ -3197,9 +3302,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "ittapi-rs" @@ -3212,9 +3317,9 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" dependencies = [ "libc", ] @@ -3246,9 +3351,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "68c16e1bfd491478ab155fd8b4896b86f9ede344949b641e61501e07c2b8b4d5" dependencies = [ "wasm-bindgen", ] @@ -3265,7 +3370,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" dependencies = [ - "base64", + "base64 0.13.1", "crypto-common", "digest 0.10.6", "hmac 0.12.1", @@ -3313,9 +3418,9 @@ dependencies = [ "clock", "collections", "ctor", - "env_logger", + "env_logger 0.9.3", "fs", - "futures 0.3.25", + "futures 0.3.28", "fuzzy", "git", "gpui", @@ -3347,7 +3452,7 @@ dependencies = [ "tree-sitter-python", "tree-sitter-ruby", "tree-sitter-rust", - "tree-sitter-typescript 0.20.1", + "tree-sitter-typescript 0.20.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicase", "unindent", "util", @@ -3393,15 +3498,15 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.138" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libgit2-sys" -version = "0.14.0+1.5.0" +version = "0.14.2+1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a00859c70c8a4f7218e6d1cc32875c4b55f6799445b842b0d8ed5e4c3d959b" +checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4" dependencies = [ "cc", "libc", @@ -3448,9 +3553,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" dependencies = [ "cc", "libc", @@ -3469,9 +3574,9 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] @@ -3488,6 +3593,12 @@ version = "0.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5284f00d480e1c39af34e72f8ad60b94f47007e3481cd3b731c1d67190ddc7b7" +[[package]] +name = "linux-raw-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" + [[package]] name = "lipsum" version = "0.8.2" @@ -3507,13 +3618,13 @@ dependencies = [ "async-trait", "block", "byteorder", - "bytes 1.3.0", + "bytes 1.4.0", "cocoa", "collections", "core-foundation", "core-graphics", "foreign-types", - "futures 0.3.25", + "futures 0.3.28", "gpui", "hmac 0.12.1", "jwt", @@ -3538,7 +3649,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "futures 0.3.25", + "futures 0.3.28", "hmac 0.12.1", "jwt", "log", @@ -3580,8 +3691,8 @@ dependencies = [ "async-pipe", "collections", "ctor", - "env_logger", - "futures 0.3.25", + "env_logger 0.9.3", + "futures 0.3.28", "gpui", "log", "lsp-types", @@ -3615,7 +3726,7 @@ dependencies = [ "anyhow", "collections", "editor", - "futures 0.3.25", + "futures 0.3.28", "gpui", "language", "lsp", @@ -3657,9 +3768,9 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "matchit" @@ -3695,7 +3806,7 @@ dependencies = [ "anyhow", "bindgen", "block", - "bytes 1.3.0", + "bytes 1.4.0", "core-foundation", "foreign-types", "metal", @@ -3737,9 +3848,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ "autocfg 1.1.0", ] @@ -3767,9 +3878,9 @@ dependencies = [ [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minimal-lexical" @@ -3798,18 +3909,18 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] @@ -3845,14 +3956,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -3981,7 +4092,7 @@ dependencies = [ "anyhow", "async-compression", "async-tar", - "futures 0.3.25", + "futures 0.3.28", "gpui", "parking_lot 0.11.2", "serde", @@ -3993,9 +4104,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -4012,9 +4123,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" dependencies = [ "winapi 0.3.9", ] @@ -4102,11 +4213,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi 0.2.6", "libc", ] @@ -4116,13 +4227,13 @@ version = "0.5.0" source = "git+https://github.com/KillTheMule/nvim-rs?branch=master#d701c2790dcb2579f8f4d7003ba30e2100a7d25b" dependencies = [ "async-trait", - "futures 0.3.25", + "futures 0.3.28", "log", "parity-tokio-ipc", "rmp", "rmpv", "tokio", - "tokio-util 0.7.4", + "tokio-util 0.7.8", ] [[package]] @@ -4158,18 +4269,18 @@ dependencies = [ [[package]] name = "object" -version = "0.29.0" +version = "0.30.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "opaque-debug" @@ -4179,9 +4290,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.43" +version = "0.10.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020433887e44c27ff16365eaa2d380547a94544ad509aff6eb5b6e3e0b27b376" +checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -4194,13 +4305,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -4211,11 +4322,10 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.78" +version = "0.9.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07d5c8cb6e57b3a3612064d7b18b117912b4ce70955c2504d4b741c9e244b132" +checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" dependencies = [ - "autocfg 1.1.0", "cc", "libc", "pkg-config", @@ -4233,15 +4343,15 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.4.1" +version = "6.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" [[package]] name = "ouroboros" -version = "0.15.5" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbb50b356159620db6ac971c6d5c9ab788c9cc38a6f49619fca2a27acb062ca" +checksum = "e1358bd1558bd2a083fed428ffeda486fbfb323e698cdda7794259d592ca72db" dependencies = [ "aliasable", "ouroboros_macro", @@ -4249,15 +4359,15 @@ dependencies = [ [[package]] name = "ouroboros_macro" -version = "0.15.5" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0d9d1a6191c4f391f87219d1ea42b23f09ee84d64763cd05ee6ea88d9f384d" +checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" dependencies = [ "Inflector", "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4298,7 +4408,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" dependencies = [ - "futures 0.3.25", + "futures 0.3.28", "libc", "log", "rand 0.7.3", @@ -4308,9 +4418,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "parking_lot" @@ -4320,7 +4430,7 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", ] [[package]] @@ -4330,34 +4440,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.5", + "parking_lot_core 0.9.7", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi 0.3.9", ] [[package]] name = "parking_lot_core" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -4373,9 +4483,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "pathfinder_color" @@ -4426,7 +4536,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" dependencies = [ - "base64", + "base64 0.13.1", "once_cell", "regex", ] @@ -4439,9 +4549,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.5.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc8bed3549e0f9b0a2a78bf7c0018237a2cdf085eecbbc048e52612438e4e9d0" +checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" dependencies = [ "thiserror", "ucd-trie", @@ -4449,9 +4559,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" dependencies = [ "fixedbitset", "indexmap", @@ -4463,7 +4573,7 @@ version = "0.1.0" dependencies = [ "ctor", "editor", - "env_logger", + "env_logger 0.9.3", "gpui", "menu", "parking_lot 0.11.2", @@ -4497,7 +4607,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4520,22 +4630,22 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" -version = "1.3.1" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" +checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" dependencies = [ - "base64", + "base64 0.21.0", "indexmap", "line-wrap", + "quick-xml", "serde", - "time 0.3.17", - "xml-rs", + "time 0.3.21", ] [[package]] @@ -4557,7 +4667,7 @@ dependencies = [ "quote", "serde", "serde_derive", - "syn", + "syn 1.0.109", ] [[package]] @@ -4590,16 +4700,18 @@ dependencies = [ [[package]] name = "polling" -version = "2.5.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "166ca89eb77fd403230b9c156612965a81e094ec6ec3aa13663d4c8b113fa748" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg 1.1.0", + "bitflags", "cfg-if 1.0.0", + "concurrent-queue", "libc", "log", - "wepoll-ffi", - "windows-sys 0.42.0", + "pin-project-lite 0.2.9", + "windows-sys 0.48.0", ] [[package]] @@ -4616,7 +4728,7 @@ checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" dependencies = [ "atomic", "crossbeam-queue", - "futures 0.3.25", + "futures 0.3.28", "log", "parking_lot 0.12.1", "pin-project", @@ -4661,7 +4773,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -4678,9 +4790,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -4700,7 +4812,7 @@ dependencies = [ name = "project" version = "0.1.0" dependencies = [ - "aho-corasick", + "aho-corasick 0.7.20", "anyhow", "async-trait", "backtrace", @@ -4710,10 +4822,10 @@ dependencies = [ "copilot", "ctor", "db", - "env_logger", + "env_logger 0.9.3", "fs", "fsevent", - "futures 0.3.25", + "futures 0.3.28", "fuzzy", "git", "glob", @@ -4754,7 +4866,7 @@ dependencies = [ "context_menu", "drag_and_drop", "editor", - "futures 0.3.25", + "futures 0.3.28", "gpui", "menu", "postage", @@ -4773,7 +4885,7 @@ version = "0.1.0" dependencies = [ "anyhow", "editor", - "futures 0.3.25", + "futures 0.3.28", "fuzzy", "gpui", "language", @@ -4810,7 +4922,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de5e2533f59d08fcf364fd374ebda0692a70bd6d7e66ef97f306f45c6c5d8020" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "prost-derive 0.8.0", ] @@ -4820,7 +4932,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "prost-derive 0.9.0", ] @@ -4830,7 +4942,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "heck 0.3.3", "itertools", "lazy_static", @@ -4854,7 +4966,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4867,7 +4979,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4876,7 +4988,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "603bbd6394701d13f3f25aada59c7de9d35a6a5887cfc156181234a44002771b" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "prost 0.8.0", ] @@ -4886,7 +4998,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "prost 0.9.0", ] @@ -4922,7 +5034,7 @@ checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4937,10 +5049,19 @@ dependencies = [ ] [[package]] -name = "quote" -version = "1.0.21" +name = "quick-xml" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] @@ -5032,7 +5153,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.9", ] [[package]] @@ -5046,24 +5167,23 @@ dependencies = [ [[package]] name = "rayon" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e060280438193c554f654141c9ea9417886713b7acd75974c85b18a69a88e0b" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ - "crossbeam-channel 0.5.6", + "crossbeam-channel 0.5.8", "crossbeam-deque", - "crossbeam-utils 0.8.14", + "crossbeam-utils 0.8.15", "num_cpus", ] @@ -5110,14 +5230,23 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.8", - "redox_syscall", + "getrandom 0.2.9", + "redox_syscall 0.2.16", "thiserror", ] @@ -5135,13 +5264,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ - "aho-corasick", + "aho-corasick 1.0.1", "memchr", - "regex-syntax", + "regex-syntax 0.7.1", ] [[package]] @@ -5150,14 +5279,20 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.29", ] [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" [[package]] name = "region" @@ -5182,21 +5317,21 @@ dependencies = [ [[package]] name = "rend" -version = "0.3.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" +checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" dependencies = [ "bytecheck", ] [[package]] name = "reqwest" -version = "0.11.13" +version = "0.11.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" +checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91" dependencies = [ - "base64", - "bytes 1.3.0", + "base64 0.21.0", + "bytes 1.4.0", "encoding_rs", "futures-core", "futures-util", @@ -5244,9 +5379,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.34" +version = "0.8.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3603b7d71ca82644f79b5a06d1220e9a58ede60bd32255f698cb1af8838b8db3" +checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" dependencies = [ "bytemuck", ] @@ -5268,9 +5403,9 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.39" +version = "0.7.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" +checksum = "21499ed91807f07ae081880aabb2ccc0235e9d88011867d984525e9a4c3cfa3e" dependencies = [ "bytecheck", "hashbrown 0.12.3", @@ -5282,13 +5417,13 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.39" +version = "0.7.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" +checksum = "ac1c672430eb41556291981f45ca900a0239ad007242d1cb4b4167af842db666" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5342,12 +5477,12 @@ dependencies = [ "anyhow", "async-lock", "async-tungstenite", - "base64", + "base64 0.13.1", "clock", "collections", "ctor", - "env_logger", - "futures 0.3.25", + "env_logger 0.9.3", + "futures 0.3.28", "gpui", "parking_lot 0.11.2", "prost 0.8.0", @@ -5386,9 +5521,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "6.4.2" +version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "283ffe2f866869428c92e0d61c2f35dfb4355293cdfdc48f49e895c15f1333d1" +checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -5397,22 +5532,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "6.3.1" +version = "6.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31ab23d42d71fb9be1b643fe6765d292c5e14d46912d13f3ae2815ca048ea04d" +checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn", + "syn 1.0.109", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "7.3.0" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1669d81dfabd1b5f8e2856b8bbe146c6192b0ba22162edc738ac0a5de18f054" +checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" dependencies = [ "globset", "sha2 0.10.6", @@ -5421,15 +5556,15 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.27.0" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c321ee4e17d2b7abe12b5d20c1231db708dd36185c8a21e9de5fed6da4dbe9" +checksum = "26bd36b60561ee1fb5ec2817f198b6fd09fa571c897a5e86d1487cfc2b096dfc" dependencies = [ "arrayvec 0.7.2", "borsh", "bytecheck", "byteorder", - "bytes 1.3.0", + "bytes 1.4.0", "num-traits", "rand 0.8.5", "rkyv", @@ -5439,9 +5574,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc-hash" @@ -5465,22 +5600,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "938a344304321a9da4973b9ff4f9f8db9caf4597dfd9dda6a60b523340a0fff0" dependencies = [ "bitflags", - "errno", - "io-lifetimes", + "errno 0.2.8", + "io-lifetimes 0.5.3", "itoa", "libc", - "linux-raw-sys", + "linux-raw-sys 0.0.42", "once_cell", "winapi 0.3.9", ] +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno 0.3.1", + "io-lifetimes 1.0.10", + "libc", + "linux-raw-sys 0.3.7", + "windows-sys 0.48.0", +] + [[package]] name = "rustls" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64", + "base64 0.13.1", "log", "ring", "sct 0.6.1", @@ -5489,9 +5638,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.7" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", @@ -5501,18 +5650,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64", + "base64 0.21.0", ] [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "rustybuzz" @@ -5532,9 +5681,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "safe_arch" @@ -5571,19 +5720,18 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "windows-sys 0.42.0", ] [[package]] name = "schemars" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307" +checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" dependencies = [ "dyn-clone", "schemars_derive", @@ -5593,14 +5741,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9" +checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 1.0.109", ] [[package]] @@ -5617,9 +5765,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "scrypt" @@ -5663,7 +5811,7 @@ dependencies = [ "async-stream", "async-trait", "chrono", - "futures 0.3.25", + "futures 0.3.28", "futures-util", "log", "ouroboros", @@ -5676,10 +5824,10 @@ dependencies = [ "serde_json", "sqlx", "thiserror", - "time 0.3.17", + "time 0.3.21", "tracing", "url", - "uuid 1.2.2", + "uuid 1.3.2", ] [[package]] @@ -5691,7 +5839,7 @@ dependencies = [ "heck 0.3.3", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5704,8 +5852,8 @@ dependencies = [ "rust_decimal", "sea-query-derive", "serde_json", - "time 0.3.17", - "uuid 1.2.2", + "time 0.3.21", + "uuid 1.3.2", ] [[package]] @@ -5719,8 +5867,8 @@ dependencies = [ "sea-query", "serde_json", "sqlx", - "time 0.3.17", - "uuid 1.2.2", + "time 0.3.21", + "uuid 1.3.2", ] [[package]] @@ -5732,7 +5880,7 @@ dependencies = [ "heck 0.3.3", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "thiserror", ] @@ -5755,7 +5903,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 1.0.109", ] [[package]] @@ -5771,7 +5919,7 @@ dependencies = [ "anyhow", "collections", "editor", - "futures 0.3.25", + "futures 0.3.28", "glob", "gpui", "language", @@ -5793,9 +5941,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.7.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ "bitflags", "core-foundation", @@ -5806,9 +5954,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", @@ -5840,22 +5988,22 @@ checksum = "5a9f47faea3cad316faa914d013d24f471cd90bfca1a0c70f05a3f42c6441e99" [[package]] name = "serde" -version = "1.0.148" +version = "1.0.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" +checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.148" +version = "1.0.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" +checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -5866,23 +6014,23 @@ checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "serde_fmt" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2963a69a2b3918c1dc75a45a18bd3fcd1120e31d3f59deb1b2f9b5d5ffb8baa4" +checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4" dependencies = [ "serde", ] [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "indexmap", "itoa", @@ -5892,13 +6040,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -5954,7 +6102,7 @@ dependencies = [ "assets", "collections", "fs", - "futures 0.3.25", + "futures 0.3.28", "glob", "gpui", "json_comments", @@ -5965,6 +6113,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "smallvec", "sqlez", "staff_mode", "theme", @@ -6060,9 +6209,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" dependencies = [ "libc", "signal-hook-registry", @@ -6082,13 +6231,19 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "similar" version = "1.3.0" @@ -6135,18 +6290,18 @@ checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg 1.1.0", ] [[package]] name = "slice-group-by" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" [[package]] name = "sluice" @@ -6202,9 +6357,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi 0.3.9", @@ -6218,9 +6373,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.4" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ "lock_api", ] @@ -6236,14 +6391,14 @@ name = "sqlez" version = "0.1.0" dependencies = [ "anyhow", - "futures 0.3.25", + "futures 0.3.28", "indoc", "lazy_static", "libsqlite3-sys", "parking_lot 0.11.2", "smol", "thread_local", - "uuid 1.2.2", + "uuid 1.3.2", ] [[package]] @@ -6255,14 +6410,14 @@ dependencies = [ "quote", "sqlez", "sqlformat", - "syn", + "syn 1.0.109", ] [[package]] name = "sqlformat" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87e292b4291f154971a43c3774364e2cbcaec599d3f5bf6fa9d122885dbc38a" +checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" dependencies = [ "itertools", "nom", @@ -6271,9 +6426,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "788841def501aabde58d3666fcea11351ec3962e6ea75dbcd05c84a71d68bcd1" +checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188" dependencies = [ "sqlx-core", "sqlx-macros", @@ -6281,16 +6436,16 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbc16ddba161afc99e14d1713a453747a2b07fc097d2009f4c300ec99286105" +checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" dependencies = [ - "ahash", + "ahash 0.7.6", "atoi", - "base64", + "base64 0.13.1", "bitflags", "byteorder", - "bytes 1.3.0", + "bytes 1.4.0", "chrono", "crc", "crossbeam-queue", @@ -6321,7 +6476,7 @@ dependencies = [ "percent-encoding", "rand 0.8.5", "rust_decimal", - "rustls 0.20.7", + "rustls 0.20.8", "rustls-pemfile", "serde", "serde_json", @@ -6332,23 +6487,23 @@ dependencies = [ "sqlx-rt", "stringprep", "thiserror", - "time 0.3.17", + "time 0.3.21", "tokio-stream", "url", - "uuid 1.2.2", - "webpki-roots 0.22.5", + "uuid 1.3.2", + "webpki-roots 0.22.6", "whoami", ] [[package]] name = "sqlx-macros" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b850fa514dc11f2ee85be9d055c512aa866746adfacd1cb42d867d68e6a5b0d9" +checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9" dependencies = [ "dotenvy", "either", - "heck 0.4.0", + "heck 0.4.1", "once_cell", "proc-macro2", "quote", @@ -6356,15 +6511,15 @@ dependencies = [ "sha2 0.10.6", "sqlx-core", "sqlx-rt", - "syn", + "syn 1.0.109", "url", ] [[package]] name = "sqlx-rt" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24c5b2d25fa654cc5f841750b8e1cdedbe21189bf9a9382ee90bfa9dd3562396" +checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" dependencies = [ "once_cell", "tokio", @@ -6425,7 +6580,7 @@ version = "0.1.0" dependencies = [ "arrayvec 0.7.2", "ctor", - "env_logger", + "env_logger 0.9.3", "log", "rand 0.8.5", ] @@ -6467,9 +6622,20 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.105" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", @@ -6478,21 +6644,9 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" - -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sys-info" @@ -6506,14 +6660,14 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.27.3" +version = "0.27.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1620f9573034c573376acc550f3b9a2be96daeb08abb3c12c8523e1cee06e80f" +checksum = "a902e9050fca0a5d6877550b769abd2bd1ce8c04634b941dbe2809735e1a1e33" dependencies = [ "cfg-if 1.0.0", "core-foundation-sys", "libc", - "ntapi 0.4.0", + "ntapi 0.4.1", "once_cell", "rayon", "winapi 0.3.9", @@ -6529,17 +6683,17 @@ dependencies = [ "bitflags", "cap-fs-ext", "cap-std", - "io-lifetimes", - "rustix", + "io-lifetimes 0.5.3", + "rustix 0.33.7", "winapi 0.3.9", "winx", ] [[package]] name = "target-lexicon" -version = "0.12.5" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" +checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" [[package]] name = "tempdir" @@ -6553,16 +6707,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if 1.0.0", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi 0.3.9", + "redox_syscall 0.3.5", + "rustix 0.37.19", + "windows-sys 0.45.0", ] [[package]] @@ -6582,7 +6735,7 @@ dependencies = [ "anyhow", "db", "dirs 4.0.0", - "futures 0.3.25", + "futures 0.3.28", "gpui", "itertools", "lazy_static", @@ -6612,7 +6765,7 @@ dependencies = [ "db", "dirs 4.0.0", "editor", - "futures 0.3.25", + "futures 0.3.28", "gpui", "itertools", "language", @@ -6645,7 +6798,7 @@ dependencies = [ "collections", "ctor", "digest 0.9.0", - "env_logger", + "env_logger 0.9.3", "fs", "gpui", "lazy_static", @@ -6722,22 +6875,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -6748,10 +6901,11 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if 1.0.0", "once_cell", ] @@ -6779,9 +6933,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ "itoa", "serde", @@ -6791,15 +6945,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" dependencies = [ "time-core", ] @@ -6842,28 +6996,27 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.22.0" +version = "1.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" +checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" dependencies = [ "autocfg 1.1.0", - "bytes 1.3.0", + "bytes 1.4.0", "libc", - "memchr", - "mio 0.8.5", + "mio 0.8.6", "num_cpus", "parking_lot 0.12.1", "pin-project-lite 0.2.9", "signal-hook-registry", "socket2", "tokio-macros", - "winapi 0.3.9", + "windows-sys 0.48.0", ] [[package]] @@ -6889,20 +7042,20 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", @@ -6914,16 +7067,16 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls 0.20.7", + "rustls 0.20.8", "tokio", "webpki 0.22.0", ] [[package]] name = "tokio-stream" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite 0.2.9", @@ -6948,7 +7101,7 @@ version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "futures-core", "futures-sink", "log", @@ -6958,11 +7111,11 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ - "bytes 1.3.0", + "bytes 1.4.0", "futures-core", "futures-io", "futures-sink", @@ -6973,9 +7126,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] @@ -6988,8 +7141,8 @@ checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" dependencies = [ "async-stream", "async-trait", - "base64", - "bytes 1.3.0", + "base64 0.13.1", + "bytes 1.4.0", "futures-core", "futures-util", "h2", @@ -7025,7 +7178,7 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util 0.7.8", "tower-layer", "tower-service", "tracing", @@ -7038,7 +7191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ "bitflags", - "bytes 1.3.0", + "bytes 1.4.0", "futures-core", "futures-util", "http", @@ -7077,13 +7230,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -7129,9 +7282,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "matchers", "nu-ansi-term", @@ -7331,9 +7484,9 @@ dependencies = [ [[package]] name = "tree-sitter-typescript" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e8ed0ecb931cdff13c6a13f45ccd615156e2779d9ffb0395864e05505e6e86d" +checksum = "079c695c32d39ad089101c66393aeaca30e967fba3486a91f573d2f0e12d290a" dependencies = [ "cc", "tree-sitter", @@ -7359,9 +7512,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "ttf-parser" @@ -7381,9 +7534,9 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", - "bytes 1.3.0", + "bytes 1.4.0", "http", "httparse", "log", @@ -7400,9 +7553,9 @@ version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", - "bytes 1.3.0", + "bytes 1.4.0", "http", "httparse", "log", @@ -7415,9 +7568,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" @@ -7436,9 +7589,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-bidi-mirroring" @@ -7460,9 +7613,9 @@ checksum = "7f9af028e052a610d99e066b33304625dea9613170a2563314490a4e6ec5cf7f" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" @@ -7481,9 +7634,9 @@ checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc" [[package]] name = "unicode-segmentation" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-vo" @@ -7497,12 +7650,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - [[package]] name = "unicode_categories" version = "0.1.1" @@ -7511,9 +7658,9 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" [[package]] name = "unindent" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ee9362deb4a96cef4d437d1ad49cffc9b9e92d202b6995674e928ce684f112" +checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" [[package]] name = "untrusted" @@ -7545,7 +7692,7 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef8352f317d8f9a918ba5154797fb2a93e2730244041cf7d5be35148266adfa5" dependencies = [ - "base64", + "base64 0.13.1", "data-url", "flate2", "fontdb", @@ -7574,9 +7721,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "util" @@ -7585,7 +7732,7 @@ dependencies = [ "anyhow", "backtrace", "dirs 3.0.2", - "futures 0.3.25", + "futures 0.3.28", "git2", "isahc", "lazy_static", @@ -7610,16 +7757,16 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.9", ] [[package]] name = "uuid" -version = "1.2.2" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +checksum = "4dad5567ad0cf5b760e5665964bec1b47dfd077ba8a2544b513f3556d3d239a2" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.9", "serde", ] @@ -7718,12 +7865,11 @@ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", - "winapi 0.3.9", "winapi-util", ] @@ -7769,10 +7915,10 @@ dependencies = [ "cap-time-ext", "fs-set-times", "io-extras", - "io-lifetimes", - "is-terminal", + "io-lifetimes 0.5.3", + "is-terminal 0.1.0", "lazy_static", - "rustix", + "rustix 0.33.7", "system-interface", "tracing", "wasi-common", @@ -7790,7 +7936,7 @@ dependencies = [ "cap-rand", "cap-std", "io-extras", - "rustix", + "rustix 0.33.7", "thiserror", "tracing", "wiggle", @@ -7799,9 +7945,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "5b6cb788c4e39112fbe1822277ef6fb3c55cd86b95cb3d3c4c1c9597e4ac74b4" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -7809,24 +7955,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "35e522ed4105a9d626d885b35d62501b30d9666283a5c8be12c14a8bdafe7822" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.15", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "083abe15c5d88556b77bdf7aef403625be9e327ad37c62c4e4129af740168163" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -7836,9 +7982,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "358a79a0cb89d21db8120cbfb91392335913e4890665b1a7981d9e956903b434" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7846,28 +7992,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "a901d592cafaa4d711bc324edfaff879ac700b19c3dfd60058d2b445be2691eb" [[package]] name = "wasm-encoder" -version = "0.20.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05632e0a66a6ed8cca593c24223aabd6262f256c3693ad9822c315285f010614" +checksum = "d05d0b6fcd0aeb98adf16e7975331b3c17222aa815148f5b976370ce589d80ef" dependencies = [ "leb128", ] @@ -7922,12 +8068,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1df23c642e1376892f3b72f311596976979cbf8b85469680cdd3a8a063d12a2" dependencies = [ "anyhow", - "base64", + "base64 0.13.1", "bincode", "directories-next", "file-per-thread-logger", "log", - "rustix", + "rustix 0.33.7", "serde", "sha2 0.9.9", "toml", @@ -7947,7 +8093,7 @@ dependencies = [ "cranelift-frontend", "cranelift-native", "cranelift-wasm", - "gimli", + "gimli 0.26.2", "log", "more-asserts", "object 0.28.4", @@ -7965,7 +8111,7 @@ checksum = "839d2820e4b830f4b9e7aa08d4c0acabf4a5036105d639f6dfa1c6891c73bdc6" dependencies = [ "anyhow", "cranelift-entity", - "gimli", + "gimli 0.26.2", "indexmap", "log", "more-asserts", @@ -7984,7 +8130,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3248be3c4911233535356025f6562193614a40155ee9094bb6a2b43f0dc82803" dependencies = [ "cc", - "rustix", + "rustix 0.33.7", "winapi 0.3.9", ] @@ -7994,18 +8140,18 @@ version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef0a0bcbfa18b946d890078ba0e1bc76bcc53eccfb40806c0020ec29dcd1bd49" dependencies = [ - "addr2line", + "addr2line 0.17.0", "anyhow", "bincode", "cfg-if 1.0.0", "cpp_demangle", - "gimli", + "gimli 0.26.2", "ittapi-rs", "log", "object 0.28.4", "region", "rustc-demangle", - "rustix", + "rustix 0.33.7", "serde", "target-lexicon", "thiserror", @@ -8023,7 +8169,7 @@ checksum = "4f4779d976206c458edd643d1ac622b6c37e4a0800a8b1d25dfbf245ac2f2cac" dependencies = [ "lazy_static", "object 0.28.4", - "rustix", + "rustix 0.33.7", ] [[package]] @@ -8045,7 +8191,7 @@ dependencies = [ "more-asserts", "rand 0.8.5", "region", - "rustix", + "rustix 0.33.7", "thiserror", "wasmtime-environ", "wasmtime-fiber", @@ -8089,9 +8235,9 @@ dependencies = [ [[package]] name = "wast" -version = "50.0.0" +version = "57.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2cbb59d4ac799842791fe7e806fa5dbbf6b5554d538e51cc8e176db6ff0ae34" +checksum = "6eb0f5ed17ac4421193c7477da05892c2edafd67f9639e3c11a82086416662dc" dependencies = [ "leb128", "memchr", @@ -8101,18 +8247,18 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.52" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584aaf7a1ecf4d383bbe1a25eeab0cbb8ff96acc6796707ff65cde48f4632f15" +checksum = "ab9ab0d87337c3be2bb6fc5cd331c4ba9fd6bcb4ee85048a0dd59ed9ecf92e53" dependencies = [ - "wast 50.0.0", + "wast 57.0.0", ] [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "16b5f940c7edfdc6d12126d98c9ef4d1b3d470011c47c76a6581df47ad9ba721" dependencies = [ "js-sys", "wasm-bindgen", @@ -8149,9 +8295,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki 0.22.0", ] @@ -8182,20 +8328,11 @@ dependencies = [ "workspace", ] -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - [[package]] name = "which" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" dependencies = [ "either", "libc", @@ -8204,11 +8341,10 @@ dependencies = [ [[package]] name = "whoami" -version = "1.2.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6631b6a2fd59b1841b622e8f1a7ad241ef0a46f2d580464ce8140ac94cbd571" +checksum = "2c70234412ca409cc04e864e89523cb0fc37f5e1344ebed5a3ebf4192b6b9f68" dependencies = [ - "bumpalo", "wasm-bindgen", "web-sys", ] @@ -8235,11 +8371,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63a1dccd6b3fbd9a27417f5d30ce9aa3ee9cf529aad453abbf88a49c5d605b79" dependencies = [ "anyhow", - "heck 0.4.0", + "heck 0.4.1", "proc-macro2", "quote", "shellexpand", - "syn", + "syn 1.0.109", "witx", ] @@ -8251,7 +8387,7 @@ checksum = "f1c368d57d9560c34deaa67e06b0953ccf65edb906c525e5a2c866c849b48ec2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wiggle-generate", ] @@ -8299,16 +8435,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.36.1" +name = "windows" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", + "windows-targets 0.48.0", ] [[package]] @@ -8317,86 +8449,146 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winreg" @@ -8414,7 +8606,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d5973cb8cd94a77d03ad7e23bbe14889cb29805da1cec0e4aff75e21aebded" dependencies = [ "bitflags", - "io-lifetimes", + "io-lifetimes 0.5.3", "winapi 0.3.9", ] @@ -8445,7 +8637,7 @@ version = "0.1.0" dependencies = [ "anyhow", "assets", - "async-recursion 1.0.0", + "async-recursion 1.0.4", "bincode", "call", "client", @@ -8453,9 +8645,9 @@ dependencies = [ "context_menu", "db", "drag_and_drop", - "env_logger", + "env_logger 0.9.3", "fs", - "futures 0.3.25", + "futures 0.3.28", "gpui", "indoc", "install_cli", @@ -8474,7 +8666,7 @@ dependencies = [ "terminal", "theme", "util", - "uuid 1.2.2", + "uuid 1.3.2", ] [[package]] @@ -8496,12 +8688,6 @@ dependencies = [ "libc", ] -[[package]] -name = "xml-rs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" - [[package]] name = "xmlparser" version = "0.13.5" @@ -8558,12 +8744,12 @@ dependencies = [ "db", "diagnostics", "editor", - "env_logger", + "env_logger 0.9.3", "feedback", "file_finder", "fs", "fsevent", - "futures 0.3.25", + "futures 0.3.28", "fuzzy", "go_to_line", "gpui", @@ -8631,13 +8817,13 @@ dependencies = [ "tree-sitter-rust", "tree-sitter-scheme", "tree-sitter-toml", - "tree-sitter-typescript 0.20.2", + "tree-sitter-typescript 0.20.2 (git+https://github.com/tree-sitter/tree-sitter-typescript?rev=5d20856f34315b068c41edaee2ac8a100081d259)", "tree-sitter-yaml", "unindent", "url", "urlencoding", "util", - "uuid 1.2.2", + "uuid 1.3.2", "vim", "welcome", "workspace", @@ -8654,14 +8840,13 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.15", ] [[package]] @@ -8685,10 +8870,11 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.4+zstd.1.5.2" +version = "2.0.8+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa202f2ef00074143e219d15b62ffc317d17cc33909feac471c044087cad7b0" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" dependencies = [ "cc", "libc", + "pkg-config", ] diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 9566103960..b38aa7c42d 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -31,6 +31,7 @@ schemars = "0.8" serde.workspace = true serde_derive.workspace = true serde_json.workspace = true +smallvec.workspace = true toml = "0.5" tree-sitter = "*" tree-sitter-json = "*" diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 7ba52fcb5e..9475d0205f 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -1,5 +1,6 @@ mod keymap_file; pub mod settings_file; +pub mod settings_store; pub mod watched_json; use anyhow::{bail, Result}; diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs new file mode 100644 index 0000000000..61f689c50b --- /dev/null +++ b/crates/settings/src/settings_store.rs @@ -0,0 +1,608 @@ +use anyhow::{anyhow, Result}; +use collections::{hash_map, BTreeMap, HashMap, HashSet}; +use schemars::JsonSchema; +use serde::{de::DeserializeOwned, Serialize}; +use serde_json::value::RawValue; +use smallvec::SmallVec; +use std::{ + any::{type_name, Any, TypeId}, + cmp::Ordering, + fmt::Debug, + mem, + path::Path, + sync::Arc, +}; +use util::{merge_non_null_json_value_into, ResultExt as _}; + +/// A value that can be defined as a user setting. +/// +/// Settings can be loaded from a combination of multiple JSON files. +pub trait Setting: 'static + Debug { + /// The name of a field within the JSON file from which this setting should + /// be deserialized. If this is `None`, then the setting will be deserialized + /// from the root object. + const FIELD_NAME: Option<&'static str> = None; + + /// The type that is stored in an individual JSON file. + type FileContent: DeserializeOwned + JsonSchema; + + /// The logic for combining together values from one or more JSON files into the + /// final value for this setting. + /// + /// The user values are ordered from least specific (the global settings file) + /// to most specific (the innermost local settings file). + fn load(default_value: &Self::FileContent, user_values: &[&Self::FileContent]) -> Self; + + fn load_via_json_merge( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + ) -> Self + where + Self: DeserializeOwned, + Self::FileContent: Serialize, + { + let mut merged = serde_json::Value::Null; + for value in [default_value].iter().chain(user_values) { + merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged); + } + serde_json::from_value(merged).unwrap() + } +} + +/// A set of strongly-typed setting values defined via multiple JSON files. +#[derive(Default)] +pub struct SettingsStore { + setting_keys: Vec<(Option<&'static str>, TypeId)>, + setting_values: HashMap>, + default_deserialized_settings: DeserializedSettingMap, + user_deserialized_settings: Option, + local_deserialized_settings: BTreeMap, DeserializedSettingMap>, + changed_setting_types: HashSet, +} + +#[derive(Debug)] +struct SettingValue { + global_value: Option, + local_values: Vec<(Arc, T)>, +} + +trait AnySettingValue: Debug { + fn setting_type_name(&self) -> &'static str; + fn deserialize_setting(&self, json: &str) -> Result; + fn load_setting( + &self, + default_value: &DeserializedSetting, + custom: &[&DeserializedSetting], + ) -> Box; + fn value_for_path(&self, path: Option<&Path>) -> &dyn Any; + fn set_global_value(&mut self, value: Box); + fn set_local_value(&mut self, path: Arc, value: Box); +} + +struct DeserializedSetting(Box); + +type DeserializedSettingMap = HashMap; + +impl SettingsStore { + /// Add a new type of setting to the store. + /// + /// This should be done before any settings are loaded. + pub fn register_setting(&mut self) { + let type_id = TypeId::of::(); + + let entry = self.setting_values.entry(type_id); + if matches!(entry, hash_map::Entry::Occupied(_)) { + panic!("duplicate setting type: {}", type_name::()); + } + entry.or_insert(Box::new(SettingValue:: { + global_value: None, + local_values: Vec::new(), + })); + + match self + .setting_keys + .binary_search_by_key(&T::FIELD_NAME, |e| e.0) + { + Ok(ix) | Err(ix) => self.setting_keys.insert(ix, (T::FIELD_NAME, type_id)), + } + } + + /// Get the value of a setting. + /// + /// Panics if settings have not yet been loaded, or there is no default + /// value for this setting. + pub fn get(&self, path: Option<&Path>) -> &T { + self.setting_values + .get(&TypeId::of::()) + .unwrap() + .value_for_path(path) + .downcast_ref::() + .unwrap() + } + + /// Set the default settings via a JSON string. + /// + /// The string should contain a JSON object with a default value for every setting. + pub fn set_default_settings(&mut self, default_settings_content: &str) -> Result<()> { + self.default_deserialized_settings = self.load_setting_map(default_settings_content)?; + if self.default_deserialized_settings.len() != self.setting_keys.len() { + return Err(anyhow!( + "default settings file is missing fields: {:?}", + self.setting_keys + .iter() + .filter(|(_, type_id)| !self + .default_deserialized_settings + .contains_key(type_id)) + .map(|(name, _)| *name) + .collect::>() + )); + } + self.recompute_values(false, None, None); + Ok(()) + } + + /// Set the user settings via a JSON string. + pub fn set_user_settings(&mut self, user_settings_content: &str) -> Result<()> { + let user_settings = self.load_setting_map(user_settings_content)?; + let old_user_settings = + mem::replace(&mut self.user_deserialized_settings, Some(user_settings)); + self.recompute_values(true, None, old_user_settings); + Ok(()) + } + + /// Add or remove a set of local settings via a JSON string. + pub fn set_local_settings( + &mut self, + path: Arc, + settings_content: Option<&str>, + ) -> Result<()> { + let removed_map = if let Some(settings_content) = settings_content { + self.local_deserialized_settings + .insert(path.clone(), self.load_setting_map(settings_content)?); + None + } else { + self.local_deserialized_settings.remove(&path) + }; + self.recompute_values(true, Some(&path), removed_map); + Ok(()) + } + + fn recompute_values( + &mut self, + user_settings_changed: bool, + changed_local_path: Option<&Path>, + old_settings_map: Option, + ) { + // Identify all of the setting types that have changed. + let new_settings_map = if let Some(changed_path) = changed_local_path { + &self.local_deserialized_settings.get(changed_path).unwrap() + } else if user_settings_changed { + self.user_deserialized_settings.as_ref().unwrap() + } else { + &self.default_deserialized_settings + }; + self.changed_setting_types.clear(); + self.changed_setting_types.extend(new_settings_map.keys()); + if let Some(previous_settings_map) = old_settings_map { + self.changed_setting_types + .extend(previous_settings_map.keys()); + } + + // Reload the global and local values for every changed setting. + let mut user_values_stack = Vec::<&DeserializedSetting>::new(); + for setting_type_id in self.changed_setting_types.iter() { + let setting_value = self.setting_values.get_mut(setting_type_id).unwrap(); + + // Build the prioritized list of deserialized values to pass to the setting's + // load function. + user_values_stack.clear(); + if let Some(user_settings) = &self.user_deserialized_settings { + if let Some(user_value) = user_settings.get(setting_type_id) { + user_values_stack.push(&user_value); + } + } + + // If the global settings file changed, reload the global value for the field. + if changed_local_path.is_none() { + let global_value = setting_value.load_setting( + &self.default_deserialized_settings[setting_type_id], + &user_values_stack, + ); + setting_value.set_global_value(global_value); + } + + // Reload the local values for the setting. + let user_value_stack_len = user_values_stack.len(); + for (path, deserialized_values) in &self.local_deserialized_settings { + // If a local settings file changed, then avoid recomputing local + // settings for any path outside of that directory. + if changed_local_path.map_or(false, |changed_local_path| { + !path.starts_with(changed_local_path) + }) { + continue; + } + + // Ignore recomputing settings for any path that hasn't customized that setting. + let Some(deserialized_value) = deserialized_values.get(setting_type_id) else { + continue; + }; + + // Build a stack of all of the local values for that setting. + user_values_stack.truncate(user_value_stack_len); + for (preceding_path, preceding_deserialized_values) in + &self.local_deserialized_settings + { + if preceding_path >= path { + break; + } + if !path.starts_with(preceding_path) { + continue; + } + + if let Some(preceding_deserialized_value) = + preceding_deserialized_values.get(setting_type_id) + { + user_values_stack.push(&*preceding_deserialized_value); + } + } + user_values_stack.push(&*deserialized_value); + + // Load the local value for the field. + let local_value = setting_value.load_setting( + &self.default_deserialized_settings[setting_type_id], + &user_values_stack, + ); + setting_value.set_local_value(path.clone(), local_value); + } + } + } + + /// Deserialize the given JSON string into a map keyed by setting type. + /// + /// Returns an error if the string doesn't contain a valid JSON object. + fn load_setting_map(&self, json: &str) -> Result { + let mut map = DeserializedSettingMap::default(); + let settings_content_by_key: BTreeMap<&str, &RawValue> = serde_json::from_str(json)?; + let mut setting_types_by_key = self.setting_keys.iter().peekable(); + + // Load all of the fields that don't have a key. + while let Some((setting_key, setting_type_id)) = setting_types_by_key.peek() { + if setting_key.is_some() { + break; + } + setting_types_by_key.next(); + if let Some(deserialized_value) = self + .setting_values + .get(setting_type_id) + .unwrap() + .deserialize_setting(json) + .log_err() + { + map.insert(*setting_type_id, deserialized_value); + } + } + + // For each key in the file, load all of the settings that belong to that key. + for (key, key_content) in settings_content_by_key { + while let Some((setting_key, setting_type_id)) = setting_types_by_key.peek() { + let setting_key = setting_key.expect("setting names are ordered"); + match setting_key.cmp(key) { + Ordering::Less => { + setting_types_by_key.next(); + continue; + } + Ordering::Greater => break, + Ordering::Equal => { + if let Some(deserialized_value) = self + .setting_values + .get(setting_type_id) + .unwrap() + .deserialize_setting(key_content.get()) + .log_err() + { + map.insert(*setting_type_id, deserialized_value); + } + setting_types_by_key.next(); + } + } + } + } + Ok(map) + } +} + +impl AnySettingValue for SettingValue { + fn setting_type_name(&self) -> &'static str { + type_name::() + } + + fn load_setting( + &self, + default_value: &DeserializedSetting, + user_values: &[&DeserializedSetting], + ) -> Box { + let default_value = default_value.0.downcast_ref::().unwrap(); + let values: SmallVec<[&T::FileContent; 6]> = user_values + .iter() + .map(|value| value.0.downcast_ref().unwrap()) + .collect(); + Box::new(T::load(default_value, &values)) + } + + fn deserialize_setting(&self, json: &str) -> Result { + let value = serde_json::from_str::(json)?; + Ok(DeserializedSetting(Box::new(value))) + } + + fn value_for_path(&self, path: Option<&Path>) -> &dyn Any { + if let Some(path) = path { + for (settings_path, value) in self.local_values.iter().rev() { + if path.starts_with(&settings_path) { + return value; + } + } + } + self.global_value.as_ref().unwrap() + } + + fn set_global_value(&mut self, value: Box) { + self.global_value = Some(*value.downcast().unwrap()); + } + + fn set_local_value(&mut self, path: Arc, value: Box) { + let value = *value.downcast().unwrap(); + match self.local_values.binary_search_by_key(&&path, |e| &e.0) { + Ok(ix) => self.local_values[ix].1 = value, + Err(ix) => self.local_values.insert(ix, (path, value)), + } + } +} + +impl Debug for SettingsStore { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + return f + .debug_struct("SettingsStore") + .field( + "setting_value_sets_by_type", + &self + .setting_values + .values() + .map(|set| (set.setting_type_name(), set)) + .collect::>(), + ) + .finish_non_exhaustive(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_derive::Deserialize; + + #[test] + fn test_settings_store() { + let mut store = SettingsStore::default(); + store.register_setting::(); + store.register_setting::(); + store.register_setting::(); + + // error - missing required field in default settings + store + .set_default_settings( + r#"{ + "user": { + "name": "John Doe", + "age": 30, + "staff": false + } + }"#, + ) + .unwrap_err(); + + // error - type error in default settings + store + .set_default_settings( + r#"{ + "turbo": "the-wrong-type", + "user": { + "name": "John Doe", + "age": 30, + "staff": false + } + }"#, + ) + .unwrap_err(); + + // valid default settings. + store + .set_default_settings( + r#"{ + "turbo": false, + "user": { + "name": "John Doe", + "age": 30, + "staff": false + } + }"#, + ) + .unwrap(); + + assert_eq!(store.get::(None), &TurboSetting(false)); + assert_eq!( + store.get::(None), + &UserSettings { + name: "John Doe".to_string(), + age: 30, + staff: false, + } + ); + assert_eq!( + store.get::(None), + &MultiKeySettings { + key1: String::new(), + key2: String::new(), + } + ); + + store + .set_user_settings( + r#"{ + "turbo": true, + "user": { "age": 31 }, + "key1": "a" + }"#, + ) + .unwrap(); + + assert_eq!(store.get::(None), &TurboSetting(true)); + assert_eq!( + store.get::(None), + &UserSettings { + name: "John Doe".to_string(), + age: 31, + staff: false + } + ); + + store + .set_local_settings( + Path::new("/root1").into(), + Some(r#"{ "user": { "staff": true } }"#), + ) + .unwrap(); + store + .set_local_settings( + Path::new("/root1/subdir").into(), + Some(r#"{ "user": { "name": "Jane Doe" } }"#), + ) + .unwrap(); + + store + .set_local_settings( + Path::new("/root2").into(), + Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#), + ) + .unwrap(); + + assert_eq!( + store.get::(Some(Path::new("/root1/something"))), + &UserSettings { + name: "John Doe".to_string(), + age: 31, + staff: true + } + ); + assert_eq!( + store.get::(Some(Path::new("/root1/subdir/something"))), + &UserSettings { + name: "Jane Doe".to_string(), + age: 31, + staff: true + } + ); + assert_eq!( + store.get::(Some(Path::new("/root2/something"))), + &UserSettings { + name: "John Doe".to_string(), + age: 42, + staff: false + } + ); + assert_eq!( + store.get::(Some(Path::new("/root2/something"))), + &MultiKeySettings { + key1: "a".to_string(), + key2: "b".to_string(), + } + ); + } + + #[derive(Debug, PartialEq, Deserialize)] + struct UserSettings { + name: String, + age: u32, + staff: bool, + } + + #[derive(Serialize, Deserialize, JsonSchema)] + struct UserSettingsJson { + name: Option, + age: Option, + staff: Option, + } + + impl Setting for UserSettings { + const FIELD_NAME: Option<&'static str> = Some("user"); + type FileContent = UserSettingsJson; + + fn load(default_value: &UserSettingsJson, user_values: &[&UserSettingsJson]) -> Self { + Self::load_via_json_merge(default_value, user_values) + } + } + + #[derive(Debug, Deserialize, PartialEq)] + struct TurboSetting(bool); + + impl Setting for TurboSetting { + const FIELD_NAME: Option<&'static str> = Some("turbo"); + type FileContent = Option; + + fn load(default_value: &Option, user_values: &[&Option]) -> Self { + Self::load_via_json_merge(default_value, user_values) + } + } + + #[derive(Clone, Debug, PartialEq, Deserialize)] + struct MultiKeySettings { + #[serde(default)] + key1: String, + #[serde(default)] + key2: String, + } + + #[derive(Serialize, Deserialize, JsonSchema)] + struct MultiKeySettingsJson { + key1: Option, + key2: Option, + } + + impl Setting for MultiKeySettings { + type FileContent = MultiKeySettingsJson; + + fn load( + default_value: &MultiKeySettingsJson, + user_values: &[&MultiKeySettingsJson], + ) -> Self { + Self::load_via_json_merge(default_value, user_values) + } + } + + #[derive(Debug, Deserialize)] + struct JournalSettings { + pub path: String, + pub hour_format: HourFormat, + } + + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] + #[serde(rename_all = "snake_case")] + enum HourFormat { + Hour12, + Hour24, + } + + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] + struct JournalSettingsJson { + pub path: Option, + pub hour_format: Option, + } + + impl Setting for JournalSettings { + const FIELD_NAME: Option<&'static str> = Some("journal"); + + type FileContent = JournalSettingsJson; + + fn load(default_value: &JournalSettingsJson, user_values: &[&JournalSettingsJson]) -> Self { + Self::load_via_json_merge(default_value, user_values) + } + } +} diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 903b0eec59..fafd9d5d3b 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -93,6 +93,27 @@ pub fn merge_json_value_into(source: serde_json::Value, target: &mut serde_json: } } +pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) { + use serde_json::Value; + if let Value::Object(source_object) = source { + let target_object = if let Value::Object(target) = target { + target + } else { + *target = Value::Object(Default::default()); + target.as_object_mut().unwrap() + }; + for (key, value) in source_object { + if let Some(target) = target_object.get_mut(&key) { + merge_non_null_json_value_into(value, target); + } else if !value.is_null() { + target_object.insert(key.clone(), value); + } + } + } else if !source.is_null() { + *target = source + } +} + pub trait ResultExt { type Ok; From 24e06334d00f9823336dbb321615e365a07538da Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 8 May 2023 14:05:38 -0700 Subject: [PATCH 007/168] Allow registering additional settings after loading global settings --- crates/settings/src/settings_store.rs | 230 +++++++++++++++----------- 1 file changed, 135 insertions(+), 95 deletions(-) diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 61f689c50b..7c8c2a7522 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -1,12 +1,10 @@ use anyhow::{anyhow, Result}; use collections::{hash_map, BTreeMap, HashMap, HashSet}; use schemars::JsonSchema; -use serde::{de::DeserializeOwned, Serialize}; -use serde_json::value::RawValue; +use serde::{de::DeserializeOwned, Deserialize as _, Serialize}; use smallvec::SmallVec; use std::{ any::{type_name, Any, TypeId}, - cmp::Ordering, fmt::Debug, mem, path::Path, @@ -18,13 +16,13 @@ use util::{merge_non_null_json_value_into, ResultExt as _}; /// /// Settings can be loaded from a combination of multiple JSON files. pub trait Setting: 'static + Debug { - /// The name of a field within the JSON file from which this setting should + /// The name of a key within the JSON file from which this setting should /// be deserialized. If this is `None`, then the setting will be deserialized /// from the root object. - const FIELD_NAME: Option<&'static str> = None; + const KEY: Option<&'static str> = None; /// The type that is stored in an individual JSON file. - type FileContent: DeserializeOwned + JsonSchema; + type FileContent: Serialize + DeserializeOwned + JsonSchema; /// The logic for combining together values from one or more JSON files into the /// final value for this setting. @@ -52,9 +50,8 @@ pub trait Setting: 'static + Debug { /// A set of strongly-typed setting values defined via multiple JSON files. #[derive(Default)] pub struct SettingsStore { - setting_keys: Vec<(Option<&'static str>, TypeId)>, setting_values: HashMap>, - default_deserialized_settings: DeserializedSettingMap, + default_deserialized_settings: Option, user_deserialized_settings: Option, local_deserialized_settings: BTreeMap, DeserializedSettingMap>, changed_setting_types: HashSet, @@ -67,8 +64,9 @@ struct SettingValue { } trait AnySettingValue: Debug { + fn key(&self) -> Option<&'static str>; fn setting_type_name(&self) -> &'static str; - fn deserialize_setting(&self, json: &str) -> Result; + fn deserialize_setting(&self, json: &serde_json::Value) -> Result; fn load_setting( &self, default_value: &DeserializedSetting, @@ -81,29 +79,40 @@ trait AnySettingValue: Debug { struct DeserializedSetting(Box); -type DeserializedSettingMap = HashMap; +struct DeserializedSettingMap { + untyped: serde_json::Value, + typed: HashMap, +} impl SettingsStore { /// Add a new type of setting to the store. - /// - /// This should be done before any settings are loaded. pub fn register_setting(&mut self) { - let type_id = TypeId::of::(); + let setting_type_id = TypeId::of::(); - let entry = self.setting_values.entry(type_id); + let entry = self.setting_values.entry(setting_type_id); if matches!(entry, hash_map::Entry::Occupied(_)) { panic!("duplicate setting type: {}", type_name::()); } - entry.or_insert(Box::new(SettingValue:: { + let setting_value = entry.or_insert(Box::new(SettingValue:: { global_value: None, local_values: Vec::new(), })); - match self - .setting_keys - .binary_search_by_key(&T::FIELD_NAME, |e| e.0) - { - Ok(ix) | Err(ix) => self.setting_keys.insert(ix, (T::FIELD_NAME, type_id)), + if let Some(default_settings) = self.default_deserialized_settings.as_mut() { + Self::load_setting_in_map(setting_type_id, setting_value, default_settings); + + let mut user_values_stack = Vec::new(); + if let Some(user_settings) = self.user_deserialized_settings.as_mut() { + Self::load_setting_in_map(setting_type_id, setting_value, user_settings); + if let Some(user_value) = user_settings.typed.get(&setting_type_id) { + user_values_stack = vec![user_value]; + } + } + if let Some(default_deserialized_value) = default_settings.typed.get(&setting_type_id) { + setting_value.set_global_value( + setting_value.load_setting(default_deserialized_value, &user_values_stack), + ); + } } } @@ -124,19 +133,18 @@ impl SettingsStore { /// /// The string should contain a JSON object with a default value for every setting. pub fn set_default_settings(&mut self, default_settings_content: &str) -> Result<()> { - self.default_deserialized_settings = self.load_setting_map(default_settings_content)?; - if self.default_deserialized_settings.len() != self.setting_keys.len() { + let deserialized_setting_map = self.load_setting_map(default_settings_content)?; + if deserialized_setting_map.typed.len() != self.setting_values.len() { return Err(anyhow!( "default settings file is missing fields: {:?}", - self.setting_keys + self.setting_values .iter() - .filter(|(_, type_id)| !self - .default_deserialized_settings - .contains_key(type_id)) + .filter(|(type_id, _)| !deserialized_setting_map.typed.contains_key(type_id)) .map(|(name, _)| *name) .collect::>() )); } + self.default_deserialized_settings = Some(deserialized_setting_map); self.recompute_values(false, None, None); Ok(()) } @@ -175,17 +183,17 @@ impl SettingsStore { ) { // Identify all of the setting types that have changed. let new_settings_map = if let Some(changed_path) = changed_local_path { - &self.local_deserialized_settings.get(changed_path).unwrap() + self.local_deserialized_settings.get(changed_path) } else if user_settings_changed { - self.user_deserialized_settings.as_ref().unwrap() + self.user_deserialized_settings.as_ref() } else { - &self.default_deserialized_settings + self.default_deserialized_settings.as_ref() }; self.changed_setting_types.clear(); - self.changed_setting_types.extend(new_settings_map.keys()); - if let Some(previous_settings_map) = old_settings_map { - self.changed_setting_types - .extend(previous_settings_map.keys()); + for map in [old_settings_map.as_ref(), new_settings_map] { + if let Some(map) = map { + self.changed_setting_types.extend(map.typed.keys()); + } } // Reload the global and local values for every changed setting. @@ -197,18 +205,26 @@ impl SettingsStore { // load function. user_values_stack.clear(); if let Some(user_settings) = &self.user_deserialized_settings { - if let Some(user_value) = user_settings.get(setting_type_id) { + if let Some(user_value) = user_settings.typed.get(setting_type_id) { user_values_stack.push(&user_value); } } + let default_deserialized_value = if let Some(value) = self + .default_deserialized_settings + .as_ref() + .and_then(|map| map.typed.get(setting_type_id)) + { + value + } else { + continue; + }; + // If the global settings file changed, reload the global value for the field. if changed_local_path.is_none() { - let global_value = setting_value.load_setting( - &self.default_deserialized_settings[setting_type_id], - &user_values_stack, + setting_value.set_global_value( + setting_value.load_setting(default_deserialized_value, &user_values_stack), ); - setting_value.set_global_value(global_value); } // Reload the local values for the setting. @@ -223,7 +239,7 @@ impl SettingsStore { } // Ignore recomputing settings for any path that hasn't customized that setting. - let Some(deserialized_value) = deserialized_values.get(setting_type_id) else { + let Some(deserialized_value) = deserialized_values.typed.get(setting_type_id) else { continue; }; @@ -240,7 +256,7 @@ impl SettingsStore { } if let Some(preceding_deserialized_value) = - preceding_deserialized_values.get(setting_type_id) + preceding_deserialized_values.typed.get(setting_type_id) { user_values_stack.push(&*preceding_deserialized_value); } @@ -248,11 +264,10 @@ impl SettingsStore { user_values_stack.push(&*deserialized_value); // Load the local value for the field. - let local_value = setting_value.load_setting( - &self.default_deserialized_settings[setting_type_id], - &user_values_stack, + setting_value.set_local_value( + path.clone(), + setting_value.load_setting(default_deserialized_value, &user_values_stack), ); - setting_value.set_local_value(path.clone(), local_value); } } } @@ -261,57 +276,42 @@ impl SettingsStore { /// /// Returns an error if the string doesn't contain a valid JSON object. fn load_setting_map(&self, json: &str) -> Result { - let mut map = DeserializedSettingMap::default(); - let settings_content_by_key: BTreeMap<&str, &RawValue> = serde_json::from_str(json)?; - let mut setting_types_by_key = self.setting_keys.iter().peekable(); - - // Load all of the fields that don't have a key. - while let Some((setting_key, setting_type_id)) = setting_types_by_key.peek() { - if setting_key.is_some() { - break; - } - setting_types_by_key.next(); - if let Some(deserialized_value) = self - .setting_values - .get(setting_type_id) - .unwrap() - .deserialize_setting(json) - .log_err() - { - map.insert(*setting_type_id, deserialized_value); - } - } - - // For each key in the file, load all of the settings that belong to that key. - for (key, key_content) in settings_content_by_key { - while let Some((setting_key, setting_type_id)) = setting_types_by_key.peek() { - let setting_key = setting_key.expect("setting names are ordered"); - match setting_key.cmp(key) { - Ordering::Less => { - setting_types_by_key.next(); - continue; - } - Ordering::Greater => break, - Ordering::Equal => { - if let Some(deserialized_value) = self - .setting_values - .get(setting_type_id) - .unwrap() - .deserialize_setting(key_content.get()) - .log_err() - { - map.insert(*setting_type_id, deserialized_value); - } - setting_types_by_key.next(); - } - } - } + let mut map = DeserializedSettingMap { + typed: HashMap::default(), + untyped: serde_json::from_str(json)?, + }; + for (setting_type_id, setting_value) in self.setting_values.iter() { + Self::load_setting_in_map(*setting_type_id, setting_value, &mut map); } Ok(map) } + + fn load_setting_in_map( + setting_type_id: TypeId, + setting_value: &Box, + map: &mut DeserializedSettingMap, + ) { + let value = if let Some(setting_key) = setting_value.key() { + if let Some(value) = map.untyped.get(setting_key) { + value + } else { + return; + } + } else { + &map.untyped + }; + + if let Some(deserialized_value) = setting_value.deserialize_setting(&value).log_err() { + map.typed.insert(setting_type_id, deserialized_value); + } + } } impl AnySettingValue for SettingValue { + fn key(&self) -> Option<&'static str> { + T::KEY + } + fn setting_type_name(&self) -> &'static str { type_name::() } @@ -329,8 +329,8 @@ impl AnySettingValue for SettingValue { Box::new(T::load(default_value, &values)) } - fn deserialize_setting(&self, json: &str) -> Result { - let value = serde_json::from_str::(json)?; + fn deserialize_setting(&self, json: &serde_json::Value) -> Result { + let value = T::FileContent::deserialize(json)?; Ok(DeserializedSetting(Box::new(value))) } @@ -380,7 +380,7 @@ mod tests { use serde_derive::Deserialize; #[test] - fn test_settings_store() { + fn test_settings_store_basic() { let mut store = SettingsStore::default(); store.register_setting::(); store.register_setting::(); @@ -517,6 +517,46 @@ mod tests { ); } + #[test] + fn test_setting_store_load_before_register() { + let mut store = SettingsStore::default(); + store + .set_default_settings( + r#"{ + "turbo": true, + "user": { + "name": "John Doe", + "age": 30, + "staff": false + }, + "key1": "x + }"#, + ) + .unwrap(); + store.set_user_settings(r#"{ "turbo": false }"#).unwrap(); + store.register_setting::(); + store.register_setting::(); + + assert_eq!(store.get::(None), &TurboSetting(false)); + assert_eq!( + store.get::(None), + &UserSettings { + name: "John Doe".to_string(), + age: 30, + staff: false, + } + ); + + store.register_setting::(); + assert_eq!( + store.get::(None), + &MultiKeySettings { + key1: "x".into(), + key2: String::new(), + } + ); + } + #[derive(Debug, PartialEq, Deserialize)] struct UserSettings { name: String, @@ -532,7 +572,7 @@ mod tests { } impl Setting for UserSettings { - const FIELD_NAME: Option<&'static str> = Some("user"); + const KEY: Option<&'static str> = Some("user"); type FileContent = UserSettingsJson; fn load(default_value: &UserSettingsJson, user_values: &[&UserSettingsJson]) -> Self { @@ -544,7 +584,7 @@ mod tests { struct TurboSetting(bool); impl Setting for TurboSetting { - const FIELD_NAME: Option<&'static str> = Some("turbo"); + const KEY: Option<&'static str> = Some("turbo"); type FileContent = Option; fn load(default_value: &Option, user_values: &[&Option]) -> Self { @@ -597,7 +637,7 @@ mod tests { } impl Setting for JournalSettings { - const FIELD_NAME: Option<&'static str> = Some("journal"); + const KEY: Option<&'static str> = Some("journal"); type FileContent = JournalSettingsJson; From 316f791a775194dab121e61732469623deb25774 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 8 May 2023 17:52:52 -0700 Subject: [PATCH 008/168] Add generic update method to SettingsStore --- crates/settings/src/settings_store.rs | 416 +++++++++++++++++++++++++- 1 file changed, 408 insertions(+), 8 deletions(-) diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 7c8c2a7522..5191d768ea 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -1,5 +1,6 @@ use anyhow::{anyhow, Result}; use collections::{hash_map, BTreeMap, HashMap, HashSet}; +use lazy_static::lazy_static; use schemars::JsonSchema; use serde::{de::DeserializeOwned, Deserialize as _, Serialize}; use smallvec::SmallVec; @@ -7,10 +8,12 @@ use std::{ any::{type_name, Any, TypeId}, fmt::Debug, mem, + ops::Range, path::Path, + str, sync::Arc, }; -use util::{merge_non_null_json_value_into, ResultExt as _}; +use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _}; /// A value that can be defined as a user setting. /// @@ -22,7 +25,7 @@ pub trait Setting: 'static + Debug { const KEY: Option<&'static str> = None; /// The type that is stored in an individual JSON file. - type FileContent: Serialize + DeserializeOwned + JsonSchema; + type FileContent: Clone + Serialize + DeserializeOwned + JsonSchema; /// The logic for combining together values from one or more JSON files into the /// final value for this setting. @@ -37,7 +40,6 @@ pub trait Setting: 'static + Debug { ) -> Self where Self: DeserializeOwned, - Self::FileContent: Serialize, { let mut merged = serde_json::Value::Null; for value in [default_value].iter().chain(user_values) { @@ -55,6 +57,7 @@ pub struct SettingsStore { user_deserialized_settings: Option, local_deserialized_settings: BTreeMap, DeserializedSettingMap>, changed_setting_types: HashSet, + tab_size_callback: Option<(TypeId, Box Option>)>, } #[derive(Debug)] @@ -129,6 +132,81 @@ impl SettingsStore { .unwrap() } + /// Update the value of a setting. + /// + /// Returns a list of edits to apply to the JSON file. + pub fn update( + &self, + text: &str, + update: impl Fn(&mut T::FileContent), + ) -> Vec<(Range, String)> { + let setting_type_id = TypeId::of::(); + let old_content = self + .user_deserialized_settings + .as_ref() + .unwrap() + .typed + .get(&setting_type_id) + .unwrap() + .0 + .downcast_ref::() + .unwrap() + .clone(); + let mut new_content = old_content.clone(); + update(&mut new_content); + + let mut parser = tree_sitter::Parser::new(); + parser.set_language(tree_sitter_json::language()).unwrap(); + let tree = parser.parse(text, None).unwrap(); + + let old_value = &serde_json::to_value(old_content).unwrap(); + let new_value = &serde_json::to_value(new_content).unwrap(); + + let mut key_path = Vec::new(); + if let Some(key) = T::KEY { + key_path.push(key); + } + + let mut edits = Vec::new(); + let tab_size = self.json_tab_size(); + update_value_in_json_text( + &text, + &tree, + &mut key_path, + tab_size, + &old_value, + &new_value, + &mut edits, + ); + edits.sort_unstable_by_key(|e| e.0.start); + return edits; + } + + /// Configure the tab sized when updating JSON files. + pub fn set_json_tab_size_callback( + &mut self, + get_tab_size: fn(&T) -> Option, + ) { + self.tab_size_callback = Some(( + TypeId::of::(), + Box::new(move |value| get_tab_size(value.downcast_ref::().unwrap())), + )); + } + + fn json_tab_size(&self) -> usize { + const DEFAULT_JSON_TAB_SIZE: usize = 2; + + if let Some((setting_type_id, callback)) = &self.tab_size_callback { + let setting_value = self.setting_values.get(setting_type_id).unwrap(); + let value = setting_value.value_for_path(None); + if let Some(value) = callback(value) { + return value; + } + } + + DEFAULT_JSON_TAB_SIZE + } + /// Set the default settings via a JSON string. /// /// The string should contain a JSON object with a default value for every setting. @@ -277,8 +355,8 @@ impl SettingsStore { /// Returns an error if the string doesn't contain a valid JSON object. fn load_setting_map(&self, json: &str) -> Result { let mut map = DeserializedSettingMap { + untyped: parse_json_with_comments(json)?, typed: HashMap::default(), - untyped: serde_json::from_str(json)?, }; for (setting_type_id, setting_value) in self.setting_values.iter() { Self::load_setting_in_map(*setting_type_id, setting_value, &mut map); @@ -374,10 +452,231 @@ impl Debug for SettingsStore { } } +fn update_value_in_json_text<'a>( + text: &str, + syntax_tree: &tree_sitter::Tree, + key_path: &mut Vec<&'a str>, + tab_size: usize, + old_value: &'a serde_json::Value, + new_value: &'a serde_json::Value, + edits: &mut Vec<(Range, String)>, +) { + // If the old and new values are both objects, then compare them key by key, + // preserving the comments and formatting of the unchanged parts. Otherwise, + // replace the old value with the new value. + if let (serde_json::Value::Object(old_object), serde_json::Value::Object(new_object)) = + (old_value, new_value) + { + for (key, old_sub_value) in old_object.iter() { + key_path.push(key); + let new_sub_value = new_object.get(key).unwrap_or(&serde_json::Value::Null); + update_value_in_json_text( + text, + syntax_tree, + key_path, + tab_size, + old_sub_value, + new_sub_value, + edits, + ); + key_path.pop(); + } + for (key, new_sub_value) in new_object.iter() { + key_path.push(key); + if !old_object.contains_key(key) { + update_value_in_json_text( + text, + syntax_tree, + key_path, + tab_size, + &serde_json::Value::Null, + new_sub_value, + edits, + ); + } + key_path.pop(); + } + } else if old_value != new_value { + let (range, replacement) = + replace_value_in_json_text(text, syntax_tree, &key_path, tab_size, &new_value); + edits.push((range, replacement)); + } +} + +lazy_static! { + static ref PAIR_QUERY: tree_sitter::Query = tree_sitter::Query::new( + tree_sitter_json::language(), + "(pair key: (string) @key value: (_) @value)", + ) + .unwrap(); +} + +fn replace_value_in_json_text( + text: &str, + syntax_tree: &tree_sitter::Tree, + key_path: &[&str], + tab_size: usize, + new_value: impl Serialize, +) -> (Range, String) { + const LANGUAGE_OVERRIDES: &'static str = "language_overrides"; + const LANGUAGES: &'static str = "languages"; + + let mut cursor = tree_sitter::QueryCursor::new(); + + let has_language_overrides = text.contains(LANGUAGE_OVERRIDES); + + let mut depth = 0; + let mut last_value_range = 0..0; + let mut first_key_start = None; + let mut existing_value_range = 0..text.len(); + let matches = cursor.matches(&PAIR_QUERY, syntax_tree.root_node(), text.as_bytes()); + for mat in matches { + if mat.captures.len() != 2 { + continue; + } + + let key_range = mat.captures[0].node.byte_range(); + let value_range = mat.captures[1].node.byte_range(); + + // Don't enter sub objects until we find an exact + // match for the current keypath + if last_value_range.contains_inclusive(&value_range) { + continue; + } + + last_value_range = value_range.clone(); + + if key_range.start > existing_value_range.end { + break; + } + + first_key_start.get_or_insert_with(|| key_range.start); + + let found_key = text + .get(key_range.clone()) + .map(|key_text| { + if key_path[depth] == LANGUAGES && has_language_overrides { + return key_text == format!("\"{}\"", LANGUAGE_OVERRIDES); + } else { + return key_text == format!("\"{}\"", key_path[depth]); + } + }) + .unwrap_or(false); + + if found_key { + existing_value_range = value_range; + // Reset last value range when increasing in depth + last_value_range = existing_value_range.start..existing_value_range.start; + depth += 1; + + if depth == key_path.len() { + break; + } else { + first_key_start = None; + } + } + } + + // We found the exact key we want, insert the new value + if depth == key_path.len() { + let new_val = to_pretty_json(&new_value, tab_size, tab_size * depth); + (existing_value_range, new_val) + } else { + // We have key paths, construct the sub objects + let new_key = if has_language_overrides && key_path[depth] == LANGUAGES { + LANGUAGE_OVERRIDES + } else { + key_path[depth] + }; + + // We don't have the key, construct the nested objects + let mut new_value = serde_json::to_value(new_value).unwrap(); + for key in key_path[(depth + 1)..].iter().rev() { + if has_language_overrides && key == &LANGUAGES { + new_value = serde_json::json!({ LANGUAGE_OVERRIDES.to_string(): new_value }); + } else { + new_value = serde_json::json!({ key.to_string(): new_value }); + } + } + + if let Some(first_key_start) = first_key_start { + let mut row = 0; + let mut column = 0; + for (ix, char) in text.char_indices() { + if ix == first_key_start { + break; + } + if char == '\n' { + row += 1; + column = 0; + } else { + column += char.len_utf8(); + } + } + + if row > 0 { + // depth is 0 based, but division needs to be 1 based. + let new_val = to_pretty_json(&new_value, column / (depth + 1), column); + let space = ' '; + let content = format!("\"{new_key}\": {new_val},\n{space:width$}", width = column); + (first_key_start..first_key_start, content) + } else { + let new_val = serde_json::to_string(&new_value).unwrap(); + let mut content = format!(r#""{new_key}": {new_val},"#); + content.push(' '); + (first_key_start..first_key_start, content) + } + } else { + new_value = serde_json::json!({ new_key.to_string(): new_value }); + let indent_prefix_len = 4 * depth; + let mut new_val = to_pretty_json(&new_value, 4, indent_prefix_len); + if depth == 0 { + new_val.push('\n'); + } + + (existing_value_range, new_val) + } + } +} + +fn to_pretty_json(value: &impl Serialize, indent_size: usize, indent_prefix_len: usize) -> String { + const SPACES: [u8; 32] = [b' '; 32]; + + debug_assert!(indent_size <= SPACES.len()); + debug_assert!(indent_prefix_len <= SPACES.len()); + + let mut output = Vec::new(); + let mut ser = serde_json::Serializer::with_formatter( + &mut output, + serde_json::ser::PrettyFormatter::with_indent(&SPACES[0..indent_size.min(SPACES.len())]), + ); + + value.serialize(&mut ser).unwrap(); + let text = String::from_utf8(output).unwrap(); + + let mut adjusted_text = String::new(); + for (i, line) in text.split('\n').enumerate() { + if i > 0 { + adjusted_text.push_str(str::from_utf8(&SPACES[0..indent_prefix_len]).unwrap()); + } + adjusted_text.push_str(line); + adjusted_text.push('\n'); + } + adjusted_text.pop(); + adjusted_text +} + +fn parse_json_with_comments(content: &str) -> Result { + Ok(serde_json::from_reader( + json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()), + )?) +} + #[cfg(test)] mod tests { use super::*; use serde_derive::Deserialize; + use unindent::Unindent; #[test] fn test_settings_store_basic() { @@ -518,7 +817,7 @@ mod tests { } #[test] - fn test_setting_store_load_before_register() { + fn test_setting_store_assign_json_before_register() { let mut store = SettingsStore::default(); store .set_default_settings( @@ -529,7 +828,7 @@ mod tests { "age": 30, "staff": false }, - "key1": "x + "key1": "x" }"#, ) .unwrap(); @@ -557,6 +856,86 @@ mod tests { ); } + #[test] + fn test_setting_store_update() { + let mut store = SettingsStore::default(); + store.register_setting::(); + store.register_setting::(); + + // entries added and updated + check_settings_update::( + &mut store, + r#"{ + "languages": { + "JSON": { + "is_enabled": true + } + } + }"# + .unindent(), + |settings| { + settings.languages.get_mut("JSON").unwrap().is_enabled = false; + settings + .languages + .insert("Rust".into(), LanguageSettingEntry { is_enabled: true }); + }, + r#"{ + "languages": { + "Rust": { + "is_enabled": true + }, + "JSON": { + "is_enabled": false + } + } + }"# + .unindent(), + ); + + // weird formatting + check_settings_update::( + &mut store, + r#"{ + "user": { "age": 36, "name": "Max", "staff": true } + }"# + .unindent(), + |settings| settings.age = Some(37), + r#"{ + "user": { "age": 37, "name": "Max", "staff": true } + }"# + .unindent(), + ); + + // no content + check_settings_update::( + &mut store, + r#""#.unindent(), + |settings| settings.age = Some(37), + r#"{ + "user": { + "age": 37 + } + } + "# + .unindent(), + ); + } + + fn check_settings_update( + store: &mut SettingsStore, + old_json: String, + update: fn(&mut T::FileContent), + expected_new_json: String, + ) { + store.set_user_settings(&old_json).ok(); + let edits = store.update::(&old_json, update); + let mut new_json = old_json; + for (range, replacement) in edits.into_iter().rev() { + new_json.replace_range(range, &replacement); + } + pretty_assertions::assert_eq!(new_json, expected_new_json); + } + #[derive(Debug, PartialEq, Deserialize)] struct UserSettings { name: String, @@ -564,7 +943,7 @@ mod tests { staff: bool, } - #[derive(Serialize, Deserialize, JsonSchema)] + #[derive(Clone, Serialize, Deserialize, JsonSchema)] struct UserSettingsJson { name: Option, age: Option, @@ -600,7 +979,7 @@ mod tests { key2: String, } - #[derive(Serialize, Deserialize, JsonSchema)] + #[derive(Clone, Serialize, Deserialize, JsonSchema)] struct MultiKeySettingsJson { key1: Option, key2: Option, @@ -645,4 +1024,25 @@ mod tests { Self::load_via_json_merge(default_value, user_values) } } + + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] + struct LanguageSettings { + #[serde(default)] + languages: HashMap, + } + + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] + struct LanguageSettingEntry { + is_enabled: bool, + } + + impl Setting for LanguageSettings { + const KEY: Option<&'static str> = None; + + type FileContent = Self; + + fn load(default_value: &Self, user_values: &[&Self]) -> Self { + Self::load_via_json_merge(default_value, user_values) + } + } } From 9a6a2d9d2719660b3b5719f311f0643d0a8bbe26 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 9 May 2023 18:14:42 -0700 Subject: [PATCH 009/168] Start using the SettingsStore in the app --- Cargo.lock | 3 + crates/copilot_button/Cargo.toml | 1 + crates/copilot_button/src/copilot_button.rs | 66 +- crates/settings/Cargo.toml | 3 +- crates/settings/src/keymap_file.rs | 2 +- crates/settings/src/settings.rs | 794 +++----------------- crates/settings/src/settings_file.rs | 362 +++++---- crates/settings/src/settings_store.rs | 209 ++++-- crates/theme/Cargo.toml | 3 + crates/theme/src/theme_registry.rs | 15 +- crates/theme_selector/Cargo.toml | 1 + crates/theme_selector/src/theme_selector.rs | 16 +- crates/welcome/Cargo.toml | 1 + crates/welcome/src/base_keymap_picker.rs | 19 +- crates/welcome/src/welcome.rs | 24 +- crates/zed/src/main.rs | 58 +- crates/zed/src/zed.rs | 2 +- 17 files changed, 530 insertions(+), 1049 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 02370dc98b..266b0e6d41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1404,6 +1404,7 @@ dependencies = [ "context_menu", "copilot", "editor", + "fs", "futures 0.3.28", "gpui", "settings", @@ -6847,6 +6848,7 @@ name = "theme_selector" version = "0.1.0" dependencies = [ "editor", + "fs", "fuzzy", "gpui", "log", @@ -8315,6 +8317,7 @@ dependencies = [ "anyhow", "db", "editor", + "fs", "fuzzy", "gpui", "install_cli", diff --git a/crates/copilot_button/Cargo.toml b/crates/copilot_button/Cargo.toml index 2d42b192d9..77937e8bd0 100644 --- a/crates/copilot_button/Cargo.toml +++ b/crates/copilot_button/Cargo.toml @@ -12,6 +12,7 @@ doctest = false assets = { path = "../assets" } copilot = { path = "../copilot" } editor = { path = "../editor" } +fs = { path = "../fs" } context_menu = { path = "../context_menu" } gpui = { path = "../gpui" } settings = { path = "../settings" } diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 4b0c9b494a..fec48f1f34 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -2,13 +2,14 @@ use anyhow::Result; use context_menu::{ContextMenu, ContextMenuItem}; use copilot::{Copilot, SignOut, Status}; use editor::{scroll::autoscroll::Autoscroll, Editor}; +use fs::Fs; use gpui::{ elements::*, platform::{CursorStyle, MouseButton}, AnyElement, AppContext, AsyncAppContext, Element, Entity, MouseState, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; -use settings::{settings_file::SettingsFile, Settings}; +use settings::{update_settings_file, Settings, SettingsStore}; use std::{path::Path, sync::Arc}; use util::{paths, ResultExt}; use workspace::{ @@ -26,6 +27,7 @@ pub struct CopilotButton { editor_enabled: Option, language: Option>, path: Option>, + fs: Arc, } impl Entity for CopilotButton { @@ -143,7 +145,7 @@ impl View for CopilotButton { } impl CopilotButton { - pub fn new(cx: &mut ViewContext) -> Self { + pub fn new(fs: Arc, cx: &mut ViewContext) -> Self { let button_view_id = cx.view_id(); let menu = cx.add_view(|cx| { let mut menu = ContextMenu::new(button_view_id, cx); @@ -164,17 +166,19 @@ impl CopilotButton { editor_enabled: None, language: None, path: None, + fs, } } pub fn deploy_copilot_start_menu(&mut self, cx: &mut ViewContext) { let mut menu_options = Vec::with_capacity(2); + let fs = self.fs.clone(); menu_options.push(ContextMenuItem::handler("Sign In", |cx| { initiate_sign_in(cx) })); - menu_options.push(ContextMenuItem::handler("Disable Copilot", |cx| { - hide_copilot(cx) + menu_options.push(ContextMenuItem::handler("Disable Copilot", move |cx| { + hide_copilot(fs.clone(), cx) })); self.popup_menu.update(cx, |menu, cx| { @@ -189,10 +193,12 @@ impl CopilotButton { pub fn deploy_copilot_menu(&mut self, cx: &mut ViewContext) { let settings = cx.global::(); + let fs = self.fs.clone(); let mut menu_options = Vec::with_capacity(8); if let Some(language) = self.language.clone() { + let fs = fs.clone(); let language_enabled = settings.copilot_enabled_for_language(Some(language.as_ref())); menu_options.push(ContextMenuItem::handler( format!( @@ -200,7 +206,7 @@ impl CopilotButton { if language_enabled { "Hide" } else { "Show" }, language ), - move |cx| toggle_copilot_for_language(language.clone(), cx), + move |cx| toggle_copilot_for_language(language.clone(), fs.clone(), cx), )); } @@ -235,7 +241,7 @@ impl CopilotButton { } else { "Show Suggestions for All Files" }, - |cx| toggle_copilot_globally(cx), + move |cx| toggle_copilot_globally(fs.clone(), cx), )); menu_options.push(ContextMenuItem::Separator); @@ -322,24 +328,26 @@ async fn configure_disabled_globs( settings_editor.downgrade().update(&mut cx, |item, cx| { let text = item.buffer().read(cx).snapshot(cx).text(); - let edits = SettingsFile::update_unsaved(&text, cx, |file| { - let copilot = file.copilot.get_or_insert_with(Default::default); - let globs = copilot.disabled_globs.get_or_insert_with(|| { - cx.global::() - .copilot - .disabled_globs - .clone() - .iter() - .map(|glob| glob.as_str().to_string()) - .collect::>() - }); + let edits = cx + .global::() + .update::(&text, |file| { + let copilot = file.copilot.get_or_insert_with(Default::default); + let globs = copilot.disabled_globs.get_or_insert_with(|| { + cx.global::() + .copilot + .disabled_globs + .clone() + .iter() + .map(|glob| glob.as_str().to_string()) + .collect::>() + }); - if let Some(path_to_disable) = &path_to_disable { - globs.push(path_to_disable.to_string_lossy().into_owned()); - } else { - globs.clear(); - } - }); + if let Some(path_to_disable) = &path_to_disable { + globs.push(path_to_disable.to_string_lossy().into_owned()); + } else { + globs.clear(); + } + }); if !edits.is_empty() { item.change_selections(Some(Autoscroll::newest()), cx, |selections| { @@ -356,19 +364,19 @@ async fn configure_disabled_globs( anyhow::Ok(()) } -fn toggle_copilot_globally(cx: &mut AppContext) { +fn toggle_copilot_globally(fs: Arc, cx: &mut AppContext) { let show_copilot_suggestions = cx.global::().show_copilot_suggestions(None, None); - SettingsFile::update(cx, move |file_contents| { + update_settings_file(fs, cx, move |file_contents| { file_contents.editor.show_copilot_suggestions = Some((!show_copilot_suggestions).into()) }); } -fn toggle_copilot_for_language(language: Arc, cx: &mut AppContext) { +fn toggle_copilot_for_language(language: Arc, fs: Arc, cx: &mut AppContext) { let show_copilot_suggestions = cx .global::() .show_copilot_suggestions(Some(&language), None); - SettingsFile::update(cx, move |file_contents| { + update_settings_file(fs, cx, move |file_contents| { file_contents.languages.insert( language, settings::EditorSettings { @@ -379,8 +387,8 @@ fn toggle_copilot_for_language(language: Arc, cx: &mut AppContext) { }); } -fn hide_copilot(cx: &mut AppContext) { - SettingsFile::update(cx, move |file_contents| { +fn hide_copilot(fs: Arc, cx: &mut AppContext) { + update_settings_file(fs, cx, move |file_contents| { file_contents.features.copilot = Some(false) }); } diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index b38aa7c42d..6d6d303a82 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -9,7 +9,7 @@ path = "src/settings.rs" doctest = false [features] -test-support = [] +test-support = ["theme/test-support", "gpui/test-support", "fs/test-support"] [dependencies] assets = { path = "../assets" } @@ -39,6 +39,7 @@ tree-sitter-json = "*" [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } fs = { path = "../fs", features = ["test-support"] } +theme = { path = "../theme", features = ["test-support"] } pretty_assertions = "1.3.0" unindent.workspace = true diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index a45a53145e..e0b3f547f9 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -1,4 +1,4 @@ -use crate::{parse_json_with_comments, Settings}; +use crate::{settings_store::parse_json_with_comments, Settings}; use anyhow::{Context, Result}; use assets::Assets; use collections::BTreeMap; diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 9475d0205f..c3911f9254 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -1,34 +1,31 @@ mod keymap_file; -pub mod settings_file; -pub mod settings_store; -pub mod watched_json; +mod settings_file; +mod settings_store; -use anyhow::{bail, Result}; +use anyhow::bail; use gpui::{ font_cache::{FamilyId, FontCache}, - fonts, AssetSource, + fonts, AppContext, AssetSource, }; -use lazy_static::lazy_static; use schemars::{ gen::{SchemaGenerator, SchemaSettings}, schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec}, JsonSchema, }; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use serde_json::Value; +use settings_store::Setting; use sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use std::{ - borrow::Cow, collections::HashMap, num::NonZeroU32, ops::Range, path::Path, str, sync::Arc, -}; +use std::{borrow::Cow, collections::HashMap, num::NonZeroU32, path::Path, str, sync::Arc}; use theme::{Theme, ThemeRegistry}; -use tree_sitter::{Query, Tree}; -use util::{RangeExt, ResultExt as _}; +use util::ResultExt as _; pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; -pub use watched_json::watch_files; +pub use settings_file::*; +pub use settings_store::SettingsStore; pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json"; pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json"; @@ -69,6 +66,92 @@ pub struct Settings { pub base_keymap: BaseKeymap, } +impl Setting for Settings { + type FileContent = SettingsFileContent; + + fn load( + defaults: &Self::FileContent, + user_values: &[&Self::FileContent], + cx: &AppContext, + ) -> Self { + let buffer_font_features = defaults.buffer_font_features.clone().unwrap(); + let themes = cx.global::>(); + + let mut this = Self { + buffer_font_family: cx + .font_cache() + .load_family( + &[defaults.buffer_font_family.as_ref().unwrap()], + &buffer_font_features, + ) + .unwrap(), + buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(), + buffer_font_features, + buffer_font_size: defaults.buffer_font_size.unwrap(), + active_pane_magnification: defaults.active_pane_magnification.unwrap(), + default_buffer_font_size: defaults.buffer_font_size.unwrap(), + confirm_quit: defaults.confirm_quit.unwrap(), + cursor_blink: defaults.cursor_blink.unwrap(), + hover_popover_enabled: defaults.hover_popover_enabled.unwrap(), + show_completions_on_input: defaults.show_completions_on_input.unwrap(), + show_call_status_icon: defaults.show_call_status_icon.unwrap(), + vim_mode: defaults.vim_mode.unwrap(), + autosave: defaults.autosave.unwrap(), + default_dock_anchor: defaults.default_dock_anchor.unwrap(), + editor_defaults: EditorSettings { + tab_size: defaults.editor.tab_size, + hard_tabs: defaults.editor.hard_tabs, + soft_wrap: defaults.editor.soft_wrap, + preferred_line_length: defaults.editor.preferred_line_length, + remove_trailing_whitespace_on_save: defaults + .editor + .remove_trailing_whitespace_on_save, + ensure_final_newline_on_save: defaults.editor.ensure_final_newline_on_save, + format_on_save: defaults.editor.format_on_save.clone(), + formatter: defaults.editor.formatter.clone(), + enable_language_server: defaults.editor.enable_language_server, + show_copilot_suggestions: defaults.editor.show_copilot_suggestions, + show_whitespaces: defaults.editor.show_whitespaces, + }, + editor_overrides: Default::default(), + copilot: CopilotSettings { + disabled_globs: defaults + .copilot + .clone() + .unwrap() + .disabled_globs + .unwrap() + .into_iter() + .map(|s| glob::Pattern::new(&s).unwrap()) + .collect(), + }, + git: defaults.git.unwrap(), + git_overrides: Default::default(), + journal_defaults: defaults.journal.clone(), + journal_overrides: Default::default(), + terminal_defaults: defaults.terminal.clone(), + terminal_overrides: Default::default(), + language_defaults: defaults.languages.clone(), + language_overrides: Default::default(), + lsp: defaults.lsp.clone(), + theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), + telemetry_defaults: defaults.telemetry, + telemetry_overrides: Default::default(), + auto_update: defaults.auto_update.unwrap(), + base_keymap: Default::default(), + features: Features { + copilot: defaults.features.copilot.unwrap(), + }, + }; + + for value in user_values.into_iter().copied().cloned() { + this.set_user_settings(value, themes.as_ref(), cx.font_cache()); + } + + this + } +} + #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] pub enum BaseKeymap { #[default] @@ -477,7 +560,7 @@ impl Settings { value } - let defaults: SettingsFileContent = parse_json_with_comments( + let defaults: SettingsFileContent = settings_store::parse_json_with_comments( str::from_utf8(assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap().as_ref()).unwrap(), ) .unwrap(); @@ -914,686 +997,3 @@ fn merge(target: &mut T, value: Option) { *target = value; } } - -pub fn parse_json_with_comments(content: &str) -> Result { - Ok(serde_json::from_reader( - json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()), - )?) -} - -lazy_static! { - static ref PAIR_QUERY: Query = Query::new( - tree_sitter_json::language(), - " - (pair - key: (string) @key - value: (_) @value) - ", - ) - .unwrap(); -} - -fn update_object_in_settings_file<'a>( - old_object: &'a serde_json::Map, - new_object: &'a serde_json::Map, - text: &str, - syntax_tree: &Tree, - tab_size: usize, - key_path: &mut Vec<&'a str>, - edits: &mut Vec<(Range, String)>, -) { - for (key, old_value) in old_object.iter() { - key_path.push(key); - let new_value = new_object.get(key).unwrap_or(&Value::Null); - - // If the old and new values are both objects, then compare them key by key, - // preserving the comments and formatting of the unchanged parts. Otherwise, - // replace the old value with the new value. - if let (Value::Object(old_sub_object), Value::Object(new_sub_object)) = - (old_value, new_value) - { - update_object_in_settings_file( - old_sub_object, - new_sub_object, - text, - syntax_tree, - tab_size, - key_path, - edits, - ) - } else if old_value != new_value { - let (range, replacement) = - update_key_in_settings_file(text, syntax_tree, &key_path, tab_size, &new_value); - edits.push((range, replacement)); - } - - key_path.pop(); - } -} - -fn update_key_in_settings_file( - text: &str, - syntax_tree: &Tree, - key_path: &[&str], - tab_size: usize, - new_value: impl Serialize, -) -> (Range, String) { - const LANGUAGE_OVERRIDES: &'static str = "language_overrides"; - const LANGUAGES: &'static str = "languages"; - - let mut cursor = tree_sitter::QueryCursor::new(); - - let has_language_overrides = text.contains(LANGUAGE_OVERRIDES); - - let mut depth = 0; - let mut last_value_range = 0..0; - let mut first_key_start = None; - let mut existing_value_range = 0..text.len(); - let matches = cursor.matches(&PAIR_QUERY, syntax_tree.root_node(), text.as_bytes()); - for mat in matches { - if mat.captures.len() != 2 { - continue; - } - - let key_range = mat.captures[0].node.byte_range(); - let value_range = mat.captures[1].node.byte_range(); - - // Don't enter sub objects until we find an exact - // match for the current keypath - if last_value_range.contains_inclusive(&value_range) { - continue; - } - - last_value_range = value_range.clone(); - - if key_range.start > existing_value_range.end { - break; - } - - first_key_start.get_or_insert_with(|| key_range.start); - - let found_key = text - .get(key_range.clone()) - .map(|key_text| { - if key_path[depth] == LANGUAGES && has_language_overrides { - return key_text == format!("\"{}\"", LANGUAGE_OVERRIDES); - } else { - return key_text == format!("\"{}\"", key_path[depth]); - } - }) - .unwrap_or(false); - - if found_key { - existing_value_range = value_range; - // Reset last value range when increasing in depth - last_value_range = existing_value_range.start..existing_value_range.start; - depth += 1; - - if depth == key_path.len() { - break; - } else { - first_key_start = None; - } - } - } - - // We found the exact key we want, insert the new value - if depth == key_path.len() { - let new_val = to_pretty_json(&new_value, tab_size, tab_size * depth); - (existing_value_range, new_val) - } else { - // We have key paths, construct the sub objects - let new_key = if has_language_overrides && key_path[depth] == LANGUAGES { - LANGUAGE_OVERRIDES - } else { - key_path[depth] - }; - - // We don't have the key, construct the nested objects - let mut new_value = serde_json::to_value(new_value).unwrap(); - for key in key_path[(depth + 1)..].iter().rev() { - if has_language_overrides && key == &LANGUAGES { - new_value = serde_json::json!({ LANGUAGE_OVERRIDES.to_string(): new_value }); - } else { - new_value = serde_json::json!({ key.to_string(): new_value }); - } - } - - if let Some(first_key_start) = first_key_start { - let mut row = 0; - let mut column = 0; - for (ix, char) in text.char_indices() { - if ix == first_key_start { - break; - } - if char == '\n' { - row += 1; - column = 0; - } else { - column += char.len_utf8(); - } - } - - if row > 0 { - // depth is 0 based, but division needs to be 1 based. - let new_val = to_pretty_json(&new_value, column / (depth + 1), column); - let space = ' '; - let content = format!("\"{new_key}\": {new_val},\n{space:width$}", width = column); - (first_key_start..first_key_start, content) - } else { - let new_val = serde_json::to_string(&new_value).unwrap(); - let mut content = format!(r#""{new_key}": {new_val},"#); - content.push(' '); - (first_key_start..first_key_start, content) - } - } else { - new_value = serde_json::json!({ new_key.to_string(): new_value }); - let indent_prefix_len = 4 * depth; - let mut new_val = to_pretty_json(&new_value, 4, indent_prefix_len); - if depth == 0 { - new_val.push('\n'); - } - - (existing_value_range, new_val) - } - } -} - -fn to_pretty_json(value: &impl Serialize, indent_size: usize, indent_prefix_len: usize) -> String { - const SPACES: [u8; 32] = [b' '; 32]; - - debug_assert!(indent_size <= SPACES.len()); - debug_assert!(indent_prefix_len <= SPACES.len()); - - let mut output = Vec::new(); - let mut ser = serde_json::Serializer::with_formatter( - &mut output, - serde_json::ser::PrettyFormatter::with_indent(&SPACES[0..indent_size.min(SPACES.len())]), - ); - - value.serialize(&mut ser).unwrap(); - let text = String::from_utf8(output).unwrap(); - - let mut adjusted_text = String::new(); - for (i, line) in text.split('\n').enumerate() { - if i > 0 { - adjusted_text.push_str(str::from_utf8(&SPACES[0..indent_prefix_len]).unwrap()); - } - adjusted_text.push_str(line); - adjusted_text.push('\n'); - } - adjusted_text.pop(); - adjusted_text -} - -/// Update the settings file with the given callback. -/// -/// Returns a new JSON string and the offset where the first edit occurred. -fn update_settings_file( - text: &str, - mut old_file_content: SettingsFileContent, - tab_size: NonZeroU32, - update: impl FnOnce(&mut SettingsFileContent), -) -> Vec<(Range, String)> { - let mut new_file_content = old_file_content.clone(); - update(&mut new_file_content); - - if new_file_content.languages.len() != old_file_content.languages.len() { - for language in new_file_content.languages.keys() { - old_file_content - .languages - .entry(language.clone()) - .or_default(); - } - for language in old_file_content.languages.keys() { - new_file_content - .languages - .entry(language.clone()) - .or_default(); - } - } - - let mut parser = tree_sitter::Parser::new(); - parser.set_language(tree_sitter_json::language()).unwrap(); - let tree = parser.parse(text, None).unwrap(); - - let old_object = to_json_object(old_file_content); - let new_object = to_json_object(new_file_content); - let mut key_path = Vec::new(); - let mut edits = Vec::new(); - update_object_in_settings_file( - &old_object, - &new_object, - &text, - &tree, - tab_size.get() as usize, - &mut key_path, - &mut edits, - ); - edits.sort_unstable_by_key(|e| e.0.start); - return edits; -} - -fn to_json_object(settings_file: SettingsFileContent) -> serde_json::Map { - let tmp = serde_json::to_value(settings_file).unwrap(); - match tmp { - Value::Object(map) => map, - _ => unreachable!("SettingsFileContent represents a JSON map"), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use unindent::Unindent; - - fn assert_new_settings( - old_json: String, - update: fn(&mut SettingsFileContent), - expected_new_json: String, - ) { - let old_content: SettingsFileContent = serde_json::from_str(&old_json).unwrap_or_default(); - let edits = update_settings_file(&old_json, old_content, 4.try_into().unwrap(), update); - let mut new_json = old_json; - for (range, replacement) in edits.into_iter().rev() { - new_json.replace_range(range, &replacement); - } - pretty_assertions::assert_eq!(new_json, expected_new_json); - } - - #[test] - fn test_update_language_overrides_copilot() { - assert_new_settings( - r#" - { - "language_overrides": { - "JSON": { - "show_copilot_suggestions": false - } - } - } - "# - .unindent(), - |settings| { - settings.languages.insert( - "Rust".into(), - EditorSettings { - show_copilot_suggestions: Some(true), - ..Default::default() - }, - ); - }, - r#" - { - "language_overrides": { - "Rust": { - "show_copilot_suggestions": true - }, - "JSON": { - "show_copilot_suggestions": false - } - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_copilot_globs() { - assert_new_settings( - r#" - { - } - "# - .unindent(), - |settings| { - settings.copilot = Some(CopilotSettingsContent { - disabled_globs: Some(vec![]), - }); - }, - r#" - { - "copilot": { - "disabled_globs": [] - } - } - "# - .unindent(), - ); - - assert_new_settings( - r#" - { - "copilot": { - "disabled_globs": [ - "**/*.json" - ] - } - } - "# - .unindent(), - |settings| { - settings - .copilot - .get_or_insert(Default::default()) - .disabled_globs - .as_mut() - .unwrap() - .push(".env".into()); - }, - r#" - { - "copilot": { - "disabled_globs": [ - "**/*.json", - ".env" - ] - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_copilot() { - assert_new_settings( - r#" - { - "languages": { - "JSON": { - "show_copilot_suggestions": false - } - } - } - "# - .unindent(), - |settings| { - settings.editor.show_copilot_suggestions = Some(true); - }, - r#" - { - "show_copilot_suggestions": true, - "languages": { - "JSON": { - "show_copilot_suggestions": false - } - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_language_copilot() { - assert_new_settings( - r#" - { - "languages": { - "JSON": { - "show_copilot_suggestions": false - } - } - } - "# - .unindent(), - |settings| { - settings.languages.insert( - "Rust".into(), - EditorSettings { - show_copilot_suggestions: Some(true), - ..Default::default() - }, - ); - }, - r#" - { - "languages": { - "Rust": { - "show_copilot_suggestions": true - }, - "JSON": { - "show_copilot_suggestions": false - } - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_telemetry_setting_multiple_fields() { - assert_new_settings( - r#" - { - "telemetry": { - "metrics": false, - "diagnostics": false - } - } - "# - .unindent(), - |settings| { - settings.telemetry.set_diagnostics(true); - settings.telemetry.set_metrics(true); - }, - r#" - { - "telemetry": { - "metrics": true, - "diagnostics": true - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_telemetry_setting_weird_formatting() { - assert_new_settings( - r#"{ - "telemetry": { "metrics": false, "diagnostics": true } - }"# - .unindent(), - |settings| settings.telemetry.set_diagnostics(false), - r#"{ - "telemetry": { "metrics": false, "diagnostics": false } - }"# - .unindent(), - ); - } - - #[test] - fn test_update_telemetry_setting_other_fields() { - assert_new_settings( - r#" - { - "telemetry": { - "metrics": false, - "diagnostics": true - } - } - "# - .unindent(), - |settings| settings.telemetry.set_diagnostics(false), - r#" - { - "telemetry": { - "metrics": false, - "diagnostics": false - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_telemetry_setting_empty_telemetry() { - assert_new_settings( - r#" - { - "telemetry": {} - } - "# - .unindent(), - |settings| settings.telemetry.set_diagnostics(false), - r#" - { - "telemetry": { - "diagnostics": false - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_telemetry_setting_pre_existing() { - assert_new_settings( - r#" - { - "telemetry": { - "diagnostics": true - } - } - "# - .unindent(), - |settings| settings.telemetry.set_diagnostics(false), - r#" - { - "telemetry": { - "diagnostics": false - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_telemetry_setting() { - assert_new_settings( - "{}".into(), - |settings| settings.telemetry.set_diagnostics(true), - r#" - { - "telemetry": { - "diagnostics": true - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_update_object_empty_doc() { - assert_new_settings( - "".into(), - |settings| settings.telemetry.set_diagnostics(true), - r#" - { - "telemetry": { - "diagnostics": true - } - } - "# - .unindent(), - ); - } - - #[test] - fn test_write_theme_into_settings_with_theme() { - assert_new_settings( - r#" - { - "theme": "One Dark" - } - "# - .unindent(), - |settings| settings.theme = Some("summerfruit-light".to_string()), - r#" - { - "theme": "summerfruit-light" - } - "# - .unindent(), - ); - } - - #[test] - fn test_write_theme_into_empty_settings() { - assert_new_settings( - r#" - { - } - "# - .unindent(), - |settings| settings.theme = Some("summerfruit-light".to_string()), - r#" - { - "theme": "summerfruit-light" - } - "# - .unindent(), - ); - } - - #[test] - fn write_key_no_document() { - assert_new_settings( - "".to_string(), - |settings| settings.theme = Some("summerfruit-light".to_string()), - r#" - { - "theme": "summerfruit-light" - } - "# - .unindent(), - ); - } - - #[test] - fn test_write_theme_into_single_line_settings_without_theme() { - assert_new_settings( - r#"{ "a": "", "ok": true }"#.to_string(), - |settings| settings.theme = Some("summerfruit-light".to_string()), - r#"{ "theme": "summerfruit-light", "a": "", "ok": true }"#.to_string(), - ); - } - - #[test] - fn test_write_theme_pre_object_whitespace() { - assert_new_settings( - r#" { "a": "", "ok": true }"#.to_string(), - |settings| settings.theme = Some("summerfruit-light".to_string()), - r#" { "theme": "summerfruit-light", "a": "", "ok": true }"#.unindent(), - ); - } - - #[test] - fn test_write_theme_into_multi_line_settings_without_theme() { - assert_new_settings( - r#" - { - "a": "b" - } - "# - .unindent(), - |settings| settings.theme = Some("summerfruit-light".to_string()), - r#" - { - "theme": "summerfruit-light", - "a": "b" - } - "# - .unindent(), - ); - } -} diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 6402a07f5e..17294235ec 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -1,88 +1,181 @@ -use crate::{update_settings_file, watched_json::WatchedJsonFile, Settings, SettingsFileContent}; +use crate::{ + settings_store::parse_json_with_comments, settings_store::SettingsStore, KeymapFileContent, + Settings, SettingsFileContent, DEFAULT_SETTINGS_ASSET_PATH, +}; use anyhow::Result; use assets::Assets; use fs::Fs; -use gpui::AppContext; -use std::{io::ErrorKind, ops::Range, path::Path, sync::Arc}; +use futures::{channel::mpsc, StreamExt}; +use gpui::{executor::Background, AppContext, AssetSource}; +use std::{borrow::Cow, io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration}; +use util::{paths, ResultExt}; -// TODO: Switch SettingsFile to open a worktree and buffer for synchronization -// And instant updates in the Zed editor -#[derive(Clone)] -pub struct SettingsFile { - path: &'static Path, - settings_file_content: WatchedJsonFile, - fs: Arc, +pub fn default_settings() -> Cow<'static, str> { + match Assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap() { + Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()), + Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()), + } } -impl SettingsFile { - pub fn new( - path: &'static Path, - settings_file_content: WatchedJsonFile, - fs: Arc, - ) -> Self { - SettingsFile { - path, - settings_file_content, - fs, - } - } +#[cfg(any(test, feature = "test-support"))] +pub fn test_settings() -> String { + let mut value = + parse_json_with_comments::(default_settings().as_ref()).unwrap(); + util::merge_non_null_json_value_into( + serde_json::json!({ + "buffer_font_family": "Courier", + "buffer_font_features": {}, + "default_buffer_font_size": 14, + "preferred_line_length": 80, + "theme": theme::EMPTY_THEME_NAME, + }), + &mut value, + ); + serde_json::to_string(&value).unwrap() +} - async fn load_settings(path: &Path, fs: &Arc) -> Result { - match fs.load(path).await { - result @ Ok(_) => result, - Err(err) => { - if let Some(e) = err.downcast_ref::() { - if e.kind() == ErrorKind::NotFound { - return Ok(Settings::initial_user_settings_content(&Assets).to_string()); +pub fn watch_config_file( + executor: Arc, + fs: Arc, + path: PathBuf, +) -> mpsc::UnboundedReceiver { + let (tx, rx) = mpsc::unbounded(); + executor + .spawn(async move { + let events = fs.watch(&path, Duration::from_millis(100)).await; + futures::pin_mut!(events); + loop { + if let Ok(contents) = fs.load(&path).await { + if !tx.unbounded_send(contents).is_ok() { + break; } } - return Err(err); + if events.next().await.is_none() { + break; + } + } + }) + .detach(); + rx +} + +pub fn handle_keymap_file_changes( + mut user_keymap_file_rx: mpsc::UnboundedReceiver, + cx: &mut AppContext, +) { + cx.spawn(move |mut cx| async move { + let mut settings_subscription = None; + while let Some(user_keymap_content) = user_keymap_file_rx.next().await { + if let Ok(keymap_content) = + parse_json_with_comments::(&user_keymap_content) + { + cx.update(|cx| { + cx.clear_bindings(); + KeymapFileContent::load_defaults(cx); + keymap_content.clone().add_to_cx(cx).log_err(); + }); + + let mut old_base_keymap = cx.read(|cx| cx.global::().base_keymap.clone()); + drop(settings_subscription); + settings_subscription = Some(cx.update(|cx| { + cx.observe_global::(move |cx| { + let settings = cx.global::(); + if settings.base_keymap != old_base_keymap { + old_base_keymap = settings.base_keymap.clone(); + + cx.clear_bindings(); + KeymapFileContent::load_defaults(cx); + keymap_content.clone().add_to_cx(cx).log_err(); + } + }) + .detach(); + })); } } - } + }) + .detach(); +} - pub fn update_unsaved( - text: &str, - cx: &AppContext, - update: impl FnOnce(&mut SettingsFileContent), - ) -> Vec<(Range, String)> { - let this = cx.global::(); - let tab_size = cx.global::().tab_size(Some("JSON")); - let current_file_content = this.settings_file_content.current(); - update_settings_file(&text, current_file_content, tab_size, update) - } +pub fn handle_settings_file_changes( + mut user_settings_file_rx: mpsc::UnboundedReceiver, + cx: &mut AppContext, +) { + let user_settings_content = cx.background().block(user_settings_file_rx.next()).unwrap(); + cx.update_global::(|store, cx| { + store + .set_user_settings(&user_settings_content, cx) + .log_err(); - pub fn update( - cx: &mut AppContext, - update: impl 'static + Send + FnOnce(&mut SettingsFileContent), - ) { - let this = cx.global::(); - let tab_size = cx.global::().tab_size(Some("JSON")); - let current_file_content = this.settings_file_content.current(); - let fs = this.fs.clone(); - let path = this.path.clone(); + // TODO - remove the Settings global, use the SettingsStore instead. + store.register_setting::(cx); + cx.set_global(store.get::(None).clone()); + }); + cx.spawn(move |mut cx| async move { + while let Some(user_settings_content) = user_settings_file_rx.next().await { + cx.update(|cx| { + cx.update_global::(|store, cx| { + store + .set_user_settings(&user_settings_content, cx) + .log_err(); + + // TODO - remove the Settings global, use the SettingsStore instead. + cx.set_global(store.get::(None).clone()); + }); + }); + } + }) + .detach(); +} + +async fn load_settings(fs: &Arc) -> Result { + match fs.load(&paths::SETTINGS).await { + result @ Ok(_) => result, + Err(err) => { + if let Some(e) = err.downcast_ref::() { + if e.kind() == ErrorKind::NotFound { + return Ok(Settings::initial_user_settings_content(&Assets).to_string()); + } + } + return Err(err); + } + } +} + +pub fn update_settings_file( + fs: Arc, + cx: &mut AppContext, + update: impl 'static + Send + FnOnce(&mut SettingsFileContent), +) { + cx.spawn(|cx| async move { + let old_text = cx + .background() + .spawn({ + let fs = fs.clone(); + async move { load_settings(&fs).await } + }) + .await?; + + let edits = cx.read(|cx| { + cx.global::() + .update::(&old_text, update) + }); + + let mut new_text = old_text; + for (range, replacement) in edits.into_iter().rev() { + new_text.replace_range(range, &replacement); + } cx.background() - .spawn(async move { - let old_text = SettingsFile::load_settings(path, &fs).await?; - let edits = update_settings_file(&old_text, current_file_content, tab_size, update); - let mut new_text = old_text; - for (range, replacement) in edits.into_iter().rev() { - new_text.replace_range(range, &replacement); - } - fs.atomic_write(path.to_path_buf(), new_text).await?; - anyhow::Ok(()) - }) - .detach_and_log_err(cx) - } + .spawn(async move { fs.atomic_write(paths::SETTINGS.clone(), new_text).await }) + .await?; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } #[cfg(test)] mod tests { use super::*; - use crate::{ - watch_files, watched_json::watch_settings_file, EditorSettings, Settings, SoftWrap, - }; use fs::FakeFs; use gpui::{actions, elements::*, Action, Entity, TestAppContext, View, ViewContext}; use theme::ThemeRegistry; @@ -107,7 +200,6 @@ mod tests { async fn test_base_keymap(cx: &mut gpui::TestAppContext) { let executor = cx.background(); let fs = FakeFs::new(executor.clone()); - let font_cache = cx.font_cache(); actions!(test, [A, B]); // From the Atom keymap @@ -145,25 +237,26 @@ mod tests { .await .unwrap(); - let settings_file = - WatchedJsonFile::new(fs.clone(), &executor, "/settings.json".as_ref()).await; - let keymaps_file = - WatchedJsonFile::new(fs.clone(), &executor, "/keymap.json".as_ref()).await; - - let default_settings = cx.read(Settings::test); - cx.update(|cx| { + let mut store = SettingsStore::default(); + store.set_default_settings(&test_settings(), cx).unwrap(); + cx.set_global(store); + cx.set_global(ThemeRegistry::new(Assets, cx.font_cache().clone())); cx.add_global_action(|_: &A, _cx| {}); cx.add_global_action(|_: &B, _cx| {}); cx.add_global_action(|_: &ActivatePreviousPane, _cx| {}); cx.add_global_action(|_: &ActivatePrevItem, _cx| {}); - watch_files( - default_settings, - settings_file, - ThemeRegistry::new((), font_cache), - keymaps_file, - cx, - ) + + let settings_rx = watch_config_file( + executor.clone(), + fs.clone(), + PathBuf::from("/settings.json"), + ); + let keymap_rx = + watch_config_file(executor.clone(), fs.clone(), PathBuf::from("/keymap.json")); + + handle_keymap_file_changes(keymap_rx, cx); + handle_settings_file_changes(settings_rx, cx); }); cx.foreground().run_until_parked(); @@ -255,113 +348,4 @@ mod tests { ); } } - - #[gpui::test] - async fn test_watch_settings_files(cx: &mut gpui::TestAppContext) { - let executor = cx.background(); - let fs = FakeFs::new(executor.clone()); - let font_cache = cx.font_cache(); - - fs.save( - "/settings.json".as_ref(), - &r#" - { - "buffer_font_size": 24, - "soft_wrap": "editor_width", - "tab_size": 8, - "language_overrides": { - "Markdown": { - "tab_size": 2, - "preferred_line_length": 100, - "soft_wrap": "preferred_line_length" - } - } - } - "# - .into(), - Default::default(), - ) - .await - .unwrap(); - - let source = WatchedJsonFile::new(fs.clone(), &executor, "/settings.json".as_ref()).await; - - let default_settings = cx.read(Settings::test).with_language_defaults( - "JavaScript", - EditorSettings { - tab_size: Some(2.try_into().unwrap()), - ..Default::default() - }, - ); - cx.update(|cx| { - watch_settings_file( - default_settings.clone(), - source, - ThemeRegistry::new((), font_cache), - cx, - ) - }); - - cx.foreground().run_until_parked(); - let settings = cx.read(|cx| cx.global::().clone()); - assert_eq!(settings.buffer_font_size, 24.0); - - assert_eq!(settings.soft_wrap(None), SoftWrap::EditorWidth); - assert_eq!( - settings.soft_wrap(Some("Markdown")), - SoftWrap::PreferredLineLength - ); - assert_eq!( - settings.soft_wrap(Some("JavaScript")), - SoftWrap::EditorWidth - ); - - assert_eq!(settings.preferred_line_length(None), 80); - assert_eq!(settings.preferred_line_length(Some("Markdown")), 100); - assert_eq!(settings.preferred_line_length(Some("JavaScript")), 80); - - assert_eq!(settings.tab_size(None).get(), 8); - assert_eq!(settings.tab_size(Some("Markdown")).get(), 2); - assert_eq!(settings.tab_size(Some("JavaScript")).get(), 8); - - fs.save( - "/settings.json".as_ref(), - &"(garbage)".into(), - Default::default(), - ) - .await - .unwrap(); - // fs.remove_file("/settings.json".as_ref(), Default::default()) - // .await - // .unwrap(); - - cx.foreground().run_until_parked(); - let settings = cx.read(|cx| cx.global::().clone()); - assert_eq!(settings.buffer_font_size, 24.0); - - assert_eq!(settings.soft_wrap(None), SoftWrap::EditorWidth); - assert_eq!( - settings.soft_wrap(Some("Markdown")), - SoftWrap::PreferredLineLength - ); - assert_eq!( - settings.soft_wrap(Some("JavaScript")), - SoftWrap::EditorWidth - ); - - assert_eq!(settings.preferred_line_length(None), 80); - assert_eq!(settings.preferred_line_length(Some("Markdown")), 100); - assert_eq!(settings.preferred_line_length(Some("JavaScript")), 80); - - assert_eq!(settings.tab_size(None).get(), 8); - assert_eq!(settings.tab_size(Some("Markdown")).get(), 2); - assert_eq!(settings.tab_size(Some("JavaScript")).get(), 8); - - fs.remove_file("/settings.json".as_ref(), Default::default()) - .await - .unwrap(); - cx.foreground().run_until_parked(); - let settings = cx.read(|cx| cx.global::().clone()); - assert_eq!(settings.buffer_font_size, default_settings.buffer_font_size); - } } diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 5191d768ea..394d457d3d 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -1,5 +1,6 @@ use anyhow::{anyhow, Result}; use collections::{hash_map, BTreeMap, HashMap, HashSet}; +use gpui::AppContext; use lazy_static::lazy_static; use schemars::JsonSchema; use serde::{de::DeserializeOwned, Deserialize as _, Serialize}; @@ -18,7 +19,7 @@ use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _}; /// A value that can be defined as a user setting. /// /// Settings can be loaded from a combination of multiple JSON files. -pub trait Setting: 'static + Debug { +pub trait Setting: 'static { /// The name of a key within the JSON file from which this setting should /// be deserialized. If this is `None`, then the setting will be deserialized /// from the root object. @@ -32,7 +33,11 @@ pub trait Setting: 'static + Debug { /// /// The user values are ordered from least specific (the global settings file) /// to most specific (the innermost local settings file). - fn load(default_value: &Self::FileContent, user_values: &[&Self::FileContent]) -> Self; + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + cx: &AppContext, + ) -> Self; fn load_via_json_merge( default_value: &Self::FileContent, @@ -66,7 +71,7 @@ struct SettingValue { local_values: Vec<(Arc, T)>, } -trait AnySettingValue: Debug { +trait AnySettingValue { fn key(&self) -> Option<&'static str>; fn setting_type_name(&self) -> &'static str; fn deserialize_setting(&self, json: &serde_json::Value) -> Result; @@ -74,6 +79,7 @@ trait AnySettingValue: Debug { &self, default_value: &DeserializedSetting, custom: &[&DeserializedSetting], + cx: &AppContext, ) -> Box; fn value_for_path(&self, path: Option<&Path>) -> &dyn Any; fn set_global_value(&mut self, value: Box); @@ -89,7 +95,7 @@ struct DeserializedSettingMap { impl SettingsStore { /// Add a new type of setting to the store. - pub fn register_setting(&mut self) { + pub fn register_setting(&mut self, cx: &AppContext) { let setting_type_id = TypeId::of::(); let entry = self.setting_values.entry(setting_type_id); @@ -112,24 +118,26 @@ impl SettingsStore { } } if let Some(default_deserialized_value) = default_settings.typed.get(&setting_type_id) { - setting_value.set_global_value( - setting_value.load_setting(default_deserialized_value, &user_values_stack), - ); + setting_value.set_global_value(setting_value.load_setting( + default_deserialized_value, + &user_values_stack, + cx, + )); } } } /// Get the value of a setting. /// - /// Panics if settings have not yet been loaded, or there is no default + /// Panics if the given setting type has not been registered, or if there is no /// value for this setting. pub fn get(&self, path: Option<&Path>) -> &T { self.setting_values .get(&TypeId::of::()) - .unwrap() + .expect("unregistered setting type") .value_for_path(path) .downcast_ref::() - .unwrap() + .expect("no default value for setting type") } /// Update the value of a setting. @@ -138,7 +146,7 @@ impl SettingsStore { pub fn update( &self, text: &str, - update: impl Fn(&mut T::FileContent), + update: impl FnOnce(&mut T::FileContent), ) -> Vec<(Range, String)> { let setting_type_id = TypeId::of::(); let old_content = self @@ -210,7 +218,11 @@ impl SettingsStore { /// Set the default settings via a JSON string. /// /// The string should contain a JSON object with a default value for every setting. - pub fn set_default_settings(&mut self, default_settings_content: &str) -> Result<()> { + pub fn set_default_settings( + &mut self, + default_settings_content: &str, + cx: &mut AppContext, + ) -> Result<()> { let deserialized_setting_map = self.load_setting_map(default_settings_content)?; if deserialized_setting_map.typed.len() != self.setting_values.len() { return Err(anyhow!( @@ -223,16 +235,20 @@ impl SettingsStore { )); } self.default_deserialized_settings = Some(deserialized_setting_map); - self.recompute_values(false, None, None); + self.recompute_values(false, None, None, cx); Ok(()) } /// Set the user settings via a JSON string. - pub fn set_user_settings(&mut self, user_settings_content: &str) -> Result<()> { + pub fn set_user_settings( + &mut self, + user_settings_content: &str, + cx: &mut AppContext, + ) -> Result<()> { let user_settings = self.load_setting_map(user_settings_content)?; let old_user_settings = mem::replace(&mut self.user_deserialized_settings, Some(user_settings)); - self.recompute_values(true, None, old_user_settings); + self.recompute_values(true, None, old_user_settings, cx); Ok(()) } @@ -241,6 +257,7 @@ impl SettingsStore { &mut self, path: Arc, settings_content: Option<&str>, + cx: &mut AppContext, ) -> Result<()> { let removed_map = if let Some(settings_content) = settings_content { self.local_deserialized_settings @@ -249,7 +266,7 @@ impl SettingsStore { } else { self.local_deserialized_settings.remove(&path) }; - self.recompute_values(true, Some(&path), removed_map); + self.recompute_values(true, Some(&path), removed_map, cx); Ok(()) } @@ -258,6 +275,7 @@ impl SettingsStore { user_settings_changed: bool, changed_local_path: Option<&Path>, old_settings_map: Option, + cx: &AppContext, ) { // Identify all of the setting types that have changed. let new_settings_map = if let Some(changed_path) = changed_local_path { @@ -300,9 +318,11 @@ impl SettingsStore { // If the global settings file changed, reload the global value for the field. if changed_local_path.is_none() { - setting_value.set_global_value( - setting_value.load_setting(default_deserialized_value, &user_values_stack), - ); + setting_value.set_global_value(setting_value.load_setting( + default_deserialized_value, + &user_values_stack, + cx, + )); } // Reload the local values for the setting. @@ -344,7 +364,7 @@ impl SettingsStore { // Load the local value for the field. setting_value.set_local_value( path.clone(), - setting_value.load_setting(default_deserialized_value, &user_values_stack), + setting_value.load_setting(default_deserialized_value, &user_values_stack, cx), ); } } @@ -398,13 +418,14 @@ impl AnySettingValue for SettingValue { &self, default_value: &DeserializedSetting, user_values: &[&DeserializedSetting], + cx: &AppContext, ) -> Box { let default_value = default_value.0.downcast_ref::().unwrap(); let values: SmallVec<[&T::FileContent; 6]> = user_values .iter() .map(|value| value.0.downcast_ref().unwrap()) .collect(); - Box::new(T::load(default_value, &values)) + Box::new(T::load(default_value, &values, cx)) } fn deserialize_setting(&self, json: &serde_json::Value) -> Result { @@ -420,7 +441,9 @@ impl AnySettingValue for SettingValue { } } } - self.global_value.as_ref().unwrap() + self.global_value + .as_ref() + .expect("no default value for setting") } fn set_global_value(&mut self, value: Box) { @@ -436,21 +459,21 @@ impl AnySettingValue for SettingValue { } } -impl Debug for SettingsStore { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - return f - .debug_struct("SettingsStore") - .field( - "setting_value_sets_by_type", - &self - .setting_values - .values() - .map(|set| (set.setting_type_name(), set)) - .collect::>(), - ) - .finish_non_exhaustive(); - } -} +// impl Debug for SettingsStore { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// return f +// .debug_struct("SettingsStore") +// .field( +// "setting_value_sets_by_type", +// &self +// .setting_values +// .values() +// .map(|set| (set.setting_type_name(), set)) +// .collect::>(), +// ) +// .finish_non_exhaustive(); +// } +// } fn update_value_in_json_text<'a>( text: &str, @@ -503,14 +526,6 @@ fn update_value_in_json_text<'a>( } } -lazy_static! { - static ref PAIR_QUERY: tree_sitter::Query = tree_sitter::Query::new( - tree_sitter_json::language(), - "(pair key: (string) @key value: (_) @value)", - ) - .unwrap(); -} - fn replace_value_in_json_text( text: &str, syntax_tree: &tree_sitter::Tree, @@ -521,6 +536,14 @@ fn replace_value_in_json_text( const LANGUAGE_OVERRIDES: &'static str = "language_overrides"; const LANGUAGES: &'static str = "languages"; + lazy_static! { + static ref PAIR_QUERY: tree_sitter::Query = tree_sitter::Query::new( + tree_sitter_json::language(), + "(pair key: (string) @key value: (_) @value)", + ) + .unwrap(); + } + let mut cursor = tree_sitter::QueryCursor::new(); let has_language_overrides = text.contains(LANGUAGE_OVERRIDES); @@ -666,7 +689,7 @@ fn to_pretty_json(value: &impl Serialize, indent_size: usize, indent_prefix_len: adjusted_text } -fn parse_json_with_comments(content: &str) -> Result { +pub fn parse_json_with_comments(content: &str) -> Result { Ok(serde_json::from_reader( json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()), )?) @@ -678,12 +701,12 @@ mod tests { use serde_derive::Deserialize; use unindent::Unindent; - #[test] - fn test_settings_store_basic() { + #[gpui::test] + fn test_settings_store_basic(cx: &mut AppContext) { let mut store = SettingsStore::default(); - store.register_setting::(); - store.register_setting::(); - store.register_setting::(); + store.register_setting::(cx); + store.register_setting::(cx); + store.register_setting::(cx); // error - missing required field in default settings store @@ -695,6 +718,7 @@ mod tests { "staff": false } }"#, + cx, ) .unwrap_err(); @@ -709,6 +733,7 @@ mod tests { "staff": false } }"#, + cx, ) .unwrap_err(); @@ -723,6 +748,7 @@ mod tests { "staff": false } }"#, + cx, ) .unwrap(); @@ -750,6 +776,7 @@ mod tests { "user": { "age": 31 }, "key1": "a" }"#, + cx, ) .unwrap(); @@ -767,12 +794,14 @@ mod tests { .set_local_settings( Path::new("/root1").into(), Some(r#"{ "user": { "staff": true } }"#), + cx, ) .unwrap(); store .set_local_settings( Path::new("/root1/subdir").into(), Some(r#"{ "user": { "name": "Jane Doe" } }"#), + cx, ) .unwrap(); @@ -780,6 +809,7 @@ mod tests { .set_local_settings( Path::new("/root2").into(), Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#), + cx, ) .unwrap(); @@ -816,8 +846,8 @@ mod tests { ); } - #[test] - fn test_setting_store_assign_json_before_register() { + #[gpui::test] + fn test_setting_store_assign_json_before_register(cx: &mut AppContext) { let mut store = SettingsStore::default(); store .set_default_settings( @@ -830,11 +860,14 @@ mod tests { }, "key1": "x" }"#, + cx, ) .unwrap(); - store.set_user_settings(r#"{ "turbo": false }"#).unwrap(); - store.register_setting::(); - store.register_setting::(); + store + .set_user_settings(r#"{ "turbo": false }"#, cx) + .unwrap(); + store.register_setting::(cx); + store.register_setting::(cx); assert_eq!(store.get::(None), &TurboSetting(false)); assert_eq!( @@ -846,7 +879,7 @@ mod tests { } ); - store.register_setting::(); + store.register_setting::(cx); assert_eq!( store.get::(None), &MultiKeySettings { @@ -856,11 +889,12 @@ mod tests { ); } - #[test] - fn test_setting_store_update() { + #[gpui::test] + fn test_setting_store_update(cx: &mut AppContext) { let mut store = SettingsStore::default(); - store.register_setting::(); - store.register_setting::(); + store.register_setting::(cx); + store.register_setting::(cx); + store.register_setting::(cx); // entries added and updated check_settings_update::( @@ -890,6 +924,7 @@ mod tests { } }"# .unindent(), + cx, ); // weird formatting @@ -904,6 +939,33 @@ mod tests { "user": { "age": 37, "name": "Max", "staff": true } }"# .unindent(), + cx, + ); + + // single-line formatting, other keys + check_settings_update::( + &mut store, + r#"{ "one": 1, "two": 2 }"#.unindent(), + |settings| settings.key1 = Some("x".into()), + r#"{ "key1": "x", "one": 1, "two": 2 }"#.unindent(), + cx, + ); + + // empty object + check_settings_update::( + &mut store, + r#"{ + "user": {} + }"# + .unindent(), + |settings| settings.age = Some(37), + r#"{ + "user": { + "age": 37 + } + }"# + .unindent(), + cx, ); // no content @@ -918,6 +980,7 @@ mod tests { } "# .unindent(), + cx, ); } @@ -926,8 +989,9 @@ mod tests { old_json: String, update: fn(&mut T::FileContent), expected_new_json: String, + cx: &mut AppContext, ) { - store.set_user_settings(&old_json).ok(); + store.set_user_settings(&old_json, cx).ok(); let edits = store.update::(&old_json, update); let mut new_json = old_json; for (range, replacement) in edits.into_iter().rev() { @@ -954,7 +1018,11 @@ mod tests { const KEY: Option<&'static str> = Some("user"); type FileContent = UserSettingsJson; - fn load(default_value: &UserSettingsJson, user_values: &[&UserSettingsJson]) -> Self { + fn load( + default_value: &UserSettingsJson, + user_values: &[&UserSettingsJson], + _: &AppContext, + ) -> Self { Self::load_via_json_merge(default_value, user_values) } } @@ -966,7 +1034,11 @@ mod tests { const KEY: Option<&'static str> = Some("turbo"); type FileContent = Option; - fn load(default_value: &Option, user_values: &[&Option]) -> Self { + fn load( + default_value: &Option, + user_values: &[&Option], + _: &AppContext, + ) -> Self { Self::load_via_json_merge(default_value, user_values) } } @@ -991,6 +1063,7 @@ mod tests { fn load( default_value: &MultiKeySettingsJson, user_values: &[&MultiKeySettingsJson], + _: &AppContext, ) -> Self { Self::load_via_json_merge(default_value, user_values) } @@ -1020,7 +1093,11 @@ mod tests { type FileContent = JournalSettingsJson; - fn load(default_value: &JournalSettingsJson, user_values: &[&JournalSettingsJson]) -> Self { + fn load( + default_value: &JournalSettingsJson, + user_values: &[&JournalSettingsJson], + _: &AppContext, + ) -> Self { Self::load_via_json_merge(default_value, user_values) } } @@ -1041,7 +1118,7 @@ mod tests { type FileContent = Self; - fn load(default_value: &Self, user_values: &[&Self]) -> Self { + fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Self { Self::load_via_json_merge(default_value, user_values) } } diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index 67a28397e2..d37ac3465b 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" edition = "2021" publish = false +[features] +test-support = ["gpui/test-support"] + [lib] path = "src/theme.rs" doctest = false diff --git a/crates/theme/src/theme_registry.rs b/crates/theme/src/theme_registry.rs index f9f89b7adc..2bcdb4528c 100644 --- a/crates/theme/src/theme_registry.rs +++ b/crates/theme/src/theme_registry.rs @@ -20,15 +20,26 @@ pub struct ThemeRegistry { next_theme_id: AtomicUsize, } +#[cfg(any(test, feature = "test-support"))] +pub const EMPTY_THEME_NAME: &'static str = "empty-theme"; + impl ThemeRegistry { pub fn new(source: impl AssetSource, font_cache: Arc) -> Arc { - Arc::new(Self { + let this = Arc::new(Self { assets: Box::new(source), themes: Default::default(), theme_data: Default::default(), next_theme_id: Default::default(), font_cache, - }) + }); + + #[cfg(any(test, feature = "test-support"))] + this.themes.lock().insert( + EMPTY_THEME_NAME.to_string(), + gpui::fonts::with_font_cache(this.font_cache.clone(), || Arc::new(Theme::default())), + ); + + this } pub fn list(&self, staff: bool) -> impl Iterator + '_ { diff --git a/crates/theme_selector/Cargo.toml b/crates/theme_selector/Cargo.toml index a404e43f29..ac3a85d89a 100644 --- a/crates/theme_selector/Cargo.toml +++ b/crates/theme_selector/Cargo.toml @@ -11,6 +11,7 @@ doctest = false [dependencies] editor = { path = "../editor" } fuzzy = { path = "../fuzzy" } +fs = { path = "../fs" } gpui = { path = "../gpui" } picker = { path = "../picker" } theme = { path = "../theme" } diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 21332114e2..a35e546891 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -1,7 +1,8 @@ +use fs::Fs; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use gpui::{actions, elements::*, AnyElement, AppContext, Element, MouseState, ViewContext}; use picker::{Picker, PickerDelegate, PickerEvent}; -use settings::{settings_file::SettingsFile, Settings}; +use settings::{update_settings_file, Settings}; use staff_mode::StaffMode; use std::sync::Arc; use theme::{Theme, ThemeMeta, ThemeRegistry}; @@ -18,7 +19,8 @@ pub fn init(cx: &mut AppContext) { pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { workspace.toggle_modal(cx, |workspace, cx| { let themes = workspace.app_state().themes.clone(); - cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(themes, cx), cx)) + let fs = workspace.app_state().fs.clone(); + cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(fs, themes, cx), cx)) }); } @@ -40,6 +42,7 @@ pub fn reload(themes: Arc, cx: &mut AppContext) { pub type ThemeSelector = Picker; pub struct ThemeSelectorDelegate { + fs: Arc, registry: Arc, theme_data: Vec, matches: Vec, @@ -49,7 +52,11 @@ pub struct ThemeSelectorDelegate { } impl ThemeSelectorDelegate { - fn new(registry: Arc, cx: &mut ViewContext) -> Self { + fn new( + fs: Arc, + registry: Arc, + cx: &mut ViewContext, + ) -> Self { let settings = cx.global::(); let original_theme = settings.theme.clone(); @@ -68,6 +75,7 @@ impl ThemeSelectorDelegate { }) .collect(); let mut this = Self { + fs, registry, theme_data: theme_names, matches, @@ -121,7 +129,7 @@ impl PickerDelegate for ThemeSelectorDelegate { self.selection_completed = true; let theme_name = cx.global::().theme.meta.name.clone(); - SettingsFile::update(cx, |settings_content| { + update_settings_file(self.fs.clone(), cx, |settings_content| { settings_content.theme = Some(theme_name); }); diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index d35cced642..696a5c5e4e 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -14,6 +14,7 @@ test-support = [] anyhow.workspace = true log.workspace = true editor = { path = "../editor" } +fs = { path = "../fs" } fuzzy = { path = "../fuzzy" } gpui = { path = "../gpui" } db = { path = "../db" } diff --git a/crates/welcome/src/base_keymap_picker.rs b/crates/welcome/src/base_keymap_picker.rs index 260c279e18..c0e9e0a38d 100644 --- a/crates/welcome/src/base_keymap_picker.rs +++ b/crates/welcome/src/base_keymap_picker.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use gpui::{ actions, @@ -7,7 +5,9 @@ use gpui::{ AppContext, Task, ViewContext, }; use picker::{Picker, PickerDelegate, PickerEvent}; -use settings::{settings_file::SettingsFile, BaseKeymap, Settings}; +use project::Fs; +use settings::{update_settings_file, BaseKeymap, Settings}; +use std::sync::Arc; use util::ResultExt; use workspace::Workspace; @@ -23,8 +23,9 @@ pub fn toggle( _: &ToggleBaseKeymapSelector, cx: &mut ViewContext, ) { - workspace.toggle_modal(cx, |_, cx| { - cx.add_view(|cx| BaseKeymapSelector::new(BaseKeymapSelectorDelegate::new(cx), cx)) + workspace.toggle_modal(cx, |workspace, cx| { + let fs = workspace.app_state().fs.clone(); + cx.add_view(|cx| BaseKeymapSelector::new(BaseKeymapSelectorDelegate::new(fs, cx), cx)) }); } @@ -33,10 +34,11 @@ pub type BaseKeymapSelector = Picker; pub struct BaseKeymapSelectorDelegate { matches: Vec, selected_index: usize, + fs: Arc, } impl BaseKeymapSelectorDelegate { - fn new(cx: &mut ViewContext) -> Self { + fn new(fs: Arc, cx: &mut ViewContext) -> Self { let base = cx.global::().base_keymap; let selected_index = BaseKeymap::OPTIONS .iter() @@ -45,6 +47,7 @@ impl BaseKeymapSelectorDelegate { Self { matches: Vec::new(), selected_index, + fs, } } } @@ -119,7 +122,9 @@ impl PickerDelegate for BaseKeymapSelectorDelegate { fn confirm(&mut self, cx: &mut ViewContext) { if let Some(selection) = self.matches.get(self.selected_index) { let base_keymap = BaseKeymap::from_names(&selection.string); - SettingsFile::update(cx, move |settings| settings.base_keymap = Some(base_keymap)); + update_settings_file(self.fs.clone(), cx, move |settings| { + settings.base_keymap = Some(base_keymap) + }); } cx.emit(PickerEvent::Dismiss); } diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index a3d91adc91..c2e65dc524 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -7,7 +7,7 @@ use gpui::{ elements::{Flex, Label, ParentElement}, AnyElement, AppContext, Element, Entity, Subscription, View, ViewContext, WeakViewHandle, }; -use settings::{settings_file::SettingsFile, Settings}; +use settings::{update_settings_file, Settings}; use workspace::{ item::Item, open_new, sidebar::SidebarSide, AppState, PaneBackdrop, Welcome, Workspace, @@ -169,10 +169,13 @@ impl View for WelcomePage { metrics, 0, cx, - |_, checked, cx| { - SettingsFile::update(cx, move |file| { - file.telemetry.set_metrics(checked) - }) + |this, checked, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + let fs = workspace.read(cx).app_state().fs.clone(); + update_settings_file(fs, cx, move |file| { + file.telemetry.set_metrics(checked) + }) + } }, ) .contained() @@ -185,10 +188,13 @@ impl View for WelcomePage { diagnostics, 0, cx, - |_, checked, cx| { - SettingsFile::update(cx, move |file| { - file.telemetry.set_diagnostics(checked) - }) + |this, checked, cx| { + if let Some(workspace) = this.workspace.upgrade(cx) { + let fs = workspace.read(cx).app_state().fs.clone(); + update_settings_file(fs, cx, move |file| { + file.telemetry.set_diagnostics(checked) + }) + } }, ) .contained() diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f498078b52..3d43109e6b 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -24,8 +24,8 @@ use parking_lot::Mutex; use project::Fs; use serde::{Deserialize, Serialize}; use settings::{ - self, settings_file::SettingsFile, KeymapFileContent, Settings, SettingsFileContent, - WorkingDirectory, + default_settings, handle_keymap_file_changes, handle_settings_file_changes, watch_config_file, + Settings, SettingsStore, WorkingDirectory, }; use simplelog::ConfigBuilder; use smol::process::Command; @@ -37,6 +37,7 @@ use std::{ os::unix::prelude::OsStrExt, panic, path::PathBuf, + str, sync::{Arc, Weak}, thread, time::Duration, @@ -46,7 +47,6 @@ use util::http::{self, HttpClient}; use welcome::{show_welcome_experience, FIRST_OPEN}; use fs::RealFs; -use settings::watched_json::WatchedJsonFile; #[cfg(debug_assertions)] use staff_mode::StaffMode; use theme::ThemeRegistry; @@ -75,10 +75,11 @@ fn main() { load_embedded_fonts(&app); let fs = Arc::new(RealFs); - let themes = ThemeRegistry::new(Assets, app.font_cache()); - let default_settings = Settings::defaults(Assets, &app.font_cache(), &themes); - let config_files = load_config_files(&app, fs.clone()); + let user_settings_file_rx = + watch_config_file(app.background(), fs.clone(), paths::SETTINGS.clone()); + let user_keymap_file_rx = + watch_config_file(app.background(), fs.clone(), paths::KEYMAP.clone()); let login_shell_env_loaded = if stdout_is_a_pty() { Task::ready(()) @@ -126,26 +127,18 @@ fn main() { app.run(move |cx| { cx.set_global(*RELEASE_CHANNEL); + cx.set_global(themes.clone()); #[cfg(debug_assertions)] cx.set_global(StaffMode(true)); - let (settings_file_content, keymap_file) = cx.background().block(config_files).unwrap(); - - //Setup settings global before binding actions - cx.set_global(SettingsFile::new( - &paths::SETTINGS, - settings_file_content.clone(), - fs.clone(), - )); - - settings::watch_files( - default_settings, - settings_file_content, - themes.clone(), - keymap_file, - cx, - ); + let mut store = SettingsStore::default(); + store + .set_default_settings(default_settings().as_ref(), cx) + .unwrap(); + cx.set_global(store); + handle_settings_file_changes(user_settings_file_rx, cx); + handle_keymap_file_changes(user_keymap_file_rx, cx); if !stdout_is_a_pty() { upload_previous_panics(http.clone(), cx); @@ -585,27 +578,6 @@ async fn watch_themes( None } -fn load_config_files( - app: &App, - fs: Arc, -) -> oneshot::Receiver<( - WatchedJsonFile, - WatchedJsonFile, -)> { - let executor = app.background(); - let (tx, rx) = oneshot::channel(); - executor - .clone() - .spawn(async move { - let settings_file = - WatchedJsonFile::new(fs.clone(), &executor, paths::SETTINGS.clone()).await; - let keymap_file = WatchedJsonFile::new(fs, &executor, paths::KEYMAP.clone()).await; - tx.send((settings_file, keymap_file)).ok() - }) - .detach(); - rx -} - fn connect_to_cli( server_name: &str, ) -> Result<(mpsc::Receiver, IpcSender)> { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index f687237bd2..26d2b50e0d 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -323,7 +323,7 @@ pub fn initialize_workspace( }); let toggle_terminal = cx.add_view(|cx| TerminalButton::new(workspace_handle.clone(), cx)); - let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx)); + let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx)); let diagnostic_summary = cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); let activity_indicator = From b6b2c5d1d1604178d0ff2cb108a0843b15f45782 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 May 2023 09:56:58 -0700 Subject: [PATCH 010/168] Generalize settings JSON schema logic to work w/ arbitrary setting types --- crates/settings/src/settings.rs | 139 +++++++++++++------------- crates/settings/src/settings_store.rs | 99 +++++++++++++++++- crates/zed/src/languages/json.rs | 16 ++- 3 files changed, 179 insertions(+), 75 deletions(-) diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index c3911f9254..3bc7eb81df 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -8,7 +8,7 @@ use gpui::{ fonts, AppContext, AssetSource, }; use schemars::{ - gen::{SchemaGenerator, SchemaSettings}, + gen::SchemaGenerator, schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec}, JsonSchema, }; @@ -25,7 +25,7 @@ use util::ResultExt as _; pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; pub use settings_file::*; -pub use settings_store::SettingsStore; +pub use settings_store::{SettingsJsonSchemaParams, SettingsStore}; pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json"; pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json"; @@ -150,6 +150,75 @@ impl Setting for Settings { this } + + fn json_schema( + generator: &mut SchemaGenerator, + params: &SettingsJsonSchemaParams, + ) -> schemars::schema::RootSchema { + let mut root_schema = generator.root_schema_for::(); + + // Create a schema for a theme name. + let theme_name_schema = SchemaObject { + instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))), + enum_values: Some( + params + .theme_names + .iter() + .cloned() + .map(Value::String) + .collect(), + ), + ..Default::default() + }; + + // Create a schema for a 'languages overrides' object, associating editor + // settings with specific langauges. + assert!(root_schema.definitions.contains_key("EditorSettings")); + + let languages_object_schema = SchemaObject { + instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))), + object: Some(Box::new(ObjectValidation { + properties: params + .language_names + .iter() + .map(|name| { + ( + name.clone(), + Schema::new_ref("#/definitions/EditorSettings".into()), + ) + }) + .collect(), + ..Default::default() + })), + ..Default::default() + }; + + // Add these new schemas as definitions, and modify properties of the root + // schema to reference them. + root_schema.definitions.extend([ + ("ThemeName".into(), theme_name_schema.into()), + ("Languages".into(), languages_object_schema.into()), + ]); + let root_schema_object = &mut root_schema.schema.object.as_mut().unwrap(); + + root_schema_object.properties.extend([ + ( + "theme".to_owned(), + Schema::new_ref("#/definitions/ThemeName".into()), + ), + ( + "languages".to_owned(), + Schema::new_ref("#/definitions/Languages".into()), + ), + // For backward compatibility + ( + "language_overrides".to_owned(), + Schema::new_ref("#/definitions/Languages".into()), + ), + ]); + + root_schema + } } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] @@ -926,72 +995,6 @@ impl Settings { } } -pub fn settings_file_json_schema( - theme_names: Vec, - language_names: &[String], -) -> serde_json::Value { - let settings = SchemaSettings::draft07().with(|settings| { - settings.option_add_null_type = false; - }); - let generator = SchemaGenerator::new(settings); - - let mut root_schema = generator.into_root_schema_for::(); - - // Create a schema for a theme name. - let theme_name_schema = SchemaObject { - instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))), - enum_values: Some(theme_names.into_iter().map(Value::String).collect()), - ..Default::default() - }; - - // Create a schema for a 'languages overrides' object, associating editor - // settings with specific langauges. - assert!(root_schema.definitions.contains_key("EditorSettings")); - - let languages_object_schema = SchemaObject { - instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))), - object: Some(Box::new(ObjectValidation { - properties: language_names - .iter() - .map(|name| { - ( - name.clone(), - Schema::new_ref("#/definitions/EditorSettings".into()), - ) - }) - .collect(), - ..Default::default() - })), - ..Default::default() - }; - - // Add these new schemas as definitions, and modify properties of the root - // schema to reference them. - root_schema.definitions.extend([ - ("ThemeName".into(), theme_name_schema.into()), - ("Languages".into(), languages_object_schema.into()), - ]); - let root_schema_object = &mut root_schema.schema.object.as_mut().unwrap(); - - root_schema_object.properties.extend([ - ( - "theme".to_owned(), - Schema::new_ref("#/definitions/ThemeName".into()), - ), - ( - "languages".to_owned(), - Schema::new_ref("#/definitions/Languages".into()), - ), - // For backward compatibility - ( - "language_overrides".to_owned(), - Schema::new_ref("#/definitions/Languages".into()), - ), - ]); - - serde_json::to_value(root_schema).unwrap() -} - fn merge(target: &mut T, value: Option) { if let Some(value) = value { *target = value; diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 394d457d3d..887b4eef11 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -1,8 +1,8 @@ use anyhow::{anyhow, Result}; -use collections::{hash_map, BTreeMap, HashMap, HashSet}; +use collections::{btree_map, hash_map, BTreeMap, HashMap, HashSet}; use gpui::AppContext; use lazy_static::lazy_static; -use schemars::JsonSchema; +use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema}; use serde::{de::DeserializeOwned, Deserialize as _, Serialize}; use smallvec::SmallVec; use std::{ @@ -39,6 +39,10 @@ pub trait Setting: 'static { cx: &AppContext, ) -> Self; + fn json_schema(generator: &mut SchemaGenerator, _: &SettingsJsonSchemaParams) -> RootSchema { + generator.root_schema_for::() + } + fn load_via_json_merge( default_value: &Self::FileContent, user_values: &[&Self::FileContent], @@ -54,6 +58,11 @@ pub trait Setting: 'static { } } +pub struct SettingsJsonSchemaParams<'a> { + pub theme_names: &'a [String], + pub language_names: &'a [String], +} + /// A set of strongly-typed setting values defined via multiple JSON files. #[derive(Default)] pub struct SettingsStore { @@ -84,6 +93,11 @@ trait AnySettingValue { fn value_for_path(&self, path: Option<&Path>) -> &dyn Any; fn set_global_value(&mut self, value: Box); fn set_local_value(&mut self, path: Arc, value: Box); + fn json_schema( + &self, + generator: &mut SchemaGenerator, + _: &SettingsJsonSchemaParams, + ) -> RootSchema; } struct DeserializedSetting(Box); @@ -270,6 +284,79 @@ impl SettingsStore { Ok(()) } + pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams) -> serde_json::Value { + use schemars::{ + gen::SchemaSettings, + schema::{Schema, SchemaObject}, + }; + + let settings = SchemaSettings::draft07().with(|settings| { + settings.option_add_null_type = false; + }); + let mut generator = SchemaGenerator::new(settings); + let mut combined_schema = RootSchema::default(); + + for setting_value in self.setting_values.values() { + let setting_schema = setting_value.json_schema(&mut generator, schema_params); + combined_schema + .definitions + .extend(setting_schema.definitions); + + let target_schema = if let Some(key) = setting_value.key() { + let key_schema = combined_schema + .schema + .object() + .properties + .entry(key.to_string()) + .or_insert_with(|| Schema::Object(SchemaObject::default())); + if let Schema::Object(key_schema) = key_schema { + key_schema + } else { + continue; + } + } else { + &mut combined_schema.schema + }; + + merge_schema(target_schema, setting_schema.schema); + } + + fn merge_schema(target: &mut SchemaObject, source: SchemaObject) { + if let Some(source) = source.object { + let target_properties = &mut target.object().properties; + for (key, value) in source.properties { + match target_properties.entry(key) { + btree_map::Entry::Vacant(e) => { + e.insert(value); + } + btree_map::Entry::Occupied(e) => { + if let (Schema::Object(target), Schema::Object(src)) = + (e.into_mut(), value) + { + merge_schema(target, src); + } + } + } + } + } + + overwrite(&mut target.instance_type, source.instance_type); + overwrite(&mut target.string, source.string); + overwrite(&mut target.number, source.number); + overwrite(&mut target.reference, source.reference); + overwrite(&mut target.array, source.array); + overwrite(&mut target.enum_values, source.enum_values); + + fn overwrite(target: &mut Option, source: Option) { + if let Some(source) = source { + *target = Some(source); + } + } + } + + serde_json::to_value(&combined_schema).unwrap() + } + fn recompute_values( &mut self, user_settings_changed: bool, @@ -457,6 +544,14 @@ impl AnySettingValue for SettingValue { Err(ix) => self.local_values.insert(ix, (path, value)), } } + + fn json_schema( + &self, + generator: &mut SchemaGenerator, + params: &SettingsJsonSchemaParams, + ) -> RootSchema { + T::json_schema(generator, params) + } } // impl Debug for SettingsStore { diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index d87d36abfe..8ea07c626d 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -6,7 +6,7 @@ use gpui::AppContext; use language::{LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter}; use node_runtime::NodeRuntime; use serde_json::json; -use settings::{keymap_file_json_schema, settings_file_json_schema}; +use settings::{keymap_file_json_schema, SettingsJsonSchemaParams, SettingsStore}; use smol::fs; use staff_mode::StaffMode; use std::{ @@ -128,12 +128,18 @@ impl LspAdapter for JsonLspAdapter { cx: &mut AppContext, ) -> Option> { let action_names = cx.all_action_names().collect::>(); - let theme_names = self + let theme_names = &self .themes .list(**cx.default_global::()) .map(|meta| meta.name) - .collect(); - let language_names = self.languages.language_names(); + .collect::>(); + let language_names = &self.languages.language_names(); + let settings_schema = cx + .global::() + .json_schema(&SettingsJsonSchemaParams { + theme_names, + language_names, + }); Some( future::ready(serde_json::json!({ "json": { @@ -143,7 +149,7 @@ impl LspAdapter for JsonLspAdapter { "schemas": [ { "fileMatch": [schema_file_match(&paths::SETTINGS)], - "schema": settings_file_json_schema(theme_names, &language_names), + "schema": settings_schema, }, { "fileMatch": [schema_file_match(&paths::KEYMAP)], From 926d7b356d585c5631a459dfed103c405b30b9a5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 May 2023 10:25:49 -0700 Subject: [PATCH 011/168] Define auto_update setting in the auto_update crate --- crates/auto_update/src/auto_update.rs | 29 +++++++++++++++++++++------ crates/settings/src/settings.rs | 10 +-------- crates/settings/src/settings_file.rs | 21 +++++++++++++++++-- crates/settings/src/settings_store.rs | 11 ++++++++++ 4 files changed, 54 insertions(+), 17 deletions(-) diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 68d3776e1c..2e00a204ba 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -10,7 +10,7 @@ use gpui::{ use isahc::AsyncBody; use serde::Deserialize; use serde_derive::Serialize; -use settings::Settings; +use settings::{Setting, Settings, SettingsStore}; use smol::{fs::File, io::AsyncReadExt, process::Command}; use std::{ffi::OsString, sync::Arc, time::Duration}; use update_notification::UpdateNotification; @@ -58,18 +58,35 @@ impl Entity for AutoUpdater { type Event = (); } +struct AutoUpdateSetting(bool); + +impl Setting for AutoUpdateSetting { + const KEY: Option<&'static str> = Some("auto_update"); + + type FileContent = Option; + + fn load(default_value: &Option, user_values: &[&Option], _: &AppContext) -> Self { + Self( + Self::json_merge(default_value, user_values) + .unwrap() + .unwrap(), + ) + } +} + pub fn init(http_client: Arc, server_url: String, cx: &mut AppContext) { + settings::register_setting::(cx); + if let Some(version) = (*ZED_APP_VERSION).or_else(|| cx.platform().app_version().ok()) { let auto_updater = cx.add_model(|cx| { let updater = AutoUpdater::new(version, http_client, server_url); - let mut update_subscription = cx - .global::() - .auto_update + let mut update_subscription = settings::get_setting::(None, cx) + .0 .then(|| updater.start_polling(cx)); - cx.observe_global::(move |updater, cx| { - if cx.global::().auto_update { + cx.observe_global::(move |updater, cx| { + if settings::get_setting::(None, cx).0 { if update_subscription.is_none() { update_subscription = Some(updater.start_polling(cx)) } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 3bc7eb81df..6428e4100d 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -14,7 +14,6 @@ use schemars::{ }; use serde::{Deserialize, Serialize}; use serde_json::Value; -use settings_store::Setting; use sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, @@ -25,7 +24,7 @@ use util::ResultExt as _; pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; pub use settings_file::*; -pub use settings_store::{SettingsJsonSchemaParams, SettingsStore}; +pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore}; pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json"; pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json"; @@ -62,7 +61,6 @@ pub struct Settings { pub theme: Arc, pub telemetry_defaults: TelemetrySettings, pub telemetry_overrides: TelemetrySettings, - pub auto_update: bool, pub base_keymap: BaseKeymap, } @@ -137,7 +135,6 @@ impl Setting for Settings { theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), telemetry_defaults: defaults.telemetry, telemetry_overrides: Default::default(), - auto_update: defaults.auto_update.unwrap(), base_keymap: Default::default(), features: Features { copilot: defaults.features.copilot.unwrap(), @@ -576,8 +573,6 @@ pub struct SettingsFileContent { #[serde(default)] pub telemetry: TelemetrySettings, #[serde(default)] - pub auto_update: Option, - #[serde(default)] pub base_keymap: Option, #[serde(default)] pub features: FeaturesContent, @@ -695,7 +690,6 @@ impl Settings { theme: themes.get(&defaults.theme.unwrap()).unwrap(), telemetry_defaults: defaults.telemetry, telemetry_overrides: Default::default(), - auto_update: defaults.auto_update.unwrap(), base_keymap: Default::default(), features: Features { copilot: defaults.features.copilot.unwrap(), @@ -770,7 +764,6 @@ impl Settings { self.language_overrides = data.languages; self.telemetry_overrides = data.telemetry; self.lsp = data.lsp; - merge(&mut self.auto_update, data.auto_update); } pub fn with_language_defaults( @@ -980,7 +973,6 @@ impl Settings { metrics: Some(true), }, telemetry_overrides: Default::default(), - auto_update: true, base_keymap: Default::default(), features: Features { copilot: true }, } diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 17294235ec..a07560307f 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -1,15 +1,32 @@ use crate::{ settings_store::parse_json_with_comments, settings_store::SettingsStore, KeymapFileContent, - Settings, SettingsFileContent, DEFAULT_SETTINGS_ASSET_PATH, + Setting, Settings, SettingsFileContent, DEFAULT_SETTINGS_ASSET_PATH, }; use anyhow::Result; use assets::Assets; use fs::Fs; use futures::{channel::mpsc, StreamExt}; use gpui::{executor::Background, AppContext, AssetSource}; -use std::{borrow::Cow, io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration}; +use std::{ + borrow::Cow, + io::ErrorKind, + path::{Path, PathBuf}, + str, + sync::Arc, + time::Duration, +}; use util::{paths, ResultExt}; +pub fn register_setting(cx: &mut AppContext) { + cx.update_global::(|store, cx| { + store.register_setting::(cx); + }); +} + +pub fn get_setting<'a, T: Setting>(path: Option<&Path>, cx: &'a AppContext) -> &'a T { + cx.global::().get(path) +} + pub fn default_settings() -> Cow<'static, str> { match Assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap() { Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()), diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 887b4eef11..38f9d09df0 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -43,6 +43,17 @@ pub trait Setting: 'static { generator.root_schema_for::() } + fn json_merge( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + ) -> Result { + let mut merged = serde_json::Value::Null; + for value in [default_value].iter().chain(user_values) { + merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged); + } + Ok(serde_json::from_value(merged)?) + } + fn load_via_json_merge( default_value: &Self::FileContent, user_values: &[&Self::FileContent], From 9b06be2aa27146ee39fdbb86facdb189380e8639 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 May 2023 11:06:55 -0700 Subject: [PATCH 012/168] Define vim_mode setting in vim crate --- crates/editor/src/editor.rs | 12 +++++++--- crates/settings/src/settings.rs | 9 ++----- crates/settings/src/settings_store.rs | 26 +++++++++++++++++++- crates/vim/src/test/vim_test_context.rs | 12 +++++----- crates/vim/src/vim.rs | 32 +++++++++++++++++++++---- 5 files changed, 70 insertions(+), 21 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2bb9869e6d..964b98450b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -70,7 +70,7 @@ use scroll::{ }; use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection}; use serde::{Deserialize, Serialize}; -use settings::Settings; +use settings::{Settings, SettingsStore}; use smallvec::SmallVec; use snippet::Snippet; use std::{ @@ -6868,6 +6868,12 @@ impl Editor { .as_singleton() .and_then(|b| b.read(cx).file()), ) { + let vim_mode = cx + .global::() + .untyped_user_settings() + .get("vim_mode") + == Some(&serde_json::Value::Bool(true)); + let settings = cx.global::(); let extension = Path::new(file.file_name(cx)) @@ -6880,12 +6886,12 @@ impl Editor { "save" => "save editor", _ => name, }, - json!({ "File Extension": extension, "Vim Mode": settings.vim_mode, "In Clickhouse": true }), + json!({ "File Extension": extension, "Vim Mode": vim_mode, "In Clickhouse": true }), settings.telemetry(), ); let event = ClickhouseEvent::Editor { file_extension: extension.map(ToString::to_string), - vim_mode: settings.vim_mode, + vim_mode, operation: name, copilot_enabled: settings.features.copilot, copilot_enabled_for_language: settings.show_copilot_suggestions( diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 6428e4100d..5284d3a69a 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -43,7 +43,6 @@ pub struct Settings { pub hover_popover_enabled: bool, pub show_completions_on_input: bool, pub show_call_status_icon: bool, - pub vim_mode: bool, pub autosave: Autosave, pub default_dock_anchor: DockAnchor, pub editor_defaults: EditorSettings, @@ -65,6 +64,8 @@ pub struct Settings { } impl Setting for Settings { + const KEY: Option<&'static str> = None; + type FileContent = SettingsFileContent; fn load( @@ -93,7 +94,6 @@ impl Setting for Settings { hover_popover_enabled: defaults.hover_popover_enabled.unwrap(), show_completions_on_input: defaults.show_completions_on_input.unwrap(), show_call_status_icon: defaults.show_call_status_icon.unwrap(), - vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), default_dock_anchor: defaults.default_dock_anchor.unwrap(), editor_defaults: EditorSettings { @@ -550,8 +550,6 @@ pub struct SettingsFileContent { #[serde(default)] pub show_call_status_icon: Option, #[serde(default)] - pub vim_mode: Option, - #[serde(default)] pub autosave: Option, #[serde(default)] pub default_dock_anchor: Option, @@ -647,7 +645,6 @@ impl Settings { hover_popover_enabled: defaults.hover_popover_enabled.unwrap(), show_completions_on_input: defaults.show_completions_on_input.unwrap(), show_call_status_icon: defaults.show_call_status_icon.unwrap(), - vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), default_dock_anchor: defaults.default_dock_anchor.unwrap(), editor_defaults: EditorSettings { @@ -741,7 +738,6 @@ impl Settings { &mut self.show_completions_on_input, data.show_completions_on_input, ); - merge(&mut self.vim_mode, data.vim_mode); merge(&mut self.autosave, data.autosave); merge(&mut self.default_dock_anchor, data.default_dock_anchor); merge(&mut self.base_keymap, data.base_keymap); @@ -940,7 +936,6 @@ impl Settings { hover_popover_enabled: true, show_completions_on_input: true, show_call_status_icon: true, - vim_mode: false, autosave: Autosave::Off, default_dock_anchor: DockAnchor::Bottom, editor_defaults: EditorSettings { diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 38f9d09df0..d986ea0683 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -23,7 +23,7 @@ pub trait Setting: 'static { /// The name of a key within the JSON file from which this setting should /// be deserialized. If this is `None`, then the setting will be deserialized /// from the root object. - const KEY: Option<&'static str> = None; + const KEY: Option<&'static str>; /// The type that is stored in an individual JSON file. type FileContent: Clone + Serialize + DeserializeOwned + JsonSchema; @@ -165,6 +165,28 @@ impl SettingsStore { .expect("no default value for setting type") } + /// Get the user's settings as a raw JSON value. + /// + /// This is only for debugging and reporting. For user-facing functionality, + /// use the typed setting interface. + pub fn untyped_user_settings(&self) -> &serde_json::Value { + self.user_deserialized_settings + .as_ref() + .map_or(&serde_json::Value::Null, |s| &s.untyped) + } + + /// Override the global value for a particular setting. + /// + /// This is only for tests. Normally, settings are only loaded from + /// JSON files. + #[cfg(any(test, feature = "test-support"))] + pub fn replace_value(&mut self, value: T) { + self.setting_values + .get_mut(&TypeId::of::()) + .expect("unregistered setting type") + .set_global_value(Box::new(value)) + } + /// Update the value of a setting. /// /// Returns a list of edits to apply to the JSON file. @@ -1164,6 +1186,8 @@ mod tests { } impl Setting for MultiKeySettings { + const KEY: Option<&'static str> = None; + type FileContent = MultiKeySettingsJson; fn load( diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 69227a0e45..69e432079a 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -18,8 +18,8 @@ impl<'a> VimTestContext<'a> { pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> { let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await; cx.update(|cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.vim_mode = enabled; + cx.update_global(|store: &mut SettingsStore, _| { + store.replace_value(VimModeSetting(enabled)); }); search::init(cx); crate::init(cx); @@ -52,16 +52,16 @@ impl<'a> VimTestContext<'a> { pub fn enable_vim(&mut self) { self.cx.update(|cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.vim_mode = true; + cx.update_global(|store: &mut SettingsStore, _| { + store.replace_value(VimModeSetting(true)) }); }) } pub fn disable_vim(&mut self) { self.cx.update(|cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.vim_mode = false; + cx.update_global(|store: &mut SettingsStore, _| { + store.replace_value(VimModeSetting(false)) }); }) } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index cc686f851f..65c46e81cf 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -22,11 +22,13 @@ use language::CursorShape; use motion::Motion; use normal::normal_replace; use serde::Deserialize; -use settings::Settings; +use settings::{Setting, SettingsStore}; use state::{Mode, Operator, VimState}; use visual::visual_replace; use workspace::{self, Workspace}; +struct VimModeSetting(bool); + #[derive(Clone, Deserialize, PartialEq)] pub struct SwitchMode(pub Mode); @@ -40,6 +42,8 @@ actions!(vim, [Tab, Enter]); impl_actions!(vim, [Number, SwitchMode, PushOperator]); pub fn init(cx: &mut AppContext) { + settings::register_setting::(cx); + editor_events::init(cx); normal::init(cx); visual::init(cx); @@ -91,11 +95,11 @@ pub fn init(cx: &mut AppContext) { filter.filtered_namespaces.insert("vim"); }); cx.update_default_global(|vim: &mut Vim, cx: &mut AppContext| { - vim.set_enabled(cx.global::().vim_mode, cx) + vim.set_enabled(settings::get_setting::(None, cx).0, cx) }); - cx.observe_global::(|cx| { + cx.observe_global::(|cx| { cx.update_default_global(|vim: &mut Vim, cx: &mut AppContext| { - vim.set_enabled(cx.global::().vim_mode, cx) + vim.set_enabled(settings::get_setting::(None, cx).0, cx) }); }) .detach(); @@ -330,6 +334,26 @@ impl Vim { } } +impl Setting for VimModeSetting { + const KEY: Option<&'static str> = Some("vim_mode"); + + type FileContent = Option; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &AppContext, + ) -> Self { + Self( + user_values + .first() + .map(|e| **e) + .flatten() + .unwrap_or(default_value.unwrap()), + ) + } +} + fn local_selections_changed(newest_empty: bool, cx: &mut WindowContext) { Vim::update(cx, |vim, cx| { if vim.enabled && vim.state.mode == Mode::Normal && !newest_empty { From aa6ea920e2b8fe4e38c09b489aab9d7065c264ce Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 May 2023 12:17:52 -0700 Subject: [PATCH 013/168] Define telemetry settings in the client crate --- Cargo.lock | 2 + Cargo.toml | 1 + crates/auto_update/src/auto_update.rs | 6 +- crates/client/Cargo.toml | 1 + crates/client/src/client.rs | 61 ++++++++++++++++----- crates/client/src/telemetry.rs | 11 ++-- crates/client/src/user.rs | 13 +++-- crates/copilot_button/src/copilot_button.rs | 6 +- crates/editor/src/editor.rs | 8 +-- crates/settings/Cargo.toml | 2 +- crates/settings/src/settings.rs | 59 -------------------- crates/settings/src/settings_file.rs | 11 ++-- crates/theme_selector/src/theme_selector.rs | 2 +- crates/welcome/Cargo.toml | 1 + crates/welcome/src/base_keymap_picker.rs | 2 +- crates/welcome/src/welcome.rs | 26 +++++---- crates/zed/src/main.rs | 8 +-- 17 files changed, 100 insertions(+), 120 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 266b0e6d41..baba47b216 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1155,6 +1155,7 @@ dependencies = [ "postage", "rand 0.8.5", "rpc", + "schemars", "serde", "serde_derive", "settings", @@ -8315,6 +8316,7 @@ name = "welcome" version = "0.1.0" dependencies = [ "anyhow", + "client", "db", "editor", "fs", diff --git a/Cargo.toml b/Cargo.toml index 15df687d41..0a73b878f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,6 +85,7 @@ parking_lot = { version = "0.11.1" } postage = { version = "0.5", features = ["futures-traits"] } rand = { version = "0.8.5" } regex = { version = "1.5" } +schemars = { version = "0.8" } serde = { version = "1.0", features = ["derive", "rc"] } serde_derive = { version = "1.0", features = ["deserialize_in_place"] } serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] } diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 2e00a204ba..54f3f93b00 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -1,7 +1,7 @@ mod update_notification; use anyhow::{anyhow, Context, Result}; -use client::{Client, ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; +use client::{Client, TelemetrySettings, ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; use db::kvp::KEY_VALUE_STORE; use gpui::{ actions, platform::AppVersion, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, @@ -10,7 +10,7 @@ use gpui::{ use isahc::AsyncBody; use serde::Deserialize; use serde_derive::Serialize; -use settings::{Setting, Settings, SettingsStore}; +use settings::{Setting, SettingsStore}; use smol::{fs::File, io::AsyncReadExt, process::Command}; use std::{ffi::OsString, sync::Arc, time::Duration}; use update_notification::UpdateNotification; @@ -279,7 +279,7 @@ impl AutoUpdater { let release_channel = cx .has_global::() .then(|| cx.global::().display_name()); - let telemetry = cx.global::().telemetry().metrics(); + let telemetry = settings::get_setting::(None, cx).metrics; (installation_id, release_channel, telemetry) }); diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 99c492d638..3ecc515986 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -31,6 +31,7 @@ log.workspace = true parking_lot.workspace = true postage.workspace = true rand.workspace = true +schemars.workspace = true smol.workspace = true thiserror.workspace = true time.workspace = true diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 18a0f32ed6..9d24254b40 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -15,19 +15,17 @@ use futures::{ TryStreamExt, }; use gpui::{ - actions, - platform::AppVersion, - serde_json::{self}, - AnyModelHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, - ModelHandle, Task, View, ViewContext, WeakViewHandle, + actions, platform::AppVersion, serde_json, AnyModelHandle, AnyWeakModelHandle, + AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle, Task, View, ViewContext, + WeakViewHandle, }; use lazy_static::lazy_static; use parking_lot::RwLock; use postage::watch; use rand::prelude::*; use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage}; -use serde::Deserialize; -use settings::Settings; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; use std::{ any::TypeId, collections::HashMap, @@ -73,6 +71,8 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5); actions!(client, [SignIn, SignOut]); pub fn init(client: Arc, cx: &mut AppContext) { + settings::register_setting::(cx); + cx.add_global_action({ let client = client.clone(); move |_: &SignIn, cx| { @@ -326,6 +326,41 @@ impl Drop for PendingEntitySubscription { } } +#[derive(Copy, Clone)] +pub struct TelemetrySettings { + pub diagnostics: bool, + pub metrics: bool, +} + +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +pub struct TelemetrySettingsContent { + pub diagnostics: Option, + pub metrics: Option, +} + +impl settings::Setting for TelemetrySettings { + const KEY: Option<&'static str> = Some("telemetry"); + + type FileContent = TelemetrySettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &AppContext, + ) -> Self { + Self { + diagnostics: user_values + .first() + .and_then(|v| v.diagnostics) + .unwrap_or(default_value.diagnostics.unwrap()), + metrics: user_values + .first() + .and_then(|v| v.metrics) + .unwrap_or(default_value.metrics.unwrap()), + } + } +} + impl Client { pub fn new(http: Arc, cx: &AppContext) -> Arc { Arc::new(Self { @@ -447,9 +482,7 @@ impl Client { })); } Status::SignedOut | Status::UpgradeRequired => { - let telemetry_settings = cx.read(|cx| cx.global::().telemetry()); - self.telemetry - .set_authenticated_user_info(None, false, telemetry_settings); + cx.read(|cx| self.telemetry.set_authenticated_user_info(None, false, cx)); state._reconnect_task.take(); } _ => {} @@ -740,7 +773,7 @@ impl Client { self.telemetry().report_mixpanel_event( "read credentials from keychain", Default::default(), - cx.global::().telemetry(), + *settings::get_setting::(None, cx), ); }); } @@ -1033,7 +1066,9 @@ impl Client { let executor = cx.background(); let telemetry = self.telemetry.clone(); let http = self.http.clone(); - let metrics_enabled = cx.read(|cx| cx.global::().telemetry()); + + let telemetry_settings = + cx.read(|cx| *settings::get_setting::(None, cx)); executor.clone().spawn(async move { // Generate a pair of asymmetric encryption keys. The public key will be used by the @@ -1120,7 +1155,7 @@ impl Client { telemetry.report_mixpanel_event( "authenticate with browser", Default::default(), - metrics_enabled, + telemetry_settings, ); Ok(Credentials { diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 7151dcd7bb..5c8f208137 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -1,4 +1,4 @@ -use crate::{ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; +use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; use db::kvp::KEY_VALUE_STORE; use gpui::{ executor::Background, @@ -9,7 +9,6 @@ use lazy_static::lazy_static; use parking_lot::Mutex; use serde::Serialize; use serde_json::json; -use settings::TelemetrySettings; use std::{ io::Write, mem, @@ -241,9 +240,9 @@ impl Telemetry { self: &Arc, metrics_id: Option, is_staff: bool, - telemetry_settings: TelemetrySettings, + cx: &AppContext, ) { - if !telemetry_settings.metrics() { + if !settings::get_setting::(None, cx).metrics { return; } @@ -285,7 +284,7 @@ impl Telemetry { event: ClickhouseEvent, telemetry_settings: TelemetrySettings, ) { - if !telemetry_settings.metrics() { + if !telemetry_settings.metrics { return; } @@ -321,7 +320,7 @@ impl Telemetry { properties: Value, telemetry_settings: TelemetrySettings, ) { - if !telemetry_settings.metrics() { + if !telemetry_settings.metrics { return; } diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 6b3aa7e442..4c2721ffeb 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -5,7 +5,6 @@ use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt}; use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task}; use postage::{sink::Sink, watch}; use rpc::proto::{RequestMessage, UsersResponse}; -use settings::Settings; use staff_mode::StaffMode; use std::sync::{Arc, Weak}; use util::http::HttpClient; @@ -144,11 +143,13 @@ impl UserStore { let fetch_metrics_id = client.request(proto::GetPrivateUserInfo {}).log_err(); let (user, info) = futures::join!(fetch_user, fetch_metrics_id); - client.telemetry.set_authenticated_user_info( - info.as_ref().map(|info| info.metrics_id.clone()), - info.as_ref().map(|info| info.staff).unwrap_or(false), - cx.read(|cx| cx.global::().telemetry()), - ); + cx.read(|cx| { + client.telemetry.set_authenticated_user_info( + info.as_ref().map(|info| info.metrics_id.clone()), + info.as_ref().map(|info| info.staff).unwrap_or(false), + cx, + ) + }); cx.update(|cx| { cx.update_default_global(|staff_mode: &mut StaffMode, _| { diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index fec48f1f34..61c84bc517 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -366,7 +366,7 @@ async fn configure_disabled_globs( fn toggle_copilot_globally(fs: Arc, cx: &mut AppContext) { let show_copilot_suggestions = cx.global::().show_copilot_suggestions(None, None); - update_settings_file(fs, cx, move |file_contents| { + update_settings_file::(fs, cx, move |file_contents| { file_contents.editor.show_copilot_suggestions = Some((!show_copilot_suggestions).into()) }); } @@ -376,7 +376,7 @@ fn toggle_copilot_for_language(language: Arc, fs: Arc, cx: &mut App .global::() .show_copilot_suggestions(Some(&language), None); - update_settings_file(fs, cx, move |file_contents| { + update_settings_file::(fs, cx, move |file_contents| { file_contents.languages.insert( language, settings::EditorSettings { @@ -388,7 +388,7 @@ fn toggle_copilot_for_language(language: Arc, fs: Arc, cx: &mut App } fn hide_copilot(fs: Arc, cx: &mut AppContext) { - update_settings_file(fs, cx, move |file_contents| { + update_settings_file::(fs, cx, move |file_contents| { file_contents.features.copilot = Some(false) }); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 964b98450b..abea2d8f3a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -22,7 +22,7 @@ pub mod test; use aho_corasick::AhoCorasick; use anyhow::{anyhow, Result}; use blink_manager::BlinkManager; -use client::ClickhouseEvent; +use client::{ClickhouseEvent, TelemetrySettings}; use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use copilot::Copilot; @@ -6873,7 +6873,7 @@ impl Editor { .untyped_user_settings() .get("vim_mode") == Some(&serde_json::Value::Bool(true)); - + let telemetry_settings = *settings::get_setting::(None, cx); let settings = cx.global::(); let extension = Path::new(file.file_name(cx)) @@ -6887,7 +6887,7 @@ impl Editor { _ => name, }, json!({ "File Extension": extension, "Vim Mode": vim_mode, "In Clickhouse": true }), - settings.telemetry(), + telemetry_settings, ); let event = ClickhouseEvent::Editor { file_extension: extension.map(ToString::to_string), @@ -6903,7 +6903,7 @@ impl Editor { .as_deref(), ), }; - telemetry.report_clickhouse_event(event, settings.telemetry()) + telemetry.report_clickhouse_event(event, telemetry_settings) } } diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 6d6d303a82..ba9bc38b46 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -27,7 +27,7 @@ glob.workspace = true json_comments = "0.2" lazy_static.workspace = true postage.workspace = true -schemars = "0.8" +schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 5284d3a69a..82a4148c0d 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -58,8 +58,6 @@ pub struct Settings { pub language_overrides: HashMap, EditorSettings>, pub lsp: HashMap, LspSettings>, pub theme: Arc, - pub telemetry_defaults: TelemetrySettings, - pub telemetry_overrides: TelemetrySettings, pub base_keymap: BaseKeymap, } @@ -133,8 +131,6 @@ impl Setting for Settings { language_overrides: Default::default(), lsp: defaults.lsp.clone(), theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), - telemetry_defaults: defaults.telemetry, - telemetry_overrides: Default::default(), base_keymap: Default::default(), features: Features { copilot: defaults.features.copilot.unwrap(), @@ -260,30 +256,6 @@ impl BaseKeymap { } } -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -pub struct TelemetrySettings { - diagnostics: Option, - metrics: Option, -} - -impl TelemetrySettings { - pub fn metrics(&self) -> bool { - self.metrics.unwrap() - } - - pub fn diagnostics(&self) -> bool { - self.diagnostics.unwrap() - } - - pub fn set_metrics(&mut self, value: bool) { - self.metrics = Some(value); - } - - pub fn set_diagnostics(&mut self, value: bool) { - self.diagnostics = Some(value); - } -} - #[derive(Clone, Debug, Default)] pub struct CopilotSettings { pub disabled_globs: Vec, @@ -569,8 +541,6 @@ pub struct SettingsFileContent { #[serde(default)] pub theme: Option, #[serde(default)] - pub telemetry: TelemetrySettings, - #[serde(default)] pub base_keymap: Option, #[serde(default)] pub features: FeaturesContent, @@ -685,8 +655,6 @@ impl Settings { language_overrides: Default::default(), lsp: defaults.lsp.clone(), theme: themes.get(&defaults.theme.unwrap()).unwrap(), - telemetry_defaults: defaults.telemetry, - telemetry_overrides: Default::default(), base_keymap: Default::default(), features: Features { copilot: defaults.features.copilot.unwrap(), @@ -758,7 +726,6 @@ impl Settings { self.terminal_overrides.copy_on_select = data.terminal.copy_on_select; self.terminal_overrides = data.terminal; self.language_overrides = data.languages; - self.telemetry_overrides = data.telemetry; self.lsp = data.lsp; } @@ -869,27 +836,6 @@ impl Settings { }) } - pub fn telemetry(&self) -> TelemetrySettings { - TelemetrySettings { - diagnostics: Some(self.telemetry_diagnostics()), - metrics: Some(self.telemetry_metrics()), - } - } - - pub fn telemetry_diagnostics(&self) -> bool { - self.telemetry_overrides - .diagnostics - .or(self.telemetry_defaults.diagnostics) - .expect("missing default") - } - - pub fn telemetry_metrics(&self) -> bool { - self.telemetry_overrides - .metrics - .or(self.telemetry_defaults.metrics) - .expect("missing default") - } - fn terminal_setting(&self, f: F) -> R where F: Fn(&TerminalSettings) -> Option, @@ -963,11 +909,6 @@ impl Settings { language_overrides: Default::default(), lsp: Default::default(), theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), - telemetry_defaults: TelemetrySettings { - diagnostics: Some(true), - metrics: Some(true), - }, - telemetry_overrides: Default::default(), base_keymap: Default::default(), features: Features { copilot: true }, } diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index a07560307f..990ccf0249 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -1,6 +1,6 @@ use crate::{ settings_store::parse_json_with_comments, settings_store::SettingsStore, KeymapFileContent, - Setting, Settings, SettingsFileContent, DEFAULT_SETTINGS_ASSET_PATH, + Setting, Settings, DEFAULT_SETTINGS_ASSET_PATH, }; use anyhow::Result; use assets::Assets; @@ -158,10 +158,10 @@ async fn load_settings(fs: &Arc) -> Result { } } -pub fn update_settings_file( +pub fn update_settings_file( fs: Arc, cx: &mut AppContext, - update: impl 'static + Send + FnOnce(&mut SettingsFileContent), + update: impl 'static + Send + FnOnce(&mut T::FileContent), ) { cx.spawn(|cx| async move { let old_text = cx @@ -172,10 +172,7 @@ pub fn update_settings_file( }) .await?; - let edits = cx.read(|cx| { - cx.global::() - .update::(&old_text, update) - }); + let edits = cx.read(|cx| cx.global::().update::(&old_text, update)); let mut new_text = old_text; for (range, replacement) in edits.into_iter().rev() { diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index a35e546891..27c5a9ef4e 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -129,7 +129,7 @@ impl PickerDelegate for ThemeSelectorDelegate { self.selection_completed = true; let theme_name = cx.global::().theme.meta.name.clone(); - update_settings_file(self.fs.clone(), cx, |settings_content| { + update_settings_file::(self.fs.clone(), cx, |settings_content| { settings_content.theme = Some(theme_name); }); diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index 696a5c5e4e..82cd4ca35a 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -13,6 +13,7 @@ test-support = [] [dependencies] anyhow.workspace = true log.workspace = true +client = { path = "../client" } editor = { path = "../editor" } fs = { path = "../fs" } fuzzy = { path = "../fuzzy" } diff --git a/crates/welcome/src/base_keymap_picker.rs b/crates/welcome/src/base_keymap_picker.rs index c0e9e0a38d..24600d5b82 100644 --- a/crates/welcome/src/base_keymap_picker.rs +++ b/crates/welcome/src/base_keymap_picker.rs @@ -122,7 +122,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate { fn confirm(&mut self, cx: &mut ViewContext) { if let Some(selection) = self.matches.get(self.selected_index) { let base_keymap = BaseKeymap::from_names(&selection.string); - update_settings_file(self.fs.clone(), cx, move |settings| { + update_settings_file::(self.fs.clone(), cx, move |settings| { settings.base_keymap = Some(base_keymap) }); } diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index c2e65dc524..6b8fe7312d 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -2,6 +2,7 @@ mod base_keymap_picker; use std::{borrow::Cow, sync::Arc}; +use client::TelemetrySettings; use db::kvp::KEY_VALUE_STORE; use gpui::{ elements::{Flex, Label, ParentElement}, @@ -63,10 +64,7 @@ impl View for WelcomePage { let width = theme.welcome.page_width; - let (diagnostics, metrics) = { - let telemetry = settings.telemetry(); - (telemetry.diagnostics(), telemetry.metrics()) - }; + let telemetry_settings = *settings::get_setting::(None, cx); enum Metrics {} enum Diagnostics {} @@ -166,15 +164,17 @@ impl View for WelcomePage { .with_style(theme.welcome.usage_note.container), ), &theme.welcome.checkbox, - metrics, + telemetry_settings.metrics, 0, cx, |this, checked, cx| { if let Some(workspace) = this.workspace.upgrade(cx) { let fs = workspace.read(cx).app_state().fs.clone(); - update_settings_file(fs, cx, move |file| { - file.telemetry.set_metrics(checked) - }) + update_settings_file::( + fs, + cx, + move |setting| setting.metrics = Some(checked), + ) } }, ) @@ -185,15 +185,17 @@ impl View for WelcomePage { theme::ui::checkbox::( "Send crash reports", &theme.welcome.checkbox, - diagnostics, + telemetry_settings.diagnostics, 0, cx, |this, checked, cx| { if let Some(workspace) = this.workspace.upgrade(cx) { let fs = workspace.read(cx).app_state().fs.clone(); - update_settings_file(fs, cx, move |file| { - file.telemetry.set_diagnostics(checked) - }) + update_settings_file::( + fs, + cx, + move |setting| setting.diagnostics = Some(checked), + ) } }, ) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 3d43109e6b..58c68c56d3 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -8,7 +8,7 @@ use cli::{ ipc::{self, IpcSender}, CliRequest, CliResponse, IpcHandshake, }; -use client::{self, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; +use client::{self, TelemetrySettings, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; use db::kvp::KEY_VALUE_STORE; use editor::Editor; use futures::{ @@ -187,7 +187,7 @@ fn main() { client.telemetry().report_mixpanel_event( "start app", Default::default(), - cx.global::().telemetry(), + *settings::get_setting::(None, cx), ); let app_state = Arc::new(AppState { @@ -407,7 +407,7 @@ fn init_panic_hook(app_version: String) { } fn upload_previous_panics(http: Arc, cx: &mut AppContext) { - let diagnostics_telemetry = cx.global::().telemetry_diagnostics(); + let telemetry_settings = *settings::get_setting::(None, cx); cx.background() .spawn({ @@ -437,7 +437,7 @@ fn upload_previous_panics(http: Arc, cx: &mut AppContext) { continue; }; - if diagnostics_telemetry { + if telemetry_settings.diagnostics { let panic_data_text = smol::fs::read_to_string(&child_path) .await .context("error reading panic file")?; From 68867fe2e117ff0a119b6f57c43864950d5ddcb7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 May 2023 12:48:09 -0700 Subject: [PATCH 014/168] Define journal settings in journal crate --- Cargo.lock | 2 + crates/journal/Cargo.toml | 5 ++- crates/journal/src/journal.rs | 68 ++++++++++++++++++++++++++------- crates/settings/src/settings.rs | 39 ------------------- 4 files changed, 61 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index baba47b216..ff82c5e67b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3336,6 +3336,8 @@ dependencies = [ "editor", "gpui", "log", + "schemars", + "serde", "settings", "shellexpand", "util", diff --git a/crates/journal/Cargo.toml b/crates/journal/Cargo.toml index b88e3e093a..c1d9bde89e 100644 --- a/crates/journal/Cargo.toml +++ b/crates/journal/Cargo.toml @@ -13,9 +13,12 @@ editor = { path = "../editor" } gpui = { path = "../gpui" } util = { path = "../util" } workspace = { path = "../workspace" } +settings = { path = "../settings" } + anyhow.workspace = true chrono = "0.4" dirs = "4.0" +serde.workspace = true +schemars.workspace = true log.workspace = true -settings = { path = "../settings" } shellexpand = "2.1.0" diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 4b9622ece9..938cd82922 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -1,7 +1,8 @@ use chrono::{Datelike, Local, NaiveTime, Timelike}; use editor::{scroll::autoscroll::Autoscroll, Editor}; use gpui::{actions, AppContext}; -use settings::{HourFormat, Settings}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; use std::{ fs::OpenOptions, path::{Path, PathBuf}, @@ -11,13 +12,61 @@ use workspace::AppState; actions!(journal, [NewJournalEntry]); +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct JournalSettings { + pub path: Option, + pub hour_format: Option, +} + +impl Default for JournalSettings { + fn default() -> Self { + Self { + path: Some("~".into()), + hour_format: Some(Default::default()), + } + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum HourFormat { + #[default] + Hour12, + Hour24, +} + +impl settings::Setting for JournalSettings { + const KEY: Option<&'static str> = Some("journal"); + + type FileContent = Self; + + fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Self { + Self { + path: Some( + user_values + .first() + .and_then(|s| s.path.clone()) + .unwrap_or(default_value.path.clone().unwrap()), + ), + hour_format: Some( + user_values + .first() + .and_then(|s| s.hour_format.clone()) + .unwrap_or(default_value.hour_format.clone().unwrap()), + ), + } + } +} + pub fn init(app_state: Arc, cx: &mut AppContext) { + settings::register_setting::(cx); + cx.add_global_action(move |_: &NewJournalEntry, cx| new_journal_entry(app_state.clone(), cx)); } pub fn new_journal_entry(app_state: Arc, cx: &mut AppContext) { - let settings = cx.global::(); - let journal_dir = match journal_dir(&settings) { + let settings = settings::get_setting::(None, cx); + let journal_dir = match journal_dir(settings.path.as_ref().unwrap()) { Some(journal_dir) => journal_dir, None => { log::error!("Can't determine journal directory"); @@ -31,8 +80,7 @@ pub fn new_journal_entry(app_state: Arc, cx: &mut AppContext) { .join(format!("{:02}", now.month())); let entry_path = month_dir.join(format!("{:02}.md", now.day())); let now = now.time(); - let hour_format = &settings.journal_overrides.hour_format; - let entry_heading = heading_entry(now, &hour_format); + let entry_heading = heading_entry(now, &settings.hour_format); let create_entry = cx.background().spawn(async move { std::fs::create_dir_all(month_dir)?; @@ -76,14 +124,8 @@ pub fn new_journal_entry(app_state: Arc, cx: &mut AppContext) { .detach_and_log_err(cx); } -fn journal_dir(settings: &Settings) -> Option { - let journal_dir = settings - .journal_overrides - .path - .as_ref() - .unwrap_or(settings.journal_defaults.path.as_ref()?); - - let expanded_journal_dir = shellexpand::full(&journal_dir) //TODO handle this better +fn journal_dir(path: &str) -> Option { + let expanded_journal_dir = shellexpand::full(path) //TODO handle this better .ok() .map(|dir| Path::new(&dir.to_string()).to_path_buf().join("journal")); diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 82a4148c0d..b1e716abb8 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -50,8 +50,6 @@ pub struct Settings { pub git: GitSettings, pub git_overrides: GitSettings, pub copilot: CopilotSettings, - pub journal_defaults: JournalSettings, - pub journal_overrides: JournalSettings, pub terminal_defaults: TerminalSettings, pub terminal_overrides: TerminalSettings, pub language_defaults: HashMap, EditorSettings>, @@ -123,8 +121,6 @@ impl Setting for Settings { }, git: defaults.git.unwrap(), git_overrides: Default::default(), - journal_defaults: defaults.journal.clone(), - journal_overrides: Default::default(), terminal_defaults: defaults.terminal.clone(), terminal_overrides: Default::default(), language_defaults: defaults.languages.clone(), @@ -336,34 +332,6 @@ pub enum Autosave { OnWindowChange, } -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -pub struct JournalSettings { - pub path: Option, - pub hour_format: Option, -} - -impl Default for JournalSettings { - fn default() -> Self { - Self { - path: Some("~".into()), - hour_format: Some(Default::default()), - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum HourFormat { - Hour12, - Hour24, -} - -impl Default for HourFormat { - fn default() -> Self { - Self::Hour12 - } -} - #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct TerminalSettings { pub shell: Option, @@ -528,8 +496,6 @@ pub struct SettingsFileContent { #[serde(flatten)] pub editor: EditorSettings, #[serde(default)] - pub journal: JournalSettings, - #[serde(default)] pub terminal: TerminalSettings, #[serde(default)] pub git: Option, @@ -647,8 +613,6 @@ impl Settings { }, git: defaults.git.unwrap(), git_overrides: Default::default(), - journal_defaults: defaults.journal, - journal_overrides: Default::default(), terminal_defaults: defaults.terminal, terminal_overrides: Default::default(), language_defaults: defaults.languages, @@ -721,7 +685,6 @@ impl Settings { } self.editor_overrides = data.editor; self.git_overrides = data.git.unwrap_or_default(); - self.journal_overrides = data.journal; self.terminal_defaults.font_size = data.terminal.font_size; self.terminal_overrides.copy_on_select = data.terminal.copy_on_select; self.terminal_overrides = data.terminal; @@ -899,8 +862,6 @@ impl Settings { }, editor_overrides: Default::default(), copilot: Default::default(), - journal_defaults: Default::default(), - journal_overrides: Default::default(), terminal_defaults: Default::default(), terminal_overrides: Default::default(), git: Default::default(), From 9405b4995767cec489ef3c33aaec3d4afc293d29 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 10 May 2023 16:47:09 -0400 Subject: [PATCH 015/168] v0.87.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eee0873e5b..99b0479d1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8531,7 +8531,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.86.0" +version = "0.87.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 70c71cc18e..b74057c907 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.86.0" +version = "0.87.0" publish = false [lib] From 6385e51957590fc43b073bb6c48d2eeb3e5594f7 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 10 May 2023 18:16:20 -0400 Subject: [PATCH 016/168] collab 0.12.1 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 99b0479d1d..bef04fce14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1189,7 +1189,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.12.0" +version = "0.12.1" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index a980fdc13e..d4941438a0 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.12.0" +version = "0.12.1" publish = false [[bin]] From cee7edabf94dc3cf0ffa55e9895613224eb2a6aa Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 May 2023 16:39:59 -0700 Subject: [PATCH 017/168] Ensure the SettingsStore global is added in tests --- crates/client/src/client.rs | 25 +++++++++++-------- crates/collab/src/tests.rs | 5 +++- .../src/tests/randomized_integration_tests.rs | 7 ++++-- .../src/test/editor_lsp_test_context.rs | 4 +-- crates/gpui/src/app.rs | 11 +++++++- crates/settings/src/settings_store.rs | 23 +++++++++++------ crates/vim/src/test/vim_test_context.rs | 5 ++-- crates/workspace/src/workspace.rs | 7 ++++-- crates/zed/src/main.rs | 2 +- crates/zed/src/zed.rs | 4 +-- 10 files changed, 61 insertions(+), 32 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 9d24254b40..bcf9152051 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -70,27 +70,30 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5); actions!(client, [SignIn, SignOut]); -pub fn init(client: Arc, cx: &mut AppContext) { +pub fn init(client: &Arc, cx: &mut AppContext) { + let client = Arc::downgrade(client); settings::register_setting::(cx); cx.add_global_action({ let client = client.clone(); move |_: &SignIn, cx| { - let client = client.clone(); - cx.spawn( - |cx| async move { client.authenticate_and_connect(true, &cx).log_err().await }, - ) - .detach(); + if let Some(client) = client.upgrade() { + cx.spawn( + |cx| async move { client.authenticate_and_connect(true, &cx).log_err().await }, + ) + .detach(); + } } }); cx.add_global_action({ let client = client.clone(); move |_: &SignOut, cx| { - let client = client.clone(); - cx.spawn(|cx| async move { - client.disconnect(&cx); - }) - .detach(); + if let Some(client) = client.upgrade() { + cx.spawn(|cx| async move { + client.disconnect(&cx); + }) + .detach(); + } } }); } diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 64768d1a47..50fb9658fe 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -19,7 +19,7 @@ use gpui::{ use language::LanguageRegistry; use parking_lot::Mutex; use project::{Project, WorktreeId}; -use settings::Settings; +use settings::{Settings, SettingsStore}; use std::{ cell::{Ref, RefCell, RefMut}, env, @@ -102,6 +102,7 @@ impl TestServer { async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient { cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); cx.set_global(Settings::test(cx)); }); @@ -185,6 +186,8 @@ impl TestServer { }) }); + cx.update(|cx| client::init(&client, cx)); + let fs = FakeFs::new(cx.background()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx)); let app_state = Arc::new(workspace::AppState { diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index c4326be101..3a87ced2e1 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -20,7 +20,7 @@ use rand::{ prelude::*, }; use serde::{Deserialize, Serialize}; -use settings::Settings; +use settings::{Settings, SettingsStore}; use std::{ env, ops::Range, @@ -148,8 +148,11 @@ async fn test_random_collaboration( for (client, mut cx) in clients { cx.update(|cx| { + let store = cx.remove_global::(); + let settings = cx.remove_global::(); cx.clear_globals(); - cx.set_global(Settings::test(cx)); + cx.set_global(store); + cx.set_global(settings); drop(client); }); } diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index fe9a7909b8..e268e2a0ce 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -34,13 +34,13 @@ impl<'a> EditorLspTestContext<'a> { ) -> EditorLspTestContext<'a> { use json::json; + let app_state = cx.update(AppState::test); + cx.update(|cx| { crate::init(cx); pane::init(cx); }); - let app_state = cx.update(AppState::test); - let file_name = format!( "file.{}", language diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index d5a7a2f3ab..5def0bed9d 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1174,7 +1174,7 @@ impl AppContext { this.notify_global(type_id); result } else { - panic!("No global added for {}", std::any::type_name::()); + panic!("no global added for {}", std::any::type_name::()); } } @@ -1182,6 +1182,15 @@ impl AppContext { self.globals.clear(); } + pub fn remove_global(&mut self) -> T { + *self + .globals + .remove(&TypeId::of::()) + .unwrap_or_else(|| panic!("no global added for {}", std::any::type_name::())) + .downcast() + .unwrap() + } + pub fn add_model(&mut self, build_model: F) -> ModelHandle where T: Entity, diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index d986ea0683..59f7bee10f 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -122,11 +122,11 @@ impl SettingsStore { /// Add a new type of setting to the store. pub fn register_setting(&mut self, cx: &AppContext) { let setting_type_id = TypeId::of::(); - let entry = self.setting_values.entry(setting_type_id); if matches!(entry, hash_map::Entry::Occupied(_)) { - panic!("duplicate setting type: {}", type_name::()); + return; } + let setting_value = entry.or_insert(Box::new(SettingValue:: { global_value: None, local_values: Vec::new(), @@ -142,6 +142,7 @@ impl SettingsStore { user_values_stack = vec![user_value]; } } + if let Some(default_deserialized_value) = default_settings.typed.get(&setting_type_id) { setting_value.set_global_value(setting_value.load_setting( default_deserialized_value, @@ -159,7 +160,7 @@ impl SettingsStore { pub fn get(&self, path: Option<&Path>) -> &T { self.setting_values .get(&TypeId::of::()) - .expect("unregistered setting type") + .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::())) .value_for_path(path) .downcast_ref::() .expect("no default value for setting type") @@ -175,6 +176,14 @@ impl SettingsStore { .map_or(&serde_json::Value::Null, |s| &s.untyped) } + #[cfg(any(test, feature = "test-support"))] + pub fn test(cx: &AppContext) -> Self { + let mut this = Self::default(); + this.set_default_settings(&crate::test_settings(), cx) + .unwrap(); + this + } + /// Override the global value for a particular setting. /// /// This is only for tests. Normally, settings are only loaded from @@ -183,7 +192,7 @@ impl SettingsStore { pub fn replace_value(&mut self, value: T) { self.setting_values .get_mut(&TypeId::of::()) - .expect("unregistered setting type") + .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::())) .set_global_value(Box::new(value)) } @@ -268,7 +277,7 @@ impl SettingsStore { pub fn set_default_settings( &mut self, default_settings_content: &str, - cx: &mut AppContext, + cx: &AppContext, ) -> Result<()> { let deserialized_setting_map = self.load_setting_map(default_settings_content)?; if deserialized_setting_map.typed.len() != self.setting_values.len() { @@ -290,7 +299,7 @@ impl SettingsStore { pub fn set_user_settings( &mut self, user_settings_content: &str, - cx: &mut AppContext, + cx: &AppContext, ) -> Result<()> { let user_settings = self.load_setting_map(user_settings_content)?; let old_user_settings = @@ -304,7 +313,7 @@ impl SettingsStore { &mut self, path: Arc, settings_content: Option<&str>, - cx: &mut AppContext, + cx: &AppContext, ) -> Result<()> { let removed_map = if let Some(settings_content) = settings_content { self.local_deserialized_settings diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 69e432079a..b426cea717 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -18,11 +18,12 @@ impl<'a> VimTestContext<'a> { pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> { let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await; cx.update(|cx| { + search::init(cx); + crate::init(cx); + cx.update_global(|store: &mut SettingsStore, _| { store.replace_value(VimModeSetting(enabled)); }); - search::init(cx); - crate::init(cx); settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap(); }); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8a62a28c11..0e3ce2a724 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -369,8 +369,8 @@ pub struct AppState { impl AppState { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut AppContext) -> Arc { - let settings = Settings::test(cx); - cx.set_global(settings); + cx.set_global(settings::SettingsStore::test(cx)); + cx.set_global(Settings::test(cx)); let fs = fs::FakeFs::new(cx.background().clone()); let languages = Arc::new(LanguageRegistry::test()); @@ -378,6 +378,9 @@ impl AppState { let client = Client::new(http_client.clone(), cx); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let themes = ThemeRegistry::new((), cx.font_cache().clone()); + + client::init(&client, cx); + Arc::new(Self { client, themes, diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 58c68c56d3..465219b8a6 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -158,7 +158,7 @@ fn main() { context_menu::init(cx); project::Project::init(&client); - client::init(client.clone(), cx); + client::init(&client, cx); command_palette::init(cx); editor::init(cx); go_to_line::init(cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 26d2b50e0d..e6322ad843 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1283,9 +1283,7 @@ mod tests { #[gpui::test] async fn test_pane_actions(cx: &mut TestAppContext) { - init(cx); - - let app_state = cx.update(AppState::test); + let app_state = init(cx); app_state .fs .as_fake() From 7169f5c7609a93ad282aa0d4b5f9969ea6f447cc Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 9 May 2023 08:36:43 -0700 Subject: [PATCH 018/168] Add git status to the file system abstraction co-authored-by: petros --- Cargo.lock | 1 + crates/fs/Cargo.toml | 1 + crates/fs/src/repository.rs | 82 +++++++++++++++++++++++++++++++++- crates/project/src/worktree.rs | 37 +++++---------- 4 files changed, 94 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bef04fce14..bd1dd4f33b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2350,6 +2350,7 @@ dependencies = [ "serde_derive", "serde_json", "smol", + "sum_tree", "tempfile", "util", ] diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index d080fe3cd1..54c6ce362a 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -13,6 +13,7 @@ gpui = { path = "../gpui" } lsp = { path = "../lsp" } rope = { path = "../rope" } util = { path = "../util" } +sum_tree = { path = "../sum_tree" } anyhow.workspace = true async-trait.workspace = true futures.workspace = true diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 5624ce42f1..14e7e75a3d 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -1,9 +1,10 @@ use anyhow::Result; use collections::HashMap; use parking_lot::Mutex; +use sum_tree::TreeMap; use std::{ path::{Component, Path, PathBuf}, - sync::Arc, + sync::Arc, ffi::OsStr, os::unix::prelude::OsStrExt, }; use util::ResultExt; @@ -16,6 +17,8 @@ pub trait GitRepository: Send { fn load_index_text(&self, relative_file_path: &Path) -> Option; fn branch_name(&self) -> Option; + + fn statuses(&self) -> Option>; } impl std::fmt::Debug for dyn GitRepository { @@ -61,6 +64,79 @@ impl GitRepository for LibGitRepository { let branch = String::from_utf8_lossy(head.shorthand_bytes()); Some(branch.to_string()) } + + fn statuses(&self) -> Option> { + let statuses = self.statuses(None).log_err()?; + + let mut map = TreeMap::default(); + + for status in statuses.iter() { + let path = RepoPath(PathBuf::from(OsStr::from_bytes(status.path_bytes()))); + + let status_data = status.status(); + + let status = if status_data.contains(git2::Status::CONFLICTED) { + GitStatus::Conflict + } else if status_data.intersects(git2::Status::INDEX_MODIFIED + | git2::Status::WT_MODIFIED + | git2::Status::INDEX_RENAMED + | git2::Status::WT_RENAMED) { + GitStatus::Modified + } else if status_data.intersects(git2::Status::INDEX_NEW | git2::Status::WT_NEW) { + GitStatus::Added + } else { + GitStatus::Untracked + }; + + map.insert(path, status) + } + + Some(map) + } +} + +#[derive(Debug, Clone, Default)] +pub enum GitStatus { + Added, + Modified, + Conflict, + #[default] + Untracked, +} + +#[derive(Clone, Debug, Ord, Hash, PartialOrd, Eq, PartialEq)] +pub struct RepoPath(PathBuf); + +impl From<&Path> for RepoPath { + fn from(value: &Path) -> Self { + RepoPath(value.to_path_buf()) + } +} + +impl From for RepoPath { + fn from(value: PathBuf) -> Self { + RepoPath(value) + } +} + +impl Default for RepoPath { + fn default() -> Self { + RepoPath(PathBuf::new()) + } +} + +impl AsRef for RepoPath { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} + +impl std::ops::Deref for RepoPath { + type Target = PathBuf; + + fn deref(&self) -> &Self::Target { + &self.0 + } } #[derive(Debug, Clone, Default)] @@ -93,6 +169,10 @@ impl GitRepository for FakeGitRepository { let state = self.state.lock(); state.branch_name.clone() } + + fn statuses(&self) -> Option>{ + todo!() + } } fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> { diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 554304f3d3..e236d18efd 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -6,7 +6,7 @@ use anyhow::{anyhow, Context, Result}; use client::{proto, Client}; use clock::ReplicaId; use collections::{HashMap, VecDeque}; -use fs::{repository::GitRepository, Fs, LineEnding}; +use fs::{repository::{GitRepository, RepoPath, GitStatus}, Fs, LineEnding}; use futures::{ channel::{ mpsc::{self, UnboundedSender}, @@ -117,10 +117,11 @@ pub struct Snapshot { completed_scan_id: usize, } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct RepositoryEntry { pub(crate) work_directory: WorkDirectoryEntry, pub(crate) branch: Option>, + // pub(crate) statuses: TreeMap } impl RepositoryEntry { @@ -162,6 +163,13 @@ impl Default for RepositoryWorkDirectory { } } +impl AsRef for RepositoryWorkDirectory { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} + + #[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] pub struct WorkDirectoryEntry(ProjectEntryId); @@ -178,7 +186,7 @@ impl WorkDirectoryEntry { worktree.entry_for_id(self.0).and_then(|entry| { path.strip_prefix(&entry.path) .ok() - .map(move |path| RepoPath(path.to_owned())) + .map(move |path| path.into()) }) } } @@ -197,29 +205,6 @@ impl<'a> From for WorkDirectoryEntry { } } -#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] -pub struct RepoPath(PathBuf); - -impl AsRef for RepoPath { - fn as_ref(&self) -> &Path { - self.0.as_ref() - } -} - -impl Deref for RepoPath { - type Target = PathBuf; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl AsRef for RepositoryWorkDirectory { - fn as_ref(&self) -> &Path { - self.0.as_ref() - } -} - #[derive(Debug, Clone)] pub struct LocalSnapshot { ignores_by_parent_abs_path: HashMap, (Arc, usize)>, From 67491632cbce955038e907360c35027e094e4028 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 9 May 2023 10:02:58 -0700 Subject: [PATCH 019/168] WIP: Track live entry status in repository co-authored-by: petros --- crates/fs/src/repository.rs | 152 +++++++++++++++++++------------- crates/project/src/worktree.rs | 95 +++++++++++++++----- crates/sum_tree/src/tree_map.rs | 4 +- 3 files changed, 165 insertions(+), 86 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 14e7e75a3d..626fbf9e12 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -1,11 +1,14 @@ use anyhow::Result; use collections::HashMap; +use git2::Status; use parking_lot::Mutex; -use sum_tree::TreeMap; use std::{ + ffi::OsStr, + os::unix::prelude::OsStrExt, path::{Component, Path, PathBuf}, - sync::Arc, ffi::OsStr, os::unix::prelude::OsStrExt, + sync::Arc, }; +use sum_tree::TreeMap; use util::ResultExt; pub use git2::Repository as LibGitRepository; @@ -19,6 +22,8 @@ pub trait GitRepository: Send { fn branch_name(&self) -> Option; fn statuses(&self) -> Option>; + + fn file_status(&self, path: &RepoPath) -> Option; } impl std::fmt::Debug for dyn GitRepository { @@ -70,72 +75,22 @@ impl GitRepository for LibGitRepository { let mut map = TreeMap::default(); - for status in statuses.iter() { + for status in statuses + .iter() + .filter(|status| !status.status().contains(git2::Status::IGNORED)) + { let path = RepoPath(PathBuf::from(OsStr::from_bytes(status.path_bytes()))); - let status_data = status.status(); - - let status = if status_data.contains(git2::Status::CONFLICTED) { - GitStatus::Conflict - } else if status_data.intersects(git2::Status::INDEX_MODIFIED - | git2::Status::WT_MODIFIED - | git2::Status::INDEX_RENAMED - | git2::Status::WT_RENAMED) { - GitStatus::Modified - } else if status_data.intersects(git2::Status::INDEX_NEW | git2::Status::WT_NEW) { - GitStatus::Added - } else { - GitStatus::Untracked - }; - - map.insert(path, status) + map.insert(path, status.status().into()) } Some(map) } -} -#[derive(Debug, Clone, Default)] -pub enum GitStatus { - Added, - Modified, - Conflict, - #[default] - Untracked, -} + fn file_status(&self, path: &RepoPath) -> Option { + let status = self.status_file(path).log_err()?; -#[derive(Clone, Debug, Ord, Hash, PartialOrd, Eq, PartialEq)] -pub struct RepoPath(PathBuf); - -impl From<&Path> for RepoPath { - fn from(value: &Path) -> Self { - RepoPath(value.to_path_buf()) - } -} - -impl From for RepoPath { - fn from(value: PathBuf) -> Self { - RepoPath(value) - } -} - -impl Default for RepoPath { - fn default() -> Self { - RepoPath(PathBuf::new()) - } -} - -impl AsRef for RepoPath { - fn as_ref(&self) -> &Path { - self.0.as_ref() - } -} - -impl std::ops::Deref for RepoPath { - type Target = PathBuf; - - fn deref(&self) -> &Self::Target { - &self.0 + Some(status.into()) } } @@ -170,7 +125,11 @@ impl GitRepository for FakeGitRepository { state.branch_name.clone() } - fn statuses(&self) -> Option>{ + fn statuses(&self) -> Option> { + todo!() + } + + fn file_status(&self, _: &RepoPath) -> Option { todo!() } } @@ -203,3 +162,74 @@ fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> { _ => Ok(()), } } + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub enum GitStatus { + Added, + Modified, + Conflict, + #[default] + Untracked, +} + +impl From for GitStatus { + fn from(value: Status) -> Self { + if value.contains(git2::Status::CONFLICTED) { + GitStatus::Conflict + } else if value.intersects( + git2::Status::INDEX_MODIFIED + | git2::Status::WT_MODIFIED + | git2::Status::INDEX_RENAMED + | git2::Status::WT_RENAMED, + ) { + GitStatus::Modified + } else if value.intersects(git2::Status::INDEX_NEW | git2::Status::WT_NEW) { + GitStatus::Added + } else { + GitStatus::Untracked + } + } +} + +#[derive(Clone, Debug, Ord, Hash, PartialOrd, Eq, PartialEq)] +pub struct RepoPath(PathBuf); + +impl RepoPath { + fn new(path: PathBuf) -> Self { + debug_assert!(path.is_relative(), "Repo paths must be relative"); + + RepoPath(path) + } +} + +impl From<&Path> for RepoPath { + fn from(value: &Path) -> Self { + RepoPath::new(value.to_path_buf()) + } +} + +impl From for RepoPath { + fn from(value: PathBuf) -> Self { + RepoPath::new(value) + } +} + +impl Default for RepoPath { + fn default() -> Self { + RepoPath(PathBuf::new()) + } +} + +impl AsRef for RepoPath { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} + +impl std::ops::Deref for RepoPath { + type Target = PathBuf; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index e236d18efd..e43ab9257b 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -6,7 +6,10 @@ use anyhow::{anyhow, Context, Result}; use client::{proto, Client}; use clock::ReplicaId; use collections::{HashMap, VecDeque}; -use fs::{repository::{GitRepository, RepoPath, GitStatus}, Fs, LineEnding}; +use fs::{ + repository::{GitRepository, GitStatus, RepoPath}, + Fs, LineEnding, +}; use futures::{ channel::{ mpsc::{self, UnboundedSender}, @@ -121,7 +124,7 @@ pub struct Snapshot { pub struct RepositoryEntry { pub(crate) work_directory: WorkDirectoryEntry, pub(crate) branch: Option>, - // pub(crate) statuses: TreeMap + pub(crate) statuses: TreeMap, } impl RepositoryEntry { @@ -169,7 +172,6 @@ impl AsRef for RepositoryWorkDirectory { } } - #[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] pub struct WorkDirectoryEntry(ProjectEntryId); @@ -219,6 +221,7 @@ pub struct LocalSnapshot { #[derive(Debug, Clone)] pub struct LocalRepositoryEntry { pub(crate) scan_id: usize, + pub(crate) full_scan_id: usize, pub(crate) repo_ptr: Arc>, /// Path to the actual .git folder. /// Note: if .git is a file, this points to the folder indicated by the .git file @@ -1412,6 +1415,8 @@ impl Snapshot { let repository = RepositoryEntry { work_directory: ProjectEntryId::from_proto(repository.work_directory_id).into(), branch: repository.branch.map(Into::into), + // TODO: status + statuses: Default::default(), }; if let Some(entry) = self.entry_for_id(repository.work_directory_id()) { self.repository_entries @@ -1572,6 +1577,10 @@ impl LocalSnapshot { current_candidate.map(|entry| entry.to_owned()) } + pub(crate) fn get_local_repo(&self, repo: &RepositoryEntry) -> Option<&LocalRepositoryEntry> { + self.git_repositories.get(&repo.work_directory.0) + } + pub(crate) fn repo_for_metadata( &self, path: &Path, @@ -1842,6 +1851,7 @@ impl LocalSnapshot { RepositoryEntry { work_directory: work_dir_id.into(), branch: repo_lock.branch_name().map(Into::into), + statuses: repo_lock.statuses().unwrap_or_default(), }, ); drop(repo_lock); @@ -1850,6 +1860,7 @@ impl LocalSnapshot { work_dir_id, LocalRepositoryEntry { scan_id, + full_scan_id: scan_id, repo_ptr: repo, git_dir_path: parent_path.clone(), }, @@ -2825,26 +2836,7 @@ impl BackgroundScanner { fs_entry.is_ignored = ignore_stack.is_all(); snapshot.insert_entry(fs_entry, self.fs.as_ref()); - let scan_id = snapshot.scan_id; - - let repo_with_path_in_dotgit = snapshot.repo_for_metadata(&path); - if let Some((entry_id, repo)) = repo_with_path_in_dotgit { - let work_dir = snapshot - .entry_for_id(entry_id) - .map(|entry| RepositoryWorkDirectory(entry.path.clone()))?; - - let repo = repo.lock(); - repo.reload_index(); - let branch = repo.branch_name(); - - snapshot.git_repositories.update(&entry_id, |entry| { - entry.scan_id = scan_id; - }); - - snapshot - .repository_entries - .update(&work_dir, |entry| entry.branch = branch.map(Into::into)); - } + self.reload_repo_for_path(&path, &mut snapshot); if let Some(scan_queue_tx) = &scan_queue_tx { let mut ancestor_inodes = snapshot.ancestor_inodes_for_path(&path); @@ -2872,6 +2864,63 @@ impl BackgroundScanner { Some(event_paths) } + fn reload_repo_for_path(&self, path: &Path, snapshot: &mut LocalSnapshot) -> Option<()> { + let scan_id = snapshot.scan_id; + + if path + .components() + .any(|component| component.as_os_str() == *DOT_GIT) + { + let (entry_id, repo) = snapshot.repo_for_metadata(&path)?; + + let work_dir = snapshot + .entry_for_id(entry_id) + .map(|entry| RepositoryWorkDirectory(entry.path.clone()))?; + + let repo = repo.lock(); + repo.reload_index(); + let branch = repo.branch_name(); + let statuses = repo.statuses().unwrap_or_default(); + + snapshot.git_repositories.update(&entry_id, |entry| { + entry.scan_id = scan_id; + entry.full_scan_id = scan_id; + }); + + snapshot.repository_entries.update(&work_dir, |entry| { + entry.branch = branch.map(Into::into); + entry.statuses = statuses; + }); + } else if let Some(repo) = snapshot.repo_for(&path) { + let status = { + let local_repo = snapshot.get_local_repo(&repo)?; + // Short circuit if we've already scanned everything + if local_repo.full_scan_id == scan_id { + return None; + } + + let repo_path = repo.work_directory.relativize(&snapshot, &path)?; + let git_ptr = local_repo.repo_ptr.lock(); + git_ptr.file_status(&repo_path)? + }; + + if status != GitStatus::Untracked { + let work_dir = repo.work_directory(snapshot)?; + let work_dir_id = repo.work_directory; + + snapshot + .git_repositories + .update(&work_dir_id, |entry| entry.scan_id = scan_id); + + snapshot + .repository_entries + .update(&work_dir, |entry| entry.statuses.insert(repo_path, status)); + } + } + + Some(()) + } + async fn update_ignore_statuses(&self) { use futures::FutureExt as _; diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index 1b97cbec9f..ab37d2577a 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -2,13 +2,13 @@ use std::{cmp::Ordering, fmt::Debug}; use crate::{Bias, Dimension, Item, KeyedItem, SeekTarget, SumTree, Summary}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct TreeMap(SumTree>) where K: Clone + Debug + Default + Ord, V: Clone + Debug; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct MapEntry { key: K, value: V, From bd98f7810148076a9743fe1a5313f79c2797e29a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 9 May 2023 10:04:44 -0700 Subject: [PATCH 020/168] Fix compile error --- crates/project/src/worktree.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index e43ab9257b..570ff94f4e 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2892,6 +2892,8 @@ impl BackgroundScanner { entry.statuses = statuses; }); } else if let Some(repo) = snapshot.repo_for(&path) { + let repo_path = repo.work_directory.relativize(&snapshot, &path)?; + let status = { let local_repo = snapshot.get_local_repo(&repo)?; // Short circuit if we've already scanned everything @@ -2899,7 +2901,6 @@ impl BackgroundScanner { return None; } - let repo_path = repo.work_directory.relativize(&snapshot, &path)?; let git_ptr = local_repo.repo_ptr.lock(); git_ptr.file_status(&repo_path)? }; From 93f57430dad33b14c8e77d6239c5d4391ab651f9 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 9 May 2023 10:13:22 -0700 Subject: [PATCH 021/168] Track live entry status in repository --- crates/project/src/worktree.rs | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 570ff94f4e..fb8a0ce9e7 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2853,7 +2853,9 @@ impl BackgroundScanner { } } } - Ok(None) => {} + Ok(None) => { + self.remove_repo_path(&path, &mut snapshot); + } Err(err) => { // TODO - create a special 'error' entry in the entries tree to mark this log::error!("error reading file on event {:?}", err); @@ -2864,6 +2866,31 @@ impl BackgroundScanner { Some(event_paths) } + fn remove_repo_path(&self, path: &Path, snapshot: &mut LocalSnapshot) -> Option<()> { + if !path + .components() + .any(|component| component.as_os_str() == *DOT_GIT) + { + let scan_id = snapshot.scan_id; + let repo = snapshot.repo_for(&path)?; + + let repo_path = repo.work_directory.relativize(&snapshot, &path)?; + + let work_dir = repo.work_directory(snapshot)?; + let work_dir_id = repo.work_directory; + + snapshot + .git_repositories + .update(&work_dir_id, |entry| entry.scan_id = scan_id); + + snapshot + .repository_entries + .update(&work_dir, |entry| entry.statuses.remove(&repo_path)); + } + + Some(()) + } + fn reload_repo_for_path(&self, path: &Path, snapshot: &mut LocalSnapshot) -> Option<()> { let scan_id = snapshot.scan_id; @@ -2891,11 +2918,14 @@ impl BackgroundScanner { entry.branch = branch.map(Into::into); entry.statuses = statuses; }); - } else if let Some(repo) = snapshot.repo_for(&path) { + } else { + let repo = snapshot.repo_for(&path)?; + let repo_path = repo.work_directory.relativize(&snapshot, &path)?; let status = { let local_repo = snapshot.get_local_repo(&repo)?; + // Short circuit if we've already scanned everything if local_repo.full_scan_id == scan_id { return None; From e98507d8bf088895936ed7fb85ed3302c9e6639f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 9 May 2023 14:42:51 -0700 Subject: [PATCH 022/168] Added git status to the project panel, added worktree test --- Cargo.lock | 1 + crates/project/Cargo.toml | 1 + crates/project/src/worktree.rs | 240 ++++++++++++++++++++-- crates/project_panel/src/project_panel.rs | 23 ++- 4 files changed, 246 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd1dd4f33b..0190b4d8f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4717,6 +4717,7 @@ dependencies = [ "futures 0.3.25", "fuzzy", "git", + "git2", "glob", "gpui", "ignore", diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 2b4892aab9..85a302bdd7 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -74,5 +74,6 @@ lsp = { path = "../lsp", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } +git2 = { version = "0.15", default-features = false } tempdir.workspace = true unindent.workspace = true diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index fb8a0ce9e7..cf116d188f 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -120,6 +120,25 @@ pub struct Snapshot { completed_scan_id: usize, } +impl Snapshot { + pub fn repo_for(&self, path: &Path) -> Option { + let mut max_len = 0; + let mut current_candidate = None; + for (work_directory, repo) in (&self.repository_entries).iter() { + if repo.contains(self, path) { + if work_directory.0.as_os_str().len() >= max_len { + current_candidate = Some(repo); + max_len = work_directory.0.as_os_str().len(); + } else { + break; + } + } + } + + current_candidate.map(|entry| entry.to_owned()) + } +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct RepositoryEntry { pub(crate) work_directory: WorkDirectoryEntry, @@ -145,6 +164,13 @@ impl RepositoryEntry { pub(crate) fn contains(&self, snapshot: &Snapshot, path: &Path) -> bool { self.work_directory.contains(snapshot, path) } + + pub fn status_for(&self, snapshot: &Snapshot, path: &Path) -> Option { + self.work_directory + .relativize(snapshot, path) + .and_then(|repo_path| self.statuses.get(&repo_path)) + .cloned() + } } impl From<&RepositoryEntry> for proto::RepositoryEntry { @@ -1560,23 +1586,6 @@ impl Snapshot { } impl LocalSnapshot { - pub(crate) fn repo_for(&self, path: &Path) -> Option { - let mut max_len = 0; - let mut current_candidate = None; - for (work_directory, repo) in (&self.repository_entries).iter() { - if repo.contains(self, path) { - if work_directory.0.as_os_str().len() >= max_len { - current_candidate = Some(repo); - max_len = work_directory.0.as_os_str().len(); - } else { - break; - } - } - } - - current_candidate.map(|entry| entry.to_owned()) - } - pub(crate) fn get_local_repo(&self, repo: &RepositoryEntry) -> Option<&LocalRepositoryEntry> { self.git_repositories.get(&repo.work_directory.0) } @@ -3751,6 +3760,203 @@ mod tests { }); } + #[gpui::test] + async fn test_git_status(cx: &mut TestAppContext) { + #[track_caller] + fn git_init(path: &Path) -> git2::Repository { + git2::Repository::init(path).expect("Failed to initialize git repository") + } + + #[track_caller] + fn git_add(path: &Path, repo: &git2::Repository) { + let mut index = repo.index().expect("Failed to get index"); + index.add_path(path).expect("Failed to add a.txt"); + index.write().expect("Failed to write index"); + } + + #[track_caller] + fn git_remove_index(path: &Path, repo: &git2::Repository) { + let mut index = repo.index().expect("Failed to get index"); + index.remove_path(path).expect("Failed to add a.txt"); + index.write().expect("Failed to write index"); + } + + #[track_caller] + fn git_commit(msg: &'static str, repo: &git2::Repository) { + let signature = repo.signature().unwrap(); + let oid = repo.index().unwrap().write_tree().unwrap(); + let tree = repo.find_tree(oid).unwrap(); + if let Some(head) = repo.head().ok() { + let parent_obj = head + .peel(git2::ObjectType::Commit) + .unwrap(); + + let parent_commit = parent_obj + .as_commit() + .unwrap(); + + + repo.commit( + Some("HEAD"), + &signature, + &signature, + msg, + &tree, + &[parent_commit], + ) + .expect("Failed to commit with parent"); + } else { + repo.commit( + Some("HEAD"), + &signature, + &signature, + msg, + &tree, + &[], + ) + .expect("Failed to commit"); + } + } + + #[track_caller] + fn git_stash(repo: &mut git2::Repository) { + let signature = repo.signature().unwrap(); + repo.stash_save(&signature, "N/A", None) + .expect("Failed to stash"); + } + + #[track_caller] + fn git_reset(offset: usize, repo: &git2::Repository) { + let head = repo.head().expect("Couldn't get repo head"); + let object = head.peel(git2::ObjectType::Commit).unwrap(); + let commit = object.as_commit().unwrap(); + let new_head = commit + .parents() + .inspect(|parnet| { + parnet.message(); + }) + .skip(offset) + .next() + .expect("Not enough history"); + repo.reset(&new_head.as_object(), git2::ResetType::Soft, None) + .expect("Could not reset"); + } + + #[track_caller] + fn git_status(repo: &git2::Repository) -> HashMap { + repo.statuses(None) + .unwrap() + .iter() + .map(|status| { + (status.path().unwrap().to_string(), status.status()) + }) + .collect() + } + + let root = temp_tree(json!({ + "project": { + "a.txt": "a", + "b.txt": "bb", + }, + + })); + + let http_client = FakeHttpClient::with_404_response(); + let client = cx.read(|cx| Client::new(http_client, cx)); + let tree = Worktree::local( + client, + root.path(), + true, + Arc::new(RealFs), + Default::default(), + &mut cx.to_async(), + ) + .await + .unwrap(); + + const A_TXT: &'static str = "a.txt"; + const B_TXT: &'static str = "b.txt"; + let work_dir = root.path().join("project"); + + let mut repo = git_init(work_dir.as_path()); + git_add(Path::new(A_TXT), &repo); + git_commit("Initial commit", &repo); + + std::fs::write(work_dir.join(A_TXT), "aa").unwrap(); + + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + tree.flush_fs_events(cx).await; + + // Check that the right git state is observed on startup + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + assert_eq!(snapshot.repository_entries.iter().count(), 1); + let (dir, repo) = snapshot.repository_entries.iter().next().unwrap(); + assert_eq!(dir.0.as_ref(), Path::new("project")); + + assert_eq!(repo.statuses.iter().count(), 2); + assert_eq!( + repo.statuses.get(&Path::new(A_TXT).into()), + Some(&GitStatus::Modified) + ); + assert_eq!( + repo.statuses.get(&Path::new(B_TXT).into()), + Some(&GitStatus::Added) + ); + }); + + git_add(Path::new(A_TXT), &repo); + git_add(Path::new(B_TXT), &repo); + git_commit("Committing modified and added", &repo); + tree.flush_fs_events(cx).await; + + // Check that repo only changes are tracked + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + + assert_eq!(repo.statuses.iter().count(), 0); + assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); + assert_eq!(repo.statuses.get(&Path::new(B_TXT).into()), None); + }); + + git_reset(0, &repo); + git_remove_index(Path::new(B_TXT), &repo); + git_stash(&mut repo); + tree.flush_fs_events(cx).await; + + // Check that more complex repo changes are tracked + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + + + dbg!(&repo.statuses); + + + assert_eq!(repo.statuses.iter().count(), 1); + assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); + assert_eq!( + repo.statuses.get(&Path::new(B_TXT).into()), + Some(&GitStatus::Added) + ); + }); + + std::fs::remove_file(work_dir.join(B_TXT)).unwrap(); + tree.flush_fs_events(cx).await; + + // Check that non-repo behavior is tracked + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + + assert_eq!(repo.statuses.iter().count(), 0); + assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); + assert_eq!(repo.statuses.get(&Path::new(B_TXT).into()), None); + }); + } + #[gpui::test] async fn test_write_file(cx: &mut TestAppContext) { let dir = temp_tree(json!({ diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 7602ff7db8..845ab333e1 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -13,10 +13,10 @@ use gpui::{ keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, - ViewHandle, WeakViewHandle, + ViewHandle, WeakViewHandle, color::Color, }; use menu::{Confirm, SelectNext, SelectPrev}; -use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; +use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId, repository::GitStatus}; use settings::Settings; use std::{ cmp::Ordering, @@ -86,6 +86,7 @@ pub struct EntryDetails { is_editing: bool, is_processing: bool, is_cut: bool, + git_status: Option } actions!( @@ -1008,6 +1009,13 @@ impl ProjectPanel { let entry_range = range.start.saturating_sub(ix)..end_ix - ix; for entry in &visible_worktree_entries[entry_range] { + let path = &entry.path; + let status = snapshot.repo_for(path) + .and_then(|entry| { + entry.status_for(&snapshot, path) + }); + + let mut details = EntryDetails { filename: entry .path @@ -1028,6 +1036,7 @@ impl ProjectPanel { is_cut: self .clipboard_entry .map_or(false, |e| e.is_cut() && e.entry_id() == entry.id), + git_status: status }; if let Some(edit_state) = &self.edit_state { @@ -1069,6 +1078,15 @@ impl ProjectPanel { let kind = details.kind; let show_editor = details.is_editing && !details.is_processing; + let git_color = details.git_status.as_ref().and_then(|status| { + match status { + GitStatus::Added => Some(Color::green()), + GitStatus::Modified => Some(Color::blue()), + GitStatus::Conflict => Some(Color::red()), + GitStatus::Untracked => None, + } + }).unwrap_or(Color::transparent_black()); + Flex::row() .with_child( if kind == EntryKind::Dir { @@ -1107,6 +1125,7 @@ impl ProjectPanel { .with_height(style.height) .contained() .with_style(row_container_style) + .with_background_color(git_color) .with_padding_left(padding) .into_any_named("project panel entry visual element") } From 18cec8d64f82aa39fe8c7df059a6e030010c55b7 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 9 May 2023 14:49:35 -0700 Subject: [PATCH 023/168] Format --- crates/project/src/worktree.rs | 27 +++++--------------- crates/project_panel/src/project_panel.rs | 30 +++++++++++++---------- 2 files changed, 23 insertions(+), 34 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index cf116d188f..66cef5131b 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3787,14 +3787,9 @@ mod tests { let oid = repo.index().unwrap().write_tree().unwrap(); let tree = repo.find_tree(oid).unwrap(); if let Some(head) = repo.head().ok() { - let parent_obj = head - .peel(git2::ObjectType::Commit) - .unwrap(); - - let parent_commit = parent_obj - .as_commit() - .unwrap(); + let parent_obj = head.peel(git2::ObjectType::Commit).unwrap(); + let parent_commit = parent_obj.as_commit().unwrap(); repo.commit( Some("HEAD"), @@ -3806,15 +3801,8 @@ mod tests { ) .expect("Failed to commit with parent"); } else { - repo.commit( - Some("HEAD"), - &signature, - &signature, - msg, - &tree, - &[], - ) - .expect("Failed to commit"); + repo.commit(Some("HEAD"), &signature, &signature, msg, &tree, &[]) + .expect("Failed to commit"); } } @@ -3842,14 +3830,13 @@ mod tests { .expect("Could not reset"); } + #[allow(dead_code)] #[track_caller] fn git_status(repo: &git2::Repository) -> HashMap { repo.statuses(None) .unwrap() .iter() - .map(|status| { - (status.path().unwrap().to_string(), status.status()) - }) + .map(|status| (status.path().unwrap().to_string(), status.status())) .collect() } @@ -3931,10 +3918,8 @@ mod tests { let snapshot = tree.snapshot(); let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - dbg!(&repo.statuses); - assert_eq!(repo.statuses.iter().count(), 1); assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); assert_eq!( diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 845ab333e1..971c4207ba 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -5,6 +5,7 @@ use futures::stream::StreamExt; use gpui::{ actions, anyhow::{anyhow, Result}, + color::Color, elements::{ AnchorCorner, ChildView, ContainerStyle, Empty, Flex, Label, MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, @@ -13,10 +14,13 @@ use gpui::{ keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, - ViewHandle, WeakViewHandle, color::Color, + ViewHandle, WeakViewHandle, }; use menu::{Confirm, SelectNext, SelectPrev}; -use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId, repository::GitStatus}; +use project::{ + repository::GitStatus, Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, + WorktreeId, +}; use settings::Settings; use std::{ cmp::Ordering, @@ -86,7 +90,7 @@ pub struct EntryDetails { is_editing: bool, is_processing: bool, is_cut: bool, - git_status: Option + git_status: Option, } actions!( @@ -1010,11 +1014,9 @@ impl ProjectPanel { let entry_range = range.start.saturating_sub(ix)..end_ix - ix; for entry in &visible_worktree_entries[entry_range] { let path = &entry.path; - let status = snapshot.repo_for(path) - .and_then(|entry| { - entry.status_for(&snapshot, path) - }); - + let status = snapshot + .repo_for(path) + .and_then(|entry| entry.status_for(&snapshot, path)); let mut details = EntryDetails { filename: entry @@ -1036,7 +1038,7 @@ impl ProjectPanel { is_cut: self .clipboard_entry .map_or(false, |e| e.is_cut() && e.entry_id() == entry.id), - git_status: status + git_status: status, }; if let Some(edit_state) = &self.edit_state { @@ -1078,14 +1080,16 @@ impl ProjectPanel { let kind = details.kind; let show_editor = details.is_editing && !details.is_processing; - let git_color = details.git_status.as_ref().and_then(|status| { - match status { + let git_color = details + .git_status + .as_ref() + .and_then(|status| match status { GitStatus::Added => Some(Color::green()), GitStatus::Modified => Some(Color::blue()), GitStatus::Conflict => Some(Color::red()), GitStatus::Untracked => None, - } - }).unwrap_or(Color::transparent_black()); + }) + .unwrap_or(Color::transparent_black()); Flex::row() .with_child( From a58a33fc93dd64b607112c1cae8af26903b5a510 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 9 May 2023 19:29:45 -0700 Subject: [PATCH 024/168] WIP: integrate status with collab --- crates/collab/src/tests/integration_tests.rs | 113 ++++++++++++++++++- crates/fs/src/fs.rs | 15 ++- crates/fs/src/repository.rs | 13 ++- crates/project/src/worktree.rs | 4 + crates/rpc/proto/zed.proto | 14 +++ 5 files changed, 154 insertions(+), 5 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index e3b5b0be7e..764f070f0b 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -10,7 +10,7 @@ use editor::{ ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, ToggleCodeActions, Undo, }; -use fs::{FakeFs, Fs as _, LineEnding, RemoveOptions}; +use fs::{repository::GitStatus, FakeFs, Fs as _, LineEnding, RemoveOptions}; use futures::StreamExt as _; use gpui::{ executor::Deterministic, geometry::vector::vec2f, test::EmptyView, AppContext, ModelHandle, @@ -2690,6 +2690,117 @@ async fn test_git_branch_name( }); } +#[gpui::test] +async fn test_git_status_sync( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, + cx_c: &mut TestAppContext, +) { + deterministic.forbid_parking(); + let mut server = TestServer::start(&deterministic).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + let client_c = server.create_client(cx_c, "user_c").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + + client_a + .fs + .insert_tree( + "/dir", + json!({ + ".git": {}, + "a.txt": "a", + "b.txt": "b", + }), + ) + .await; + + const A_TXT: &'static str = "a.txt"; + const B_TXT: &'static str = "b.txt"; + + client_a + .fs + .as_fake() + .set_status_for_repo( + Path::new("/dir/.git"), + &[ + (&Path::new(A_TXT), GitStatus::Added), + (&Path::new(B_TXT), GitStatus::Added), + ], + ) + .await; + + let (project_local, _worktree_id) = client_a.build_local_project("/dir", cx_a).await; + let project_id = active_call_a + .update(cx_a, |call, cx| { + call.share_project(project_local.clone(), cx) + }) + .await + .unwrap(); + + let project_remote = client_b.build_remote_project(project_id, cx_b).await; + + // Wait for it to catch up to the new status + deterministic.run_until_parked(); + + #[track_caller] + fn assert_status(file: &impl AsRef, status: Option, project: &Project, cx: &AppContext) { + let file = file.as_ref(); + let worktrees = project.visible_worktrees(cx).collect::>(); + assert_eq!(worktrees.len(), 1); + let worktree = worktrees[0].clone(); + let snapshot = worktree.read(cx).snapshot(); + let root_entry = snapshot.root_git_entry().unwrap(); + assert_eq!(root_entry.status_for(&snapshot, file), status); + } + + // Smoke test status reading + project_local.read_with(cx_a, |project, cx| { + assert_status(&Path::new(A_TXT), Some(GitStatus::Added), project, cx); + assert_status(&Path::new(B_TXT), Some(GitStatus::Added), project, cx); + }); + project_remote.read_with(cx_b, |project, cx| { + assert_status(&Path::new(A_TXT), Some(GitStatus::Added), project, cx); + assert_status(&Path::new(B_TXT), Some(GitStatus::Added), project, cx); + }); + + client_a + .fs + .as_fake() + .set_status_for_repo( + Path::new("/dir/.git"), + &[ + (&Path::new(A_TXT), GitStatus::Modified), + (&Path::new(B_TXT), GitStatus::Modified), + ], + ) + .await; + + // Wait for buffer_local_a to receive it + deterministic.run_until_parked(); + + // Smoke test status reading + project_local.read_with(cx_a, |project, cx| { + assert_status(&Path::new(A_TXT), Some(GitStatus::Added), project, cx); + assert_status(&Path::new(B_TXT), Some(GitStatus::Added), project, cx); + }); + project_remote.read_with(cx_b, |project, cx| { + assert_status(&Path::new(A_TXT), Some(GitStatus::Added), project, cx); + assert_status(&Path::new(B_TXT), Some(GitStatus::Added), project, cx); + }); + + // And synchronization while joining + let project_remote_c = client_c.build_remote_project(project_id, cx_c).await; + project_remote_c.read_with(cx_c, |project, cx| { + assert_status(&Path::new(A_TXT), Some(GitStatus::Added), project, cx); + assert_status(&Path::new(B_TXT), Some(GitStatus::Added), project, cx); + }); +} + #[gpui::test(iterations = 10)] async fn test_fs_operations( deterministic: Arc, diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 945ffaea16..efc24553c4 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -7,7 +7,7 @@ use git2::Repository as LibGitRepository; use lazy_static::lazy_static; use parking_lot::Mutex; use regex::Regex; -use repository::GitRepository; +use repository::{GitRepository, GitStatus}; use rope::Rope; use smol::io::{AsyncReadExt, AsyncWriteExt}; use std::borrow::Cow; @@ -654,6 +654,19 @@ impl FakeFs { }); } + pub async fn set_status_for_repo(&self, dot_git: &Path, statuses: &[(&Path, GitStatus)]) { + self.with_git_state(dot_git, |state| { + state.git_statuses.clear(); + state.git_statuses.extend( + statuses + .iter() + .map(|(path, content)| { + ((**path).into(), content.clone()) + }), + ); + }); + } + pub fn paths(&self) -> Vec { let mut result = Vec::new(); let mut queue = collections::VecDeque::new(); diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 626fbf9e12..7fa20bddcb 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -102,6 +102,7 @@ pub struct FakeGitRepository { #[derive(Debug, Clone, Default)] pub struct FakeGitRepositoryState { pub index_contents: HashMap, + pub git_statuses: HashMap, pub branch_name: Option, } @@ -126,11 +127,17 @@ impl GitRepository for FakeGitRepository { } fn statuses(&self) -> Option> { - todo!() + let state = self.state.lock(); + let mut map = TreeMap::default(); + for (repo_path, status) in state.git_statuses.iter() { + map.insert(repo_path.to_owned(), status.to_owned()); + } + Some(map) } - fn file_status(&self, _: &RepoPath) -> Option { - todo!() + fn file_status(&self, path: &RepoPath) -> Option { + let state = self.state.lock(); + state.git_statuses.get(path).cloned() } } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 66cef5131b..82c719f31e 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -178,6 +178,9 @@ impl From<&RepositoryEntry> for proto::RepositoryEntry { proto::RepositoryEntry { work_directory_id: value.work_directory.to_proto(), branch: value.branch.as_ref().map(|str| str.to_string()), + // TODO: Status + removed_statuses: Default::default(), + updated_statuses: Default::default(), } } } @@ -1855,6 +1858,7 @@ impl LocalSnapshot { let scan_id = self.scan_id; let repo_lock = repo.lock(); + self.repository_entries.insert( work_directory, RepositoryEntry { diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 220ef22fb7..abe02f42bb 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -986,8 +986,22 @@ message Entry { message RepositoryEntry { uint64 work_directory_id = 1; optional string branch = 2; + repeated uint64 removed_statuses = 3; + repeated StatusEntry updated_statuses = 4; } +message StatusEntry { + uint64 entry_id = 1; + GitStatus status = 2; +} + +enum GitStatus { + Added = 0; + Modified = 1; + Conflict = 2; +} + + message BufferState { uint64 id = 1; optional File file = 2; From 94a0de4c9fe5417882075aed00f6d337f3bee86d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 9 May 2023 19:35:15 -0700 Subject: [PATCH 025/168] Fix compile errors --- crates/collab/src/db.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index bc5b816abf..5867fa7369 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -1568,6 +1568,9 @@ impl Database { worktree.updated_repositories.push(proto::RepositoryEntry { work_directory_id: db_repository.work_directory_id as u64, branch: db_repository.branch, + removed_statuses: Default::default(), + updated_statuses: Default::default(), + }); } } @@ -2648,6 +2651,9 @@ impl Database { worktree.repository_entries.push(proto::RepositoryEntry { work_directory_id: db_repository_entry.work_directory_id as u64, branch: db_repository_entry.branch, + removed_statuses: Default::default(), + updated_statuses: Default::default(), + }); } } From f935047ff27bcc9ca2acfb4855df5b5d59b951d7 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 9 May 2023 21:06:23 -0700 Subject: [PATCH 026/168] Convert git status calculation to use Entry IDs as the key instead of repo relative paths --- Cargo.lock | 1 - crates/collab/src/tests/integration_tests.rs | 3 +- crates/fs/Cargo.toml | 1 - crates/fs/src/fs.rs | 4 +- crates/fs/src/repository.rs | 15 ++- crates/project/src/worktree.rs | 120 ++++++++++++------- crates/project_panel/src/project_panel.rs | 5 +- 7 files changed, 90 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0190b4d8f5..8fe740267e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2350,7 +2350,6 @@ dependencies = [ "serde_derive", "serde_json", "smol", - "sum_tree", "tempfile", "util", ] diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 764f070f0b..b6046870d1 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2755,7 +2755,8 @@ async fn test_git_status_sync( let worktree = worktrees[0].clone(); let snapshot = worktree.read(cx).snapshot(); let root_entry = snapshot.root_git_entry().unwrap(); - assert_eq!(root_entry.status_for(&snapshot, file), status); + let file_entry_id = snapshot.entry_for_path(file).unwrap().id; + assert_eq!(root_entry.status_for(file_entry_id), status); } // Smoke test status reading diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 54c6ce362a..d080fe3cd1 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -13,7 +13,6 @@ gpui = { path = "../gpui" } lsp = { path = "../lsp" } rope = { path = "../rope" } util = { path = "../util" } -sum_tree = { path = "../sum_tree" } anyhow.workspace = true async-trait.workspace = true futures.workspace = true diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index efc24553c4..9347e7d7b1 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -7,7 +7,7 @@ use git2::Repository as LibGitRepository; use lazy_static::lazy_static; use parking_lot::Mutex; use regex::Regex; -use repository::{GitRepository, GitStatus}; +use repository::GitRepository; use rope::Rope; use smol::io::{AsyncReadExt, AsyncWriteExt}; use std::borrow::Cow; @@ -27,7 +27,7 @@ use util::ResultExt; #[cfg(any(test, feature = "test-support"))] use collections::{btree_map, BTreeMap}; #[cfg(any(test, feature = "test-support"))] -use repository::FakeGitRepositoryState; +use repository::{FakeGitRepositoryState, GitStatus}; #[cfg(any(test, feature = "test-support"))] use std::sync::Weak; diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 7fa20bddcb..73847dac29 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -8,7 +8,6 @@ use std::{ path::{Component, Path, PathBuf}, sync::Arc, }; -use sum_tree::TreeMap; use util::ResultExt; pub use git2::Repository as LibGitRepository; @@ -21,7 +20,7 @@ pub trait GitRepository: Send { fn branch_name(&self) -> Option; - fn statuses(&self) -> Option>; + fn statuses(&self) -> Option>; fn file_status(&self, path: &RepoPath) -> Option; } @@ -70,10 +69,10 @@ impl GitRepository for LibGitRepository { Some(branch.to_string()) } - fn statuses(&self) -> Option> { + fn statuses(&self) -> Option> { let statuses = self.statuses(None).log_err()?; - let mut map = TreeMap::default(); + let mut result = HashMap::default(); for status in statuses .iter() @@ -81,10 +80,10 @@ impl GitRepository for LibGitRepository { { let path = RepoPath(PathBuf::from(OsStr::from_bytes(status.path_bytes()))); - map.insert(path, status.status().into()) + result.insert(path, status.status().into()); } - Some(map) + Some(result) } fn file_status(&self, path: &RepoPath) -> Option { @@ -126,9 +125,9 @@ impl GitRepository for FakeGitRepository { state.branch_name.clone() } - fn statuses(&self) -> Option> { + fn statuses(&self) -> Option> { let state = self.state.lock(); - let mut map = TreeMap::default(); + let mut map = HashMap::default(); for (repo_path, status) in state.git_statuses.iter() { map.insert(repo_path.to_owned(), status.to_owned()); } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 82c719f31e..b9a4b549a1 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -143,7 +143,7 @@ impl Snapshot { pub struct RepositoryEntry { pub(crate) work_directory: WorkDirectoryEntry, pub(crate) branch: Option>, - pub(crate) statuses: TreeMap, + pub(crate) statuses: TreeMap, } impl RepositoryEntry { @@ -165,11 +165,8 @@ impl RepositoryEntry { self.work_directory.contains(snapshot, path) } - pub fn status_for(&self, snapshot: &Snapshot, path: &Path) -> Option { - self.work_directory - .relativize(snapshot, path) - .and_then(|repo_path| self.statuses.get(&repo_path)) - .cloned() + pub fn status_for(&self, entry: ProjectEntryId) -> Option { + self.statuses.get(&entry).cloned() } } @@ -1813,10 +1810,6 @@ impl LocalSnapshot { ); } - if parent_path.file_name() == Some(&DOT_GIT) { - self.build_repo(parent_path, fs); - } - let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)]; let mut entries_by_id_edits = Vec::new(); @@ -1833,6 +1826,10 @@ impl LocalSnapshot { self.entries_by_path.edit(entries_by_path_edits, &()); self.entries_by_id.edit(entries_by_id_edits, &()); + + if parent_path.file_name() == Some(&DOT_GIT) { + self.build_repo(parent_path, fs); + } } fn build_repo(&mut self, parent_path: Arc, fs: &dyn Fs) -> Option<()> { @@ -1858,13 +1855,13 @@ impl LocalSnapshot { let scan_id = self.scan_id; let repo_lock = repo.lock(); - + let statuses = convert_statuses(&work_directory, repo_lock.deref(), self)?; self.repository_entries.insert( work_directory, RepositoryEntry { work_directory: work_dir_id.into(), branch: repo_lock.branch_name().map(Into::into), - statuses: repo_lock.statuses().unwrap_or_default(), + statuses, }, ); drop(repo_lock); @@ -2821,6 +2818,7 @@ impl BackgroundScanner { for (abs_path, metadata) in abs_paths.iter().zip(metadata.iter()) { if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) { if matches!(metadata, Ok(None)) || doing_recursive_update { + self.remove_repo_path(&path, &mut snapshot); snapshot.remove_path(path); } event_paths.push(path.into()); @@ -2866,9 +2864,7 @@ impl BackgroundScanner { } } } - Ok(None) => { - self.remove_repo_path(&path, &mut snapshot); - } + Ok(None) => {} Err(err) => { // TODO - create a special 'error' entry in the entries tree to mark this log::error!("error reading file on event {:?}", err); @@ -2887,7 +2883,7 @@ impl BackgroundScanner { let scan_id = snapshot.scan_id; let repo = snapshot.repo_for(&path)?; - let repo_path = repo.work_directory.relativize(&snapshot, &path)?; + let repo_path_id = snapshot.entry_for_path(path)?.id; let work_dir = repo.work_directory(snapshot)?; let work_dir_id = repo.work_directory; @@ -2898,7 +2894,7 @@ impl BackgroundScanner { snapshot .repository_entries - .update(&work_dir, |entry| entry.statuses.remove(&repo_path)); + .update(&work_dir, |entry| entry.statuses.remove(&repo_path_id)); } Some(()) @@ -2911,18 +2907,19 @@ impl BackgroundScanner { .components() .any(|component| component.as_os_str() == *DOT_GIT) { - let (entry_id, repo) = snapshot.repo_for_metadata(&path)?; + let (git_dir_id, repo) = snapshot.repo_for_metadata(&path)?; let work_dir = snapshot - .entry_for_id(entry_id) + .entry_for_id(git_dir_id) .map(|entry| RepositoryWorkDirectory(entry.path.clone()))?; let repo = repo.lock(); repo.reload_index(); let branch = repo.branch_name(); - let statuses = repo.statuses().unwrap_or_default(); - snapshot.git_repositories.update(&entry_id, |entry| { + let statuses = convert_statuses(&work_dir, repo.deref(), snapshot)?; + + snapshot.git_repositories.update(&git_dir_id, |entry| { entry.scan_id = scan_id; entry.full_scan_id = scan_id; }); @@ -2936,6 +2933,8 @@ impl BackgroundScanner { let repo_path = repo.work_directory.relativize(&snapshot, &path)?; + let path_id = snapshot.entry_for_path(&path)?.id; + let status = { let local_repo = snapshot.get_local_repo(&repo)?; @@ -2958,7 +2957,7 @@ impl BackgroundScanner { snapshot .repository_entries - .update(&work_dir, |entry| entry.statuses.insert(repo_path, status)); + .update(&work_dir, |entry| entry.statuses.insert(path_id, status)); } } @@ -3167,6 +3166,19 @@ impl BackgroundScanner { } } +fn convert_statuses( + work_dir: &RepositoryWorkDirectory, + repo: &dyn GitRepository, + snapshot: &Snapshot, +) -> Option> { + let mut statuses = TreeMap::default(); + for (path, status) in repo.statuses().unwrap_or_default() { + let path_entry = snapshot.entry_for_path(&work_dir.0.join(path.as_path()))?; + statuses.insert(path_entry.id, status) + } + Some(statuses) +} + fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag { let mut result = root_char_bag; result.extend( @@ -3848,6 +3860,11 @@ mod tests { "project": { "a.txt": "a", "b.txt": "bb", + "c": { + "d": { + "e.txt": "eee" + } + } }, })); @@ -3865,18 +3882,39 @@ mod tests { .await .unwrap(); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + const A_TXT: &'static str = "a.txt"; const B_TXT: &'static str = "b.txt"; + const E_TXT: &'static str = "c/d/e.txt"; let work_dir = root.path().join("project"); + let tree_clone = tree.clone(); + let (a_txt_id, b_txt_id, e_txt_id) = cx.read(|cx| { + let tree = tree_clone.read(cx); + let a_id = tree + .entry_for_path(Path::new("project").join(Path::new(A_TXT))) + .unwrap() + .id; + let b_id = tree + .entry_for_path(Path::new("project").join(Path::new(B_TXT))) + .unwrap() + .id; + let e_id = tree + .entry_for_path(Path::new("project").join(Path::new(E_TXT))) + .unwrap() + .id; + (a_id, b_id, e_id) + }); + let mut repo = git_init(work_dir.as_path()); git_add(Path::new(A_TXT), &repo); + git_add(Path::new(E_TXT), &repo); git_commit("Initial commit", &repo); std::fs::write(work_dir.join(A_TXT), "aa").unwrap(); - cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) - .await; tree.flush_fs_events(cx).await; // Check that the right git state is observed on startup @@ -3885,16 +3923,9 @@ mod tests { assert_eq!(snapshot.repository_entries.iter().count(), 1); let (dir, repo) = snapshot.repository_entries.iter().next().unwrap(); assert_eq!(dir.0.as_ref(), Path::new("project")); - assert_eq!(repo.statuses.iter().count(), 2); - assert_eq!( - repo.statuses.get(&Path::new(A_TXT).into()), - Some(&GitStatus::Modified) - ); - assert_eq!( - repo.statuses.get(&Path::new(B_TXT).into()), - Some(&GitStatus::Added) - ); + assert_eq!(repo.statuses.get(&a_txt_id), Some(&GitStatus::Modified)); + assert_eq!(repo.statuses.get(&b_txt_id), Some(&GitStatus::Added)); }); git_add(Path::new(A_TXT), &repo); @@ -3908,15 +3939,18 @@ mod tests { let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); assert_eq!(repo.statuses.iter().count(), 0); - assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); - assert_eq!(repo.statuses.get(&Path::new(B_TXT).into()), None); + assert_eq!(repo.statuses.get(&a_txt_id), None); + assert_eq!(repo.statuses.get(&b_txt_id), None); }); git_reset(0, &repo); git_remove_index(Path::new(B_TXT), &repo); git_stash(&mut repo); + std::fs::write(work_dir.join(E_TXT), "eeee").unwrap(); tree.flush_fs_events(cx).await; + dbg!(git_status(&repo)); + // Check that more complex repo changes are tracked tree.read_with(cx, |tree, _cx| { let snapshot = tree.snapshot(); @@ -3924,15 +3958,14 @@ mod tests { dbg!(&repo.statuses); - assert_eq!(repo.statuses.iter().count(), 1); - assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); - assert_eq!( - repo.statuses.get(&Path::new(B_TXT).into()), - Some(&GitStatus::Added) - ); + assert_eq!(repo.statuses.iter().count(), 2); + assert_eq!(repo.statuses.get(&a_txt_id), None); + assert_eq!(repo.statuses.get(&b_txt_id), Some(&GitStatus::Added)); + assert_eq!(repo.statuses.get(&e_txt_id), Some(&GitStatus::Modified)); }); std::fs::remove_file(work_dir.join(B_TXT)).unwrap(); + std::fs::remove_dir_all(work_dir.join("c")).unwrap(); tree.flush_fs_events(cx).await; // Check that non-repo behavior is tracked @@ -3941,8 +3974,9 @@ mod tests { let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); assert_eq!(repo.statuses.iter().count(), 0); - assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); - assert_eq!(repo.statuses.get(&Path::new(B_TXT).into()), None); + assert_eq!(repo.statuses.get(&a_txt_id), None); + assert_eq!(repo.statuses.get(&b_txt_id), None); + assert_eq!(repo.statuses.get(&e_txt_id), None); }); } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 971c4207ba..de440b38a4 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1013,10 +1013,9 @@ impl ProjectPanel { let entry_range = range.start.saturating_sub(ix)..end_ix - ix; for entry in &visible_worktree_entries[entry_range] { - let path = &entry.path; let status = snapshot - .repo_for(path) - .and_then(|entry| entry.status_for(&snapshot, path)); + .repo_for(&entry.path) + .and_then(|repo_entry| repo_entry.status_for(entry.id)); let mut details = EntryDetails { filename: entry From 6b4242cdedbe3c571f9704a28ed103b6d51a211f Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 10 May 2023 16:21:24 +0300 Subject: [PATCH 027/168] Use theme.editor.diff for the colors --- crates/project_panel/src/project_panel.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index de440b38a4..2baccc0913 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -5,7 +5,6 @@ use futures::stream::StreamExt; use gpui::{ actions, anyhow::{anyhow, Result}, - color::Color, elements::{ AnchorCorner, ChildView, ContainerStyle, Empty, Flex, Label, MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, @@ -1079,16 +1078,23 @@ impl ProjectPanel { let kind = details.kind; let show_editor = details.is_editing && !details.is_processing; - let git_color = details + // Prepare colors for git statuses + let editor_theme = &cx.global::().theme.editor; + let color_for_added = Some(editor_theme.diff.inserted); + let color_for_modified = Some(editor_theme.diff.modified); + let color_for_conflict = Some(editor_theme.diff.deleted); + let color_for_untracked = None; + let mut filename_text_style = style.text.clone(); + filename_text_style.color = details .git_status .as_ref() .and_then(|status| match status { - GitStatus::Added => Some(Color::green()), - GitStatus::Modified => Some(Color::blue()), - GitStatus::Conflict => Some(Color::red()), - GitStatus::Untracked => None, + GitStatus::Added => color_for_added, + GitStatus::Modified => color_for_modified, + GitStatus::Conflict => color_for_conflict, + GitStatus::Untracked => color_for_untracked, }) - .unwrap_or(Color::transparent_black()); + .unwrap_or(style.text.color); Flex::row() .with_child( @@ -1117,7 +1123,7 @@ impl ProjectPanel { .flex(1.0, true) .into_any() } else { - Label::new(details.filename.clone(), style.text.clone()) + Label::new(details.filename.clone(), filename_text_style) .contained() .with_margin_left(style.icon_spacing) .aligned() @@ -1128,7 +1134,6 @@ impl ProjectPanel { .with_height(style.height) .contained() .with_style(row_container_style) - .with_background_color(git_color) .with_padding_left(padding) .into_any_named("project panel entry visual element") } From 21e1bdc8cd9bc880e01916337ee60a5a034bd30c Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 10 May 2023 17:05:53 +0300 Subject: [PATCH 028/168] Fix yellow to be yellow --- crates/gpui/src/color.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index cc725776b9..b6c1e3aff9 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -42,7 +42,7 @@ impl Color { } pub fn yellow() -> Self { - Self(ColorU::from_u32(0x00ffffff)) + Self(ColorU::from_u32(0xffff00ff)) } pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { From 0082d68d4af653a9dfd9c1dbeb1bb70373a2de51 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 10 May 2023 08:49:30 -0700 Subject: [PATCH 029/168] Revert "Convert git status calculation to use Entry IDs as the key instead of repo relative paths" This reverts commit 728c6892c924ebeabb086e308ec4b5f56c4fd72a. --- Cargo.lock | 1 + crates/collab/src/tests/integration_tests.rs | 3 +- crates/fs/Cargo.toml | 1 + crates/fs/src/fs.rs | 4 +- crates/fs/src/repository.rs | 15 +-- crates/project/src/worktree.rs | 120 +++++++------------ crates/project_panel/src/project_panel.rs | 5 +- 7 files changed, 59 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8fe740267e..0190b4d8f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2350,6 +2350,7 @@ dependencies = [ "serde_derive", "serde_json", "smol", + "sum_tree", "tempfile", "util", ] diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index b6046870d1..764f070f0b 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2755,8 +2755,7 @@ async fn test_git_status_sync( let worktree = worktrees[0].clone(); let snapshot = worktree.read(cx).snapshot(); let root_entry = snapshot.root_git_entry().unwrap(); - let file_entry_id = snapshot.entry_for_path(file).unwrap().id; - assert_eq!(root_entry.status_for(file_entry_id), status); + assert_eq!(root_entry.status_for(&snapshot, file), status); } // Smoke test status reading diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index d080fe3cd1..54c6ce362a 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -13,6 +13,7 @@ gpui = { path = "../gpui" } lsp = { path = "../lsp" } rope = { path = "../rope" } util = { path = "../util" } +sum_tree = { path = "../sum_tree" } anyhow.workspace = true async-trait.workspace = true futures.workspace = true diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 9347e7d7b1..efc24553c4 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -7,7 +7,7 @@ use git2::Repository as LibGitRepository; use lazy_static::lazy_static; use parking_lot::Mutex; use regex::Regex; -use repository::GitRepository; +use repository::{GitRepository, GitStatus}; use rope::Rope; use smol::io::{AsyncReadExt, AsyncWriteExt}; use std::borrow::Cow; @@ -27,7 +27,7 @@ use util::ResultExt; #[cfg(any(test, feature = "test-support"))] use collections::{btree_map, BTreeMap}; #[cfg(any(test, feature = "test-support"))] -use repository::{FakeGitRepositoryState, GitStatus}; +use repository::FakeGitRepositoryState; #[cfg(any(test, feature = "test-support"))] use std::sync::Weak; diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 73847dac29..7fa20bddcb 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -8,6 +8,7 @@ use std::{ path::{Component, Path, PathBuf}, sync::Arc, }; +use sum_tree::TreeMap; use util::ResultExt; pub use git2::Repository as LibGitRepository; @@ -20,7 +21,7 @@ pub trait GitRepository: Send { fn branch_name(&self) -> Option; - fn statuses(&self) -> Option>; + fn statuses(&self) -> Option>; fn file_status(&self, path: &RepoPath) -> Option; } @@ -69,10 +70,10 @@ impl GitRepository for LibGitRepository { Some(branch.to_string()) } - fn statuses(&self) -> Option> { + fn statuses(&self) -> Option> { let statuses = self.statuses(None).log_err()?; - let mut result = HashMap::default(); + let mut map = TreeMap::default(); for status in statuses .iter() @@ -80,10 +81,10 @@ impl GitRepository for LibGitRepository { { let path = RepoPath(PathBuf::from(OsStr::from_bytes(status.path_bytes()))); - result.insert(path, status.status().into()); + map.insert(path, status.status().into()) } - Some(result) + Some(map) } fn file_status(&self, path: &RepoPath) -> Option { @@ -125,9 +126,9 @@ impl GitRepository for FakeGitRepository { state.branch_name.clone() } - fn statuses(&self) -> Option> { + fn statuses(&self) -> Option> { let state = self.state.lock(); - let mut map = HashMap::default(); + let mut map = TreeMap::default(); for (repo_path, status) in state.git_statuses.iter() { map.insert(repo_path.to_owned(), status.to_owned()); } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index b9a4b549a1..82c719f31e 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -143,7 +143,7 @@ impl Snapshot { pub struct RepositoryEntry { pub(crate) work_directory: WorkDirectoryEntry, pub(crate) branch: Option>, - pub(crate) statuses: TreeMap, + pub(crate) statuses: TreeMap, } impl RepositoryEntry { @@ -165,8 +165,11 @@ impl RepositoryEntry { self.work_directory.contains(snapshot, path) } - pub fn status_for(&self, entry: ProjectEntryId) -> Option { - self.statuses.get(&entry).cloned() + pub fn status_for(&self, snapshot: &Snapshot, path: &Path) -> Option { + self.work_directory + .relativize(snapshot, path) + .and_then(|repo_path| self.statuses.get(&repo_path)) + .cloned() } } @@ -1810,6 +1813,10 @@ impl LocalSnapshot { ); } + if parent_path.file_name() == Some(&DOT_GIT) { + self.build_repo(parent_path, fs); + } + let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)]; let mut entries_by_id_edits = Vec::new(); @@ -1826,10 +1833,6 @@ impl LocalSnapshot { self.entries_by_path.edit(entries_by_path_edits, &()); self.entries_by_id.edit(entries_by_id_edits, &()); - - if parent_path.file_name() == Some(&DOT_GIT) { - self.build_repo(parent_path, fs); - } } fn build_repo(&mut self, parent_path: Arc, fs: &dyn Fs) -> Option<()> { @@ -1855,13 +1858,13 @@ impl LocalSnapshot { let scan_id = self.scan_id; let repo_lock = repo.lock(); - let statuses = convert_statuses(&work_directory, repo_lock.deref(), self)?; + self.repository_entries.insert( work_directory, RepositoryEntry { work_directory: work_dir_id.into(), branch: repo_lock.branch_name().map(Into::into), - statuses, + statuses: repo_lock.statuses().unwrap_or_default(), }, ); drop(repo_lock); @@ -2818,7 +2821,6 @@ impl BackgroundScanner { for (abs_path, metadata) in abs_paths.iter().zip(metadata.iter()) { if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) { if matches!(metadata, Ok(None)) || doing_recursive_update { - self.remove_repo_path(&path, &mut snapshot); snapshot.remove_path(path); } event_paths.push(path.into()); @@ -2864,7 +2866,9 @@ impl BackgroundScanner { } } } - Ok(None) => {} + Ok(None) => { + self.remove_repo_path(&path, &mut snapshot); + } Err(err) => { // TODO - create a special 'error' entry in the entries tree to mark this log::error!("error reading file on event {:?}", err); @@ -2883,7 +2887,7 @@ impl BackgroundScanner { let scan_id = snapshot.scan_id; let repo = snapshot.repo_for(&path)?; - let repo_path_id = snapshot.entry_for_path(path)?.id; + let repo_path = repo.work_directory.relativize(&snapshot, &path)?; let work_dir = repo.work_directory(snapshot)?; let work_dir_id = repo.work_directory; @@ -2894,7 +2898,7 @@ impl BackgroundScanner { snapshot .repository_entries - .update(&work_dir, |entry| entry.statuses.remove(&repo_path_id)); + .update(&work_dir, |entry| entry.statuses.remove(&repo_path)); } Some(()) @@ -2907,19 +2911,18 @@ impl BackgroundScanner { .components() .any(|component| component.as_os_str() == *DOT_GIT) { - let (git_dir_id, repo) = snapshot.repo_for_metadata(&path)?; + let (entry_id, repo) = snapshot.repo_for_metadata(&path)?; let work_dir = snapshot - .entry_for_id(git_dir_id) + .entry_for_id(entry_id) .map(|entry| RepositoryWorkDirectory(entry.path.clone()))?; let repo = repo.lock(); repo.reload_index(); let branch = repo.branch_name(); + let statuses = repo.statuses().unwrap_or_default(); - let statuses = convert_statuses(&work_dir, repo.deref(), snapshot)?; - - snapshot.git_repositories.update(&git_dir_id, |entry| { + snapshot.git_repositories.update(&entry_id, |entry| { entry.scan_id = scan_id; entry.full_scan_id = scan_id; }); @@ -2933,8 +2936,6 @@ impl BackgroundScanner { let repo_path = repo.work_directory.relativize(&snapshot, &path)?; - let path_id = snapshot.entry_for_path(&path)?.id; - let status = { let local_repo = snapshot.get_local_repo(&repo)?; @@ -2957,7 +2958,7 @@ impl BackgroundScanner { snapshot .repository_entries - .update(&work_dir, |entry| entry.statuses.insert(path_id, status)); + .update(&work_dir, |entry| entry.statuses.insert(repo_path, status)); } } @@ -3166,19 +3167,6 @@ impl BackgroundScanner { } } -fn convert_statuses( - work_dir: &RepositoryWorkDirectory, - repo: &dyn GitRepository, - snapshot: &Snapshot, -) -> Option> { - let mut statuses = TreeMap::default(); - for (path, status) in repo.statuses().unwrap_or_default() { - let path_entry = snapshot.entry_for_path(&work_dir.0.join(path.as_path()))?; - statuses.insert(path_entry.id, status) - } - Some(statuses) -} - fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag { let mut result = root_char_bag; result.extend( @@ -3860,11 +3848,6 @@ mod tests { "project": { "a.txt": "a", "b.txt": "bb", - "c": { - "d": { - "e.txt": "eee" - } - } }, })); @@ -3882,39 +3865,18 @@ mod tests { .await .unwrap(); - cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) - .await; - const A_TXT: &'static str = "a.txt"; const B_TXT: &'static str = "b.txt"; - const E_TXT: &'static str = "c/d/e.txt"; let work_dir = root.path().join("project"); - let tree_clone = tree.clone(); - let (a_txt_id, b_txt_id, e_txt_id) = cx.read(|cx| { - let tree = tree_clone.read(cx); - let a_id = tree - .entry_for_path(Path::new("project").join(Path::new(A_TXT))) - .unwrap() - .id; - let b_id = tree - .entry_for_path(Path::new("project").join(Path::new(B_TXT))) - .unwrap() - .id; - let e_id = tree - .entry_for_path(Path::new("project").join(Path::new(E_TXT))) - .unwrap() - .id; - (a_id, b_id, e_id) - }); - let mut repo = git_init(work_dir.as_path()); git_add(Path::new(A_TXT), &repo); - git_add(Path::new(E_TXT), &repo); git_commit("Initial commit", &repo); std::fs::write(work_dir.join(A_TXT), "aa").unwrap(); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; tree.flush_fs_events(cx).await; // Check that the right git state is observed on startup @@ -3923,9 +3885,16 @@ mod tests { assert_eq!(snapshot.repository_entries.iter().count(), 1); let (dir, repo) = snapshot.repository_entries.iter().next().unwrap(); assert_eq!(dir.0.as_ref(), Path::new("project")); + assert_eq!(repo.statuses.iter().count(), 2); - assert_eq!(repo.statuses.get(&a_txt_id), Some(&GitStatus::Modified)); - assert_eq!(repo.statuses.get(&b_txt_id), Some(&GitStatus::Added)); + assert_eq!( + repo.statuses.get(&Path::new(A_TXT).into()), + Some(&GitStatus::Modified) + ); + assert_eq!( + repo.statuses.get(&Path::new(B_TXT).into()), + Some(&GitStatus::Added) + ); }); git_add(Path::new(A_TXT), &repo); @@ -3939,18 +3908,15 @@ mod tests { let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); assert_eq!(repo.statuses.iter().count(), 0); - assert_eq!(repo.statuses.get(&a_txt_id), None); - assert_eq!(repo.statuses.get(&b_txt_id), None); + assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); + assert_eq!(repo.statuses.get(&Path::new(B_TXT).into()), None); }); git_reset(0, &repo); git_remove_index(Path::new(B_TXT), &repo); git_stash(&mut repo); - std::fs::write(work_dir.join(E_TXT), "eeee").unwrap(); tree.flush_fs_events(cx).await; - dbg!(git_status(&repo)); - // Check that more complex repo changes are tracked tree.read_with(cx, |tree, _cx| { let snapshot = tree.snapshot(); @@ -3958,14 +3924,15 @@ mod tests { dbg!(&repo.statuses); - assert_eq!(repo.statuses.iter().count(), 2); - assert_eq!(repo.statuses.get(&a_txt_id), None); - assert_eq!(repo.statuses.get(&b_txt_id), Some(&GitStatus::Added)); - assert_eq!(repo.statuses.get(&e_txt_id), Some(&GitStatus::Modified)); + assert_eq!(repo.statuses.iter().count(), 1); + assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); + assert_eq!( + repo.statuses.get(&Path::new(B_TXT).into()), + Some(&GitStatus::Added) + ); }); std::fs::remove_file(work_dir.join(B_TXT)).unwrap(); - std::fs::remove_dir_all(work_dir.join("c")).unwrap(); tree.flush_fs_events(cx).await; // Check that non-repo behavior is tracked @@ -3974,9 +3941,8 @@ mod tests { let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); assert_eq!(repo.statuses.iter().count(), 0); - assert_eq!(repo.statuses.get(&a_txt_id), None); - assert_eq!(repo.statuses.get(&b_txt_id), None); - assert_eq!(repo.statuses.get(&e_txt_id), None); + assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); + assert_eq!(repo.statuses.get(&Path::new(B_TXT).into()), None); }); } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 2baccc0913..16b6232e8b 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1012,9 +1012,10 @@ impl ProjectPanel { let entry_range = range.start.saturating_sub(ix)..end_ix - ix; for entry in &visible_worktree_entries[entry_range] { + let path = &entry.path; let status = snapshot - .repo_for(&entry.path) - .and_then(|repo_entry| repo_entry.status_for(entry.id)); + .repo_for(path) + .and_then(|entry| entry.status_for(&snapshot, path)); let mut details = EntryDetails { filename: entry From 23a19d85b817a70474f7ac2f9588191b46b7449a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 10 May 2023 09:55:10 -0700 Subject: [PATCH 030/168] Fix bug in status detection when removing a directory --- crates/collab/src/tests/integration_tests.rs | 32 +++---- crates/fs/src/fs.rs | 8 +- crates/fs/src/repository.rs | 62 ++++++------- crates/project/src/worktree.rs | 94 ++++++++++++-------- crates/project_panel/src/project_panel.rs | 17 ++-- 5 files changed, 108 insertions(+), 105 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 764f070f0b..185e6c6354 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -10,7 +10,7 @@ use editor::{ ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, ToggleCodeActions, Undo, }; -use fs::{repository::GitStatus, FakeFs, Fs as _, LineEnding, RemoveOptions}; +use fs::{repository::GitFileStatus, FakeFs, Fs as _, LineEnding, RemoveOptions}; use futures::StreamExt as _; use gpui::{ executor::Deterministic, geometry::vector::vec2f, test::EmptyView, AppContext, ModelHandle, @@ -2728,8 +2728,8 @@ async fn test_git_status_sync( .set_status_for_repo( Path::new("/dir/.git"), &[ - (&Path::new(A_TXT), GitStatus::Added), - (&Path::new(B_TXT), GitStatus::Added), + (&Path::new(A_TXT), GitFileStatus::Added), + (&Path::new(B_TXT), GitFileStatus::Added), ], ) .await; @@ -2748,7 +2748,7 @@ async fn test_git_status_sync( deterministic.run_until_parked(); #[track_caller] - fn assert_status(file: &impl AsRef, status: Option, project: &Project, cx: &AppContext) { + fn assert_status(file: &impl AsRef, status: Option, project: &Project, cx: &AppContext) { let file = file.as_ref(); let worktrees = project.visible_worktrees(cx).collect::>(); assert_eq!(worktrees.len(), 1); @@ -2760,12 +2760,12 @@ async fn test_git_status_sync( // Smoke test status reading project_local.read_with(cx_a, |project, cx| { - assert_status(&Path::new(A_TXT), Some(GitStatus::Added), project, cx); - assert_status(&Path::new(B_TXT), Some(GitStatus::Added), project, cx); + assert_status(&Path::new(A_TXT), Some(GitFileStatus::Added), project, cx); + assert_status(&Path::new(B_TXT), Some(GitFileStatus::Added), project, cx); }); project_remote.read_with(cx_b, |project, cx| { - assert_status(&Path::new(A_TXT), Some(GitStatus::Added), project, cx); - assert_status(&Path::new(B_TXT), Some(GitStatus::Added), project, cx); + assert_status(&Path::new(A_TXT), Some(GitFileStatus::Added), project, cx); + assert_status(&Path::new(B_TXT), Some(GitFileStatus::Added), project, cx); }); client_a @@ -2774,8 +2774,8 @@ async fn test_git_status_sync( .set_status_for_repo( Path::new("/dir/.git"), &[ - (&Path::new(A_TXT), GitStatus::Modified), - (&Path::new(B_TXT), GitStatus::Modified), + (&Path::new(A_TXT), GitFileStatus::Modified), + (&Path::new(B_TXT), GitFileStatus::Modified), ], ) .await; @@ -2785,19 +2785,19 @@ async fn test_git_status_sync( // Smoke test status reading project_local.read_with(cx_a, |project, cx| { - assert_status(&Path::new(A_TXT), Some(GitStatus::Added), project, cx); - assert_status(&Path::new(B_TXT), Some(GitStatus::Added), project, cx); + assert_status(&Path::new(A_TXT), Some(GitFileStatus::Added), project, cx); + assert_status(&Path::new(B_TXT), Some(GitFileStatus::Added), project, cx); }); project_remote.read_with(cx_b, |project, cx| { - assert_status(&Path::new(A_TXT), Some(GitStatus::Added), project, cx); - assert_status(&Path::new(B_TXT), Some(GitStatus::Added), project, cx); + assert_status(&Path::new(A_TXT), Some(GitFileStatus::Added), project, cx); + assert_status(&Path::new(B_TXT), Some(GitFileStatus::Added), project, cx); }); // And synchronization while joining let project_remote_c = client_c.build_remote_project(project_id, cx_c).await; project_remote_c.read_with(cx_c, |project, cx| { - assert_status(&Path::new(A_TXT), Some(GitStatus::Added), project, cx); - assert_status(&Path::new(B_TXT), Some(GitStatus::Added), project, cx); + assert_status(&Path::new(A_TXT), Some(GitFileStatus::Added), project, cx); + assert_status(&Path::new(B_TXT), Some(GitFileStatus::Added), project, cx); }); } diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index efc24553c4..fd094160f5 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -7,7 +7,7 @@ use git2::Repository as LibGitRepository; use lazy_static::lazy_static; use parking_lot::Mutex; use regex::Regex; -use repository::{GitRepository, GitStatus}; +use repository::{GitRepository, GitFileStatus}; use rope::Rope; use smol::io::{AsyncReadExt, AsyncWriteExt}; use std::borrow::Cow; @@ -654,10 +654,10 @@ impl FakeFs { }); } - pub async fn set_status_for_repo(&self, dot_git: &Path, statuses: &[(&Path, GitStatus)]) { + pub async fn set_status_for_repo(&self, dot_git: &Path, statuses: &[(&Path, GitFileStatus)]) { self.with_git_state(dot_git, |state| { - state.git_statuses.clear(); - state.git_statuses.extend( + state.worktree_statuses.clear(); + state.worktree_statuses.extend( statuses .iter() .map(|(path, content)| { diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 7fa20bddcb..3fb562570e 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -1,6 +1,5 @@ use anyhow::Result; use collections::HashMap; -use git2::Status; use parking_lot::Mutex; use std::{ ffi::OsStr, @@ -21,9 +20,9 @@ pub trait GitRepository: Send { fn branch_name(&self) -> Option; - fn statuses(&self) -> Option>; + fn worktree_statuses(&self) -> Option>; - fn file_status(&self, path: &RepoPath) -> Option; + fn worktree_status(&self, path: &RepoPath) -> Option; } impl std::fmt::Debug for dyn GitRepository { @@ -70,7 +69,7 @@ impl GitRepository for LibGitRepository { Some(branch.to_string()) } - fn statuses(&self) -> Option> { + fn worktree_statuses(&self) -> Option> { let statuses = self.statuses(None).log_err()?; let mut map = TreeMap::default(); @@ -80,17 +79,31 @@ impl GitRepository for LibGitRepository { .filter(|status| !status.status().contains(git2::Status::IGNORED)) { let path = RepoPath(PathBuf::from(OsStr::from_bytes(status.path_bytes()))); + let Some(status) = read_status(status.status()) else { + continue + }; - map.insert(path, status.status().into()) + map.insert(path, status) } Some(map) } - fn file_status(&self, path: &RepoPath) -> Option { + fn worktree_status(&self, path: &RepoPath) -> Option { let status = self.status_file(path).log_err()?; + read_status(status) + } +} - Some(status.into()) +fn read_status(status: git2::Status) -> Option { + if status.contains(git2::Status::CONFLICTED) { + Some(GitFileStatus::Conflict) + } else if status.intersects(git2::Status::WT_MODIFIED | git2::Status::WT_RENAMED) { + Some(GitFileStatus::Modified) + } else if status.intersects(git2::Status::WT_NEW) { + Some(GitFileStatus::Added) + } else { + None } } @@ -102,7 +115,7 @@ pub struct FakeGitRepository { #[derive(Debug, Clone, Default)] pub struct FakeGitRepositoryState { pub index_contents: HashMap, - pub git_statuses: HashMap, + pub worktree_statuses: HashMap, pub branch_name: Option, } @@ -126,18 +139,18 @@ impl GitRepository for FakeGitRepository { state.branch_name.clone() } - fn statuses(&self) -> Option> { + fn worktree_statuses(&self) -> Option> { let state = self.state.lock(); let mut map = TreeMap::default(); - for (repo_path, status) in state.git_statuses.iter() { + for (repo_path, status) in state.worktree_statuses.iter() { map.insert(repo_path.to_owned(), status.to_owned()); } Some(map) } - fn file_status(&self, path: &RepoPath) -> Option { + fn worktree_status(&self, path: &RepoPath) -> Option { let state = self.state.lock(); - state.git_statuses.get(path).cloned() + state.worktree_statuses.get(path).cloned() } } @@ -170,32 +183,11 @@ fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> { } } -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub enum GitStatus { +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum GitFileStatus { Added, Modified, Conflict, - #[default] - Untracked, -} - -impl From for GitStatus { - fn from(value: Status) -> Self { - if value.contains(git2::Status::CONFLICTED) { - GitStatus::Conflict - } else if value.intersects( - git2::Status::INDEX_MODIFIED - | git2::Status::WT_MODIFIED - | git2::Status::INDEX_RENAMED - | git2::Status::WT_RENAMED, - ) { - GitStatus::Modified - } else if value.intersects(git2::Status::INDEX_NEW | git2::Status::WT_NEW) { - GitStatus::Added - } else { - GitStatus::Untracked - } - } } #[derive(Clone, Debug, Ord, Hash, PartialOrd, Eq, PartialEq)] diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 82c719f31e..1fc4fcf5a8 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -7,7 +7,7 @@ use client::{proto, Client}; use clock::ReplicaId; use collections::{HashMap, VecDeque}; use fs::{ - repository::{GitRepository, GitStatus, RepoPath}, + repository::{GitRepository, GitFileStatus, RepoPath}, Fs, LineEnding, }; use futures::{ @@ -143,7 +143,7 @@ impl Snapshot { pub struct RepositoryEntry { pub(crate) work_directory: WorkDirectoryEntry, pub(crate) branch: Option>, - pub(crate) statuses: TreeMap, + pub(crate) worktree_statuses: TreeMap, } impl RepositoryEntry { @@ -165,10 +165,10 @@ impl RepositoryEntry { self.work_directory.contains(snapshot, path) } - pub fn status_for(&self, snapshot: &Snapshot, path: &Path) -> Option { + pub fn status_for(&self, snapshot: &Snapshot, path: &Path) -> Option { self.work_directory .relativize(snapshot, path) - .and_then(|repo_path| self.statuses.get(&repo_path)) + .and_then(|repo_path| self.worktree_statuses.get(&repo_path)) .cloned() } } @@ -1445,7 +1445,7 @@ impl Snapshot { work_directory: ProjectEntryId::from_proto(repository.work_directory_id).into(), branch: repository.branch.map(Into::into), // TODO: status - statuses: Default::default(), + worktree_statuses: Default::default(), }; if let Some(entry) = self.entry_for_id(repository.work_directory_id()) { self.repository_entries @@ -1864,7 +1864,7 @@ impl LocalSnapshot { RepositoryEntry { work_directory: work_dir_id.into(), branch: repo_lock.branch_name().map(Into::into), - statuses: repo_lock.statuses().unwrap_or_default(), + worktree_statuses: repo_lock.worktree_statuses().unwrap_or_default(), }, ); drop(repo_lock); @@ -2896,9 +2896,12 @@ impl BackgroundScanner { .git_repositories .update(&work_dir_id, |entry| entry.scan_id = scan_id); + // TODO: Status Replace linear scan with smarter sum tree traversal snapshot .repository_entries - .update(&work_dir, |entry| entry.statuses.remove(&repo_path)); + .update(&work_dir, |entry| entry.worktree_statuses.retain(|stored_path, _| { + !stored_path.starts_with(&repo_path) + })); } Some(()) @@ -2920,7 +2923,7 @@ impl BackgroundScanner { let repo = repo.lock(); repo.reload_index(); let branch = repo.branch_name(); - let statuses = repo.statuses().unwrap_or_default(); + let statuses = repo.worktree_statuses().unwrap_or_default(); snapshot.git_repositories.update(&entry_id, |entry| { entry.scan_id = scan_id; @@ -2929,7 +2932,7 @@ impl BackgroundScanner { snapshot.repository_entries.update(&work_dir, |entry| { entry.branch = branch.map(Into::into); - entry.statuses = statuses; + entry.worktree_statuses = statuses; }); } else { let repo = snapshot.repo_for(&path)?; @@ -2945,21 +2948,19 @@ impl BackgroundScanner { } let git_ptr = local_repo.repo_ptr.lock(); - git_ptr.file_status(&repo_path)? + git_ptr.worktree_status(&repo_path)? }; - if status != GitStatus::Untracked { - let work_dir = repo.work_directory(snapshot)?; - let work_dir_id = repo.work_directory; + let work_dir = repo.work_directory(snapshot)?; + let work_dir_id = repo.work_directory; - snapshot - .git_repositories - .update(&work_dir_id, |entry| entry.scan_id = scan_id); + snapshot + .git_repositories + .update(&work_dir_id, |entry| entry.scan_id = scan_id); - snapshot - .repository_entries - .update(&work_dir, |entry| entry.statuses.insert(repo_path, status)); - } + snapshot + .repository_entries + .update(&work_dir, |entry| entry.worktree_statuses.insert(repo_path, status)); } Some(()) @@ -3848,6 +3849,11 @@ mod tests { "project": { "a.txt": "a", "b.txt": "bb", + "c": { + "d": { + "e.txt": "eee" + } + } }, })); @@ -3865,18 +3871,22 @@ mod tests { .await .unwrap(); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + const A_TXT: &'static str = "a.txt"; const B_TXT: &'static str = "b.txt"; + const E_TXT: &'static str = "c/d/e.txt"; + let work_dir = root.path().join("project"); let mut repo = git_init(work_dir.as_path()); git_add(Path::new(A_TXT), &repo); + git_add(Path::new(E_TXT), &repo); git_commit("Initial commit", &repo); std::fs::write(work_dir.join(A_TXT), "aa").unwrap(); - cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) - .await; tree.flush_fs_events(cx).await; // Check that the right git state is observed on startup @@ -3886,14 +3896,14 @@ mod tests { let (dir, repo) = snapshot.repository_entries.iter().next().unwrap(); assert_eq!(dir.0.as_ref(), Path::new("project")); - assert_eq!(repo.statuses.iter().count(), 2); + assert_eq!(repo.worktree_statuses.iter().count(), 2); assert_eq!( - repo.statuses.get(&Path::new(A_TXT).into()), - Some(&GitStatus::Modified) + repo.worktree_statuses.get(&Path::new(A_TXT).into()), + Some(&GitFileStatus::Modified) ); assert_eq!( - repo.statuses.get(&Path::new(B_TXT).into()), - Some(&GitStatus::Added) + repo.worktree_statuses.get(&Path::new(B_TXT).into()), + Some(&GitFileStatus::Added) ); }); @@ -3907,14 +3917,15 @@ mod tests { let snapshot = tree.snapshot(); let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - assert_eq!(repo.statuses.iter().count(), 0); - assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); - assert_eq!(repo.statuses.get(&Path::new(B_TXT).into()), None); + assert_eq!(repo.worktree_statuses.iter().count(), 0); + assert_eq!(repo.worktree_statuses.get(&Path::new(A_TXT).into()), None); + assert_eq!(repo.worktree_statuses.get(&Path::new(B_TXT).into()), None); }); git_reset(0, &repo); git_remove_index(Path::new(B_TXT), &repo); git_stash(&mut repo); + std::fs::write(work_dir.join(E_TXT), "eeee").unwrap(); tree.flush_fs_events(cx).await; // Check that more complex repo changes are tracked @@ -3922,17 +3933,21 @@ mod tests { let snapshot = tree.snapshot(); let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - dbg!(&repo.statuses); - - assert_eq!(repo.statuses.iter().count(), 1); - assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); + assert_eq!(repo.worktree_statuses.iter().count(), 2); + assert_eq!(repo.worktree_statuses.get(&Path::new(A_TXT).into()), None); assert_eq!( - repo.statuses.get(&Path::new(B_TXT).into()), - Some(&GitStatus::Added) + repo.worktree_statuses.get(&Path::new(B_TXT).into()), + Some(&GitFileStatus::Added) + ); + assert_eq!( + repo.worktree_statuses.get(&Path::new(E_TXT).into()), + Some(&GitFileStatus::Modified) ); }); std::fs::remove_file(work_dir.join(B_TXT)).unwrap(); + std::fs::remove_dir_all(work_dir.join("c")).unwrap(); + tree.flush_fs_events(cx).await; // Check that non-repo behavior is tracked @@ -3940,9 +3955,10 @@ mod tests { let snapshot = tree.snapshot(); let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - assert_eq!(repo.statuses.iter().count(), 0); - assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); - assert_eq!(repo.statuses.get(&Path::new(B_TXT).into()), None); + assert_eq!(repo.worktree_statuses.iter().count(), 0); + assert_eq!(repo.worktree_statuses.get(&Path::new(A_TXT).into()), None); + assert_eq!(repo.worktree_statuses.get(&Path::new(B_TXT).into()), None); + assert_eq!(repo.worktree_statuses.get(&Path::new(E_TXT).into()), None); }); } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 16b6232e8b..49741ea49f 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -17,7 +17,7 @@ use gpui::{ }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{ - repository::GitStatus, Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, + repository::GitFileStatus, Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId, }; use settings::Settings; @@ -89,7 +89,7 @@ pub struct EntryDetails { is_editing: bool, is_processing: bool, is_cut: bool, - git_status: Option, + git_status: Option, } actions!( @@ -1081,19 +1081,14 @@ impl ProjectPanel { // Prepare colors for git statuses let editor_theme = &cx.global::().theme.editor; - let color_for_added = Some(editor_theme.diff.inserted); - let color_for_modified = Some(editor_theme.diff.modified); - let color_for_conflict = Some(editor_theme.diff.deleted); - let color_for_untracked = None; let mut filename_text_style = style.text.clone(); filename_text_style.color = details .git_status .as_ref() - .and_then(|status| match status { - GitStatus::Added => color_for_added, - GitStatus::Modified => color_for_modified, - GitStatus::Conflict => color_for_conflict, - GitStatus::Untracked => color_for_untracked, + .map(|status| match status { + GitFileStatus::Added => editor_theme.diff.inserted, + GitFileStatus::Modified => editor_theme.diff.modified, + GitFileStatus::Conflict => editor_theme.diff.deleted, }) .unwrap_or(style.text.color); From 00b345fdfe426b00c7da0219585eda03c20a9171 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 10 May 2023 11:28:39 -0700 Subject: [PATCH 031/168] Use sum tree traversal to remove paths --- crates/project/src/worktree.rs | 26 ++++++++----- crates/sum_tree/src/tree_map.rs | 68 +++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 1fc4fcf5a8..ff9f7fde9a 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -7,7 +7,7 @@ use client::{proto, Client}; use clock::ReplicaId; use collections::{HashMap, VecDeque}; use fs::{ - repository::{GitRepository, GitFileStatus, RepoPath}, + repository::{GitFileStatus, GitRepository, RepoPath}, Fs, LineEnding, }; use futures::{ @@ -46,6 +46,7 @@ use std::{ future::Future, mem, ops::{Deref, DerefMut}, + path::{Path, PathBuf}, pin::Pin, sync::{ @@ -2896,12 +2897,13 @@ impl BackgroundScanner { .git_repositories .update(&work_dir_id, |entry| entry.scan_id = scan_id); - // TODO: Status Replace linear scan with smarter sum tree traversal - snapshot - .repository_entries - .update(&work_dir, |entry| entry.worktree_statuses.retain(|stored_path, _| { - !stored_path.starts_with(&repo_path) - })); + snapshot.repository_entries.update(&work_dir, |entry| { + entry + .worktree_statuses + .remove_from_while(&repo_path, |stored_path, _| { + stored_path.starts_with(&repo_path) + }) + }); } Some(()) @@ -2958,9 +2960,9 @@ impl BackgroundScanner { .git_repositories .update(&work_dir_id, |entry| entry.scan_id = scan_id); - snapshot - .repository_entries - .update(&work_dir, |entry| entry.worktree_statuses.insert(repo_path, status)); + snapshot.repository_entries.update(&work_dir, |entry| { + entry.worktree_statuses.insert(repo_path, status) + }); } Some(()) @@ -3948,6 +3950,8 @@ mod tests { std::fs::remove_file(work_dir.join(B_TXT)).unwrap(); std::fs::remove_dir_all(work_dir.join("c")).unwrap(); + dbg!(git_status(&repo)); + tree.flush_fs_events(cx).await; // Check that non-repo behavior is tracked @@ -3955,6 +3959,8 @@ mod tests { let snapshot = tree.snapshot(); let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + dbg!(&repo.worktree_statuses); + assert_eq!(repo.worktree_statuses.iter().count(), 0); assert_eq!(repo.worktree_statuses.get(&Path::new(A_TXT).into()), None); assert_eq!(repo.worktree_statuses.get(&Path::new(B_TXT).into()), None); diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index ab37d2577a..359137d439 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -82,6 +82,36 @@ impl TreeMap { cursor.item().map(|item| (&item.key, &item.value)) } + pub fn remove_between(&mut self, from: &K, until: &K) + { + let mut cursor = self.0.cursor::>(); + let from_key = MapKeyRef(Some(from)); + let mut new_tree = cursor.slice(&from_key, Bias::Left, &()); + let until_key = MapKeyRef(Some(until)); + cursor.seek_forward(&until_key, Bias::Left, &()); + new_tree.push_tree(cursor.suffix(&()), &()); + drop(cursor); + self.0 = new_tree; + } + + pub fn remove_from_while(&mut self, from: &K, mut f: F) + where F: FnMut(&K, &V) -> bool + { + let mut cursor = self.0.cursor::>(); + let from_key = MapKeyRef(Some(from)); + let mut new_tree = cursor.slice(&from_key, Bias::Left, &()); + while let Some(item) = cursor.item() { + if !f(&item.key, &item.value) { + break; + } + cursor.next(&()); + } + new_tree.push_tree(cursor.suffix(&()), &()); + drop(cursor); + self.0 = new_tree; + } + + pub fn update(&mut self, key: &K, f: F) -> Option where F: FnOnce(&mut V) -> T, @@ -272,4 +302,42 @@ mod tests { map.retain(|key, _| *key % 2 == 0); assert_eq!(map.iter().collect::>(), vec![(&4, &"d"), (&6, &"f")]); } + + #[test] + fn test_remove_between() { + let mut map = TreeMap::default(); + + map.insert("a", 1); + map.insert("b", 2); + map.insert("baa", 3); + map.insert("baaab", 4); + map.insert("c", 5); + + map.remove_between(&"ba", &"bb"); + + assert_eq!(map.get(&"a"), Some(&1)); + assert_eq!(map.get(&"b"), Some(&2)); + assert_eq!(map.get(&"baaa"), None); + assert_eq!(map.get(&"baaaab"), None); + assert_eq!(map.get(&"c"), Some(&5)); + } + + #[test] + fn test_remove_from_while() { + let mut map = TreeMap::default(); + + map.insert("a", 1); + map.insert("b", 2); + map.insert("baa", 3); + map.insert("baaab", 4); + map.insert("c", 5); + + map.remove_from_while(&"ba", |key, _| key.starts_with(&"ba")); + + assert_eq!(map.get(&"a"), Some(&1)); + assert_eq!(map.get(&"b"), Some(&2)); + assert_eq!(map.get(&"baaa"), None); + assert_eq!(map.get(&"baaaab"), None); + assert_eq!(map.get(&"c"), Some(&5)); + } } From 2b80dfa81d2e4ed6f36443b28a18906412ef1419 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 10 May 2023 11:59:04 -0700 Subject: [PATCH 032/168] Update protos --- crates/collab/src/db.rs | 8 ++++---- crates/project/src/worktree.rs | 25 +++++++++++++------------ crates/rpc/proto/zed.proto | 6 +++--- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 5867fa7369..b9bd1374eb 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -1568,8 +1568,8 @@ impl Database { worktree.updated_repositories.push(proto::RepositoryEntry { work_directory_id: db_repository.work_directory_id as u64, branch: db_repository.branch, - removed_statuses: Default::default(), - updated_statuses: Default::default(), + removed_worktree_repo_paths: Default::default(), + updated_worktree_statuses: Default::default(), }); } @@ -2651,8 +2651,8 @@ impl Database { worktree.repository_entries.push(proto::RepositoryEntry { work_directory_id: db_repository_entry.work_directory_id as u64, branch: db_repository_entry.branch, - removed_statuses: Default::default(), - updated_statuses: Default::default(), + removed_worktree_repo_paths: Default::default(), + updated_worktree_statuses: Default::default(), }); } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index ff9f7fde9a..7b760ae354 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -180,8 +180,8 @@ impl From<&RepositoryEntry> for proto::RepositoryEntry { work_directory_id: value.work_directory.to_proto(), branch: value.branch.as_ref().map(|str| str.to_string()), // TODO: Status - removed_statuses: Default::default(), - updated_statuses: Default::default(), + removed_worktree_repo_paths: Default::default(), + updated_worktree_statuses: Default::default(), } } } @@ -1597,12 +1597,11 @@ impl LocalSnapshot { pub(crate) fn repo_for_metadata( &self, path: &Path, - ) -> Option<(ProjectEntryId, Arc>)> { - let (entry_id, local_repo) = self + ) -> Option<(&ProjectEntryId, &LocalRepositoryEntry)> { + self .git_repositories .iter() - .find(|(_, repo)| repo.in_dot_git(path))?; - Some((*entry_id, local_repo.repo_ptr.to_owned())) + .find(|(_, repo)| repo.in_dot_git(path)) } #[cfg(test)] @@ -2916,13 +2915,19 @@ impl BackgroundScanner { .components() .any(|component| component.as_os_str() == *DOT_GIT) { - let (entry_id, repo) = snapshot.repo_for_metadata(&path)?; + let (entry_id, repo_ptr) = { + let (entry_id, repo) = snapshot.repo_for_metadata(&path)?; + if repo.full_scan_id == scan_id { + return None; + } + (*entry_id, repo.repo_ptr.to_owned()) + }; let work_dir = snapshot .entry_for_id(entry_id) .map(|entry| RepositoryWorkDirectory(entry.path.clone()))?; - let repo = repo.lock(); + let repo = repo_ptr.lock(); repo.reload_index(); let branch = repo.branch_name(); let statuses = repo.worktree_statuses().unwrap_or_default(); @@ -3950,8 +3955,6 @@ mod tests { std::fs::remove_file(work_dir.join(B_TXT)).unwrap(); std::fs::remove_dir_all(work_dir.join("c")).unwrap(); - dbg!(git_status(&repo)); - tree.flush_fs_events(cx).await; // Check that non-repo behavior is tracked @@ -3959,8 +3962,6 @@ mod tests { let snapshot = tree.snapshot(); let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - dbg!(&repo.worktree_statuses); - assert_eq!(repo.worktree_statuses.iter().count(), 0); assert_eq!(repo.worktree_statuses.get(&Path::new(A_TXT).into()), None); assert_eq!(repo.worktree_statuses.get(&Path::new(B_TXT).into()), None); diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index abe02f42bb..8e45435b89 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -986,12 +986,12 @@ message Entry { message RepositoryEntry { uint64 work_directory_id = 1; optional string branch = 2; - repeated uint64 removed_statuses = 3; - repeated StatusEntry updated_statuses = 4; + repeated string removed_worktree_repo_paths = 3; + repeated StatusEntry updated_worktree_statuses = 4; } message StatusEntry { - uint64 entry_id = 1; + string repo_path = 1; GitStatus status = 2; } From e20eaca59513622b903f201410b89fb2ac21525a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 10 May 2023 16:07:41 -0700 Subject: [PATCH 033/168] Got basic replication working :) --- crates/collab/src/db.rs | 2 - crates/collab/src/tests/integration_tests.rs | 49 ++++++- crates/fs/src/fs.rs | 6 +- crates/fs/src/repository.rs | 2 +- crates/project/src/worktree.rs | 129 ++++++++++++++++--- crates/rpc/src/proto.rs | 65 +++++++++- crates/sum_tree/src/tree_map.rs | 44 ++++++- 7 files changed, 256 insertions(+), 41 deletions(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index b9bd1374eb..217987984a 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -1570,7 +1570,6 @@ impl Database { branch: db_repository.branch, removed_worktree_repo_paths: Default::default(), updated_worktree_statuses: Default::default(), - }); } } @@ -2653,7 +2652,6 @@ impl Database { branch: db_repository_entry.branch, removed_worktree_repo_paths: Default::default(), updated_worktree_statuses: Default::default(), - }); } } diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 185e6c6354..7dd8f86b8e 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2748,7 +2748,12 @@ async fn test_git_status_sync( deterministic.run_until_parked(); #[track_caller] - fn assert_status(file: &impl AsRef, status: Option, project: &Project, cx: &AppContext) { + fn assert_status( + file: &impl AsRef, + status: Option, + project: &Project, + cx: &AppContext, + ) { let file = file.as_ref(); let worktrees = project.visible_worktrees(cx).collect::>(); assert_eq!(worktrees.len(), 1); @@ -2785,19 +2790,49 @@ async fn test_git_status_sync( // Smoke test status reading project_local.read_with(cx_a, |project, cx| { - assert_status(&Path::new(A_TXT), Some(GitFileStatus::Added), project, cx); - assert_status(&Path::new(B_TXT), Some(GitFileStatus::Added), project, cx); + assert_status( + &Path::new(A_TXT), + Some(GitFileStatus::Modified), + project, + cx, + ); + assert_status( + &Path::new(B_TXT), + Some(GitFileStatus::Modified), + project, + cx, + ); }); project_remote.read_with(cx_b, |project, cx| { - assert_status(&Path::new(A_TXT), Some(GitFileStatus::Added), project, cx); - assert_status(&Path::new(B_TXT), Some(GitFileStatus::Added), project, cx); + assert_status( + &Path::new(A_TXT), + Some(GitFileStatus::Modified), + project, + cx, + ); + assert_status( + &Path::new(B_TXT), + Some(GitFileStatus::Modified), + project, + cx, + ); }); // And synchronization while joining let project_remote_c = client_c.build_remote_project(project_id, cx_c).await; project_remote_c.read_with(cx_c, |project, cx| { - assert_status(&Path::new(A_TXT), Some(GitFileStatus::Added), project, cx); - assert_status(&Path::new(B_TXT), Some(GitFileStatus::Added), project, cx); + assert_status( + &Path::new(A_TXT), + Some(GitFileStatus::Modified), + project, + cx, + ); + assert_status( + &Path::new(B_TXT), + Some(GitFileStatus::Modified), + project, + cx, + ); }); } diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index fd094160f5..09ddce2ffa 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -7,7 +7,7 @@ use git2::Repository as LibGitRepository; use lazy_static::lazy_static; use parking_lot::Mutex; use regex::Regex; -use repository::{GitRepository, GitFileStatus}; +use repository::{GitFileStatus, GitRepository}; use rope::Rope; use smol::io::{AsyncReadExt, AsyncWriteExt}; use std::borrow::Cow; @@ -660,9 +660,7 @@ impl FakeFs { state.worktree_statuses.extend( statuses .iter() - .map(|(path, content)| { - ((**path).into(), content.clone()) - }), + .map(|(path, content)| ((**path).into(), content.clone())), ); }); } diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 3fb562570e..90b3761677 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -194,7 +194,7 @@ pub enum GitFileStatus { pub struct RepoPath(PathBuf); impl RepoPath { - fn new(path: PathBuf) -> Self { + pub fn new(path: PathBuf) -> Self { debug_assert!(path.is_relative(), "Repo paths must be relative"); RepoPath(path) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 7b760ae354..0d4a02775d 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -46,7 +46,6 @@ use std::{ future::Future, mem, ops::{Deref, DerefMut}, - path::{Path, PathBuf}, pin::Pin, sync::{ @@ -147,6 +146,14 @@ pub struct RepositoryEntry { pub(crate) worktree_statuses: TreeMap, } +fn read_git_status(git_status: i32) -> Option { + proto::GitStatus::from_i32(git_status).map(|status| match status { + proto::GitStatus::Added => GitFileStatus::Added, + proto::GitStatus::Modified => GitFileStatus::Modified, + proto::GitStatus::Conflict => GitFileStatus::Conflict, + }) +} + impl RepositoryEntry { pub fn branch(&self) -> Option> { self.branch.clone() @@ -172,6 +179,70 @@ impl RepositoryEntry { .and_then(|repo_path| self.worktree_statuses.get(&repo_path)) .cloned() } + + pub fn build_update(&self, other: &Self) -> proto::RepositoryEntry { + let mut updated_statuses: Vec = Vec::new(); + let mut removed_statuses: Vec = Vec::new(); + + let mut self_statuses = self.worktree_statuses.iter().peekable(); + let mut other_statuses = other.worktree_statuses.iter().peekable(); + loop { + match (self_statuses.peek(), other_statuses.peek()) { + (Some((self_repo_path, self_status)), Some((other_repo_path, other_status))) => { + match Ord::cmp(self_repo_path, other_repo_path) { + Ordering::Less => { + updated_statuses.push(make_status_entry(self_repo_path, self_status)); + self_statuses.next(); + } + Ordering::Equal => { + if self_status != other_status { + updated_statuses + .push(make_status_entry(self_repo_path, self_status)); + } + + self_statuses.next(); + other_statuses.next(); + } + Ordering::Greater => { + removed_statuses.push(make_repo_path(other_repo_path)); + other_statuses.next(); + } + } + } + (Some((self_repo_path, self_status)), None) => { + updated_statuses.push(make_status_entry(self_repo_path, self_status)); + self_statuses.next(); + } + (None, Some((other_repo_path, _))) => { + removed_statuses.push(make_repo_path(other_repo_path)); + other_statuses.next(); + } + (None, None) => break, + } + } + + proto::RepositoryEntry { + work_directory_id: self.work_directory_id().to_proto(), + branch: self.branch.as_ref().map(|str| str.to_string()), + removed_worktree_repo_paths: removed_statuses, + updated_worktree_statuses: updated_statuses, + } + } +} + +fn make_repo_path(path: &RepoPath) -> String { + path.as_os_str().to_string_lossy().to_string() +} + +fn make_status_entry(path: &RepoPath, status: &GitFileStatus) -> proto::StatusEntry { + proto::StatusEntry { + repo_path: make_repo_path(path), + status: match status { + GitFileStatus::Added => proto::GitStatus::Added.into(), + GitFileStatus::Modified => proto::GitStatus::Modified.into(), + GitFileStatus::Conflict => proto::GitStatus::Conflict.into(), + }, + } } impl From<&RepositoryEntry> for proto::RepositoryEntry { @@ -179,9 +250,12 @@ impl From<&RepositoryEntry> for proto::RepositoryEntry { proto::RepositoryEntry { work_directory_id: value.work_directory.to_proto(), branch: value.branch.as_ref().map(|str| str.to_string()), - // TODO: Status + updated_worktree_statuses: value + .worktree_statuses + .iter() + .map(|(repo_path, status)| make_status_entry(repo_path, status)) + .collect(), removed_worktree_repo_paths: Default::default(), - updated_worktree_statuses: Default::default(), } } } @@ -1442,15 +1516,41 @@ impl Snapshot { }); for repository in update.updated_repositories { - let repository = RepositoryEntry { - work_directory: ProjectEntryId::from_proto(repository.work_directory_id).into(), - branch: repository.branch.map(Into::into), - // TODO: status - worktree_statuses: Default::default(), - }; - if let Some(entry) = self.entry_for_id(repository.work_directory_id()) { - self.repository_entries - .insert(RepositoryWorkDirectory(entry.path.clone()), repository) + let work_directory_entry: WorkDirectoryEntry = + ProjectEntryId::from_proto(repository.work_directory_id).into(); + + if let Some(entry) = self.entry_for_id(*work_directory_entry) { + let mut statuses = TreeMap::default(); + for status_entry in repository.updated_worktree_statuses { + let Some(git_file_status) = read_git_status(status_entry.status) else { + continue; + }; + + let repo_path = RepoPath::new(status_entry.repo_path.into()); + statuses.insert(repo_path, git_file_status); + } + + let work_directory = RepositoryWorkDirectory(entry.path.clone()); + if self.repository_entries.get(&work_directory).is_some() { + self.repository_entries.update(&work_directory, |repo| { + repo.branch = repository.branch.map(Into::into); + repo.worktree_statuses.insert_tree(statuses); + + for repo_path in repository.removed_worktree_repo_paths { + let repo_path = RepoPath::new(repo_path.into()); + repo.worktree_statuses.remove(&repo_path); + } + }); + } else { + self.repository_entries.insert( + work_directory, + RepositoryEntry { + work_directory: work_directory_entry, + branch: repository.branch.map(Into::into), + worktree_statuses: statuses, + }, + ) + } } else { log::error!("no work directory entry for repository {:?}", repository) } @@ -1598,8 +1698,7 @@ impl LocalSnapshot { &self, path: &Path, ) -> Option<(&ProjectEntryId, &LocalRepositoryEntry)> { - self - .git_repositories + self.git_repositories .iter() .find(|(_, repo)| repo.in_dot_git(path)) } @@ -1691,7 +1790,7 @@ impl LocalSnapshot { } Ordering::Equal => { if self_repo != other_repo { - updated_repositories.push((*self_repo).into()); + updated_repositories.push(self_repo.build_update(other_repo)); } self_repos.next(); diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 20a457cc4b..32f40ad7db 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -484,9 +484,11 @@ pub fn split_worktree_update( mut message: UpdateWorktree, max_chunk_size: usize, ) -> impl Iterator { - let mut done = false; + let mut done_files = false; + let mut done_statuses = false; + let mut repository_index = 0; iter::from_fn(move || { - if done { + if done_files && done_statuses { return None; } @@ -502,22 +504,71 @@ pub fn split_worktree_update( .drain(..removed_entries_chunk_size) .collect(); - done = message.updated_entries.is_empty() && message.removed_entries.is_empty(); + done_files = message.updated_entries.is_empty() && message.removed_entries.is_empty(); // Wait to send repositories until after we've guaranteed that their associated entries // will be read - let updated_repositories = if done { - mem::take(&mut message.updated_repositories) + let updated_repositories = if done_files { + let mut total_statuses = 0; + let mut updated_repositories = Vec::new(); + while total_statuses < max_chunk_size + && repository_index < message.updated_repositories.len() + { + let updated_statuses_chunk_size = cmp::min( + message.updated_repositories[repository_index] + .updated_worktree_statuses + .len(), + max_chunk_size - total_statuses, + ); + + let updated_statuses: Vec<_> = message.updated_repositories[repository_index] + .updated_worktree_statuses + .drain(..updated_statuses_chunk_size) + .collect(); + + total_statuses += updated_statuses.len(); + + let done_this_repo = message.updated_repositories[repository_index] + .updated_worktree_statuses + .is_empty(); + + let removed_repo_paths = if done_this_repo { + mem::take( + &mut message.updated_repositories[repository_index] + .removed_worktree_repo_paths, + ) + } else { + Default::default() + }; + + updated_repositories.push(RepositoryEntry { + work_directory_id: message.updated_repositories[repository_index] + .work_directory_id, + branch: message.updated_repositories[repository_index] + .branch + .clone(), + updated_worktree_statuses: updated_statuses, + removed_worktree_repo_paths: removed_repo_paths, + }); + + if done_this_repo { + repository_index += 1; + } + } + + updated_repositories } else { Default::default() }; - let removed_repositories = if done { + let removed_repositories = if done_files && done_statuses { mem::take(&mut message.removed_repositories) } else { Default::default() }; + done_statuses = repository_index >= message.updated_repositories.len(); + Some(UpdateWorktree { project_id: message.project_id, worktree_id: message.worktree_id, @@ -526,7 +577,7 @@ pub fn split_worktree_update( updated_entries, removed_entries, scan_id: message.scan_id, - is_last_update: done && message.is_last_update, + is_last_update: done_files && message.is_last_update, updated_repositories, removed_repositories, }) diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index 359137d439..3942d00b29 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -1,6 +1,6 @@ use std::{cmp::Ordering, fmt::Debug}; -use crate::{Bias, Dimension, Item, KeyedItem, SeekTarget, SumTree, Summary}; +use crate::{Bias, Dimension, Edit, Item, KeyedItem, SeekTarget, SumTree, Summary}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct TreeMap(SumTree>) @@ -82,8 +82,7 @@ impl TreeMap { cursor.item().map(|item| (&item.key, &item.value)) } - pub fn remove_between(&mut self, from: &K, until: &K) - { + pub fn remove_between(&mut self, from: &K, until: &K) { let mut cursor = self.0.cursor::>(); let from_key = MapKeyRef(Some(from)); let mut new_tree = cursor.slice(&from_key, Bias::Left, &()); @@ -95,7 +94,8 @@ impl TreeMap { } pub fn remove_from_while(&mut self, from: &K, mut f: F) - where F: FnMut(&K, &V) -> bool + where + F: FnMut(&K, &V) -> bool, { let mut cursor = self.0.cursor::>(); let from_key = MapKeyRef(Some(from)); @@ -111,7 +111,6 @@ impl TreeMap { self.0 = new_tree; } - pub fn update(&mut self, key: &K, f: F) -> Option where F: FnOnce(&mut V) -> T, @@ -155,6 +154,20 @@ impl TreeMap { pub fn values(&self) -> impl Iterator + '_ { self.0.iter().map(|entry| &entry.value) } + + pub fn insert_tree(&mut self, other: TreeMap) { + let edits = other + .iter() + .map(|(key, value)| { + Edit::Insert(MapEntry { + key: key.to_owned(), + value: value.to_owned(), + }) + }) + .collect(); + + self.0.edit(edits, &()); + } } impl Default for TreeMap @@ -340,4 +353,25 @@ mod tests { assert_eq!(map.get(&"baaaab"), None); assert_eq!(map.get(&"c"), Some(&5)); } + + #[test] + fn test_insert_tree() { + let mut map = TreeMap::default(); + map.insert("a", 1); + map.insert("b", 2); + map.insert("c", 3); + + let mut other = TreeMap::default(); + other.insert("a", 2); + other.insert("b", 2); + other.insert("d", 4); + + map.insert_tree(other); + + assert_eq!(map.iter().count(), 4); + assert_eq!(map.get(&"a"), Some(&2)); + assert_eq!(map.get(&"b"), Some(&2)); + assert_eq!(map.get(&"c"), Some(&3)); + assert_eq!(map.get(&"d"), Some(&4)); + } } From 65d4c4f6ed62bd25997c528b3fbd1c0fe8d044ce Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 10 May 2023 17:36:16 -0700 Subject: [PATCH 034/168] Add integration test for git status --- .../20221109000000_test_schema.sql | 19 ++- crates/collab/src/db.rs | 134 +++++++++++++++++- crates/collab/src/rpc.rs | 2 +- crates/collab/src/tests/integration_tests.rs | 2 + 4 files changed, 148 insertions(+), 9 deletions(-) diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 684b6bffe0..7c6a49f179 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -86,8 +86,8 @@ CREATE TABLE "worktree_repositories" ( "project_id" INTEGER NOT NULL, "worktree_id" INTEGER NOT NULL, "work_directory_id" INTEGER NOT NULL, - "scan_id" INTEGER NOT NULL, "branch" VARCHAR, + "scan_id" INTEGER NOT NULL, "is_deleted" BOOL NOT NULL, PRIMARY KEY(project_id, worktree_id, work_directory_id), FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE, @@ -96,6 +96,23 @@ CREATE TABLE "worktree_repositories" ( CREATE INDEX "index_worktree_repositories_on_project_id" ON "worktree_repositories" ("project_id"); CREATE INDEX "index_worktree_repositories_on_project_id_and_worktree_id" ON "worktree_repositories" ("project_id", "worktree_id"); +CREATE TABLE "worktree_repository_statuses" ( + "project_id" INTEGER NOT NULL, + "worktree_id" INTEGER NOT NULL, + "work_directory_id" INTEGER NOT NULL, + "repo_path" VARCHAR NOT NULL, + "status" INTEGER NOT NULL, + "scan_id" INTEGER NOT NULL, + "is_deleted" BOOL NOT NULL, + PRIMARY KEY(project_id, worktree_id, work_directory_id, repo_path), + FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE, + FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE +); +CREATE INDEX "index_worktree_repository_statuses_on_project_id" ON "worktree_repository_statuses" ("project_id"); +CREATE INDEX "index_worktree_repository_statuses_on_project_id_and_worktree_id" ON "worktree_repository_statuses" ("project_id", "worktree_id"); +CREATE INDEX "index_worktree_repository_statuses_on_project_id_and_worktree_id_and_work_directory_id" ON "worktree_repository_statuses" ("project_id", "worktree_id", "work_directory_id"); + + CREATE TABLE "worktree_diagnostic_summaries" ( "project_id" INTEGER NOT NULL, "worktree_id" INTEGER NOT NULL, diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 217987984a..cc85d4f369 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -15,6 +15,7 @@ mod worktree; mod worktree_diagnostic_summary; mod worktree_entry; mod worktree_repository; +mod worktree_repository_statuses; use crate::executor::Executor; use crate::{Error, Result}; @@ -2397,6 +2398,74 @@ impl Database { ) .exec(&*tx) .await?; + + for repository in update.updated_repositories.iter() { + if !repository.updated_worktree_statuses.is_empty() { + worktree_repository_statuses::Entity::insert_many( + repository + .updated_worktree_statuses + .iter() + .map(|status_entry| worktree_repository_statuses::ActiveModel { + project_id: ActiveValue::set(project_id), + worktree_id: ActiveValue::set(worktree_id), + work_directory_id: ActiveValue::set( + repository.work_directory_id as i64, + ), + repo_path: ActiveValue::set(status_entry.repo_path.clone()), + status: ActiveValue::set(status_entry.status as i64), + scan_id: ActiveValue::set(update.scan_id as i64), + is_deleted: ActiveValue::set(false), + }), + ) + .on_conflict( + OnConflict::columns([ + worktree_repository_statuses::Column::ProjectId, + worktree_repository_statuses::Column::WorktreeId, + worktree_repository_statuses::Column::WorkDirectoryId, + worktree_repository_statuses::Column::RepoPath, + ]) + .update_columns([ + worktree_repository_statuses::Column::ScanId, + worktree_repository_statuses::Column::Status, + ]) + .to_owned(), + ) + .exec(&*tx) + .await?; + } + + if !repository.removed_worktree_repo_paths.is_empty() { + worktree_repository_statuses::Entity::update_many() + .filter( + worktree_repository_statuses::Column::ProjectId + .eq(project_id) + .and( + worktree_repository_statuses::Column::WorktreeId + .eq(worktree_id), + ) + .and( + worktree_repository_statuses::Column::WorkDirectoryId + .eq(repository.work_directory_id), + ) + .and( + worktree_repository_statuses::Column::RepoPath.is_in( + repository + .removed_worktree_repo_paths + .iter() + .cloned() + .collect::>(), + ), + ), + ) + .set(worktree_repository_statuses::ActiveModel { + is_deleted: ActiveValue::Set(true), + scan_id: ActiveValue::Set(update.scan_id as i64), + ..Default::default() + }) + .exec(&*tx) + .await?; + } + } } if !update.removed_repositories.is_empty() { @@ -2417,6 +2486,25 @@ impl Database { }) .exec(&*tx) .await?; + + // Flip all status entries associated with a given repository_entry + worktree_repository_statuses::Entity::update_many() + .filter( + worktree_repository_statuses::Column::ProjectId + .eq(project_id) + .and(worktree_repository_statuses::Column::WorktreeId.eq(worktree_id)) + .and( + worktree_repository_statuses::Column::WorkDirectoryId + .is_in(update.removed_repositories.iter().map(|id| *id as i64)), + ), + ) + .set(worktree_repository_statuses::ActiveModel { + is_deleted: ActiveValue::Set(true), + scan_id: ActiveValue::Set(update.scan_id as i64), + ..Default::default() + }) + .exec(&*tx) + .await?; } let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?; @@ -2647,12 +2735,44 @@ impl Database { if let Some(worktree) = worktrees.get_mut(&(db_repository_entry.worktree_id as u64)) { - worktree.repository_entries.push(proto::RepositoryEntry { - work_directory_id: db_repository_entry.work_directory_id as u64, - branch: db_repository_entry.branch, - removed_worktree_repo_paths: Default::default(), - updated_worktree_statuses: Default::default(), - }); + worktree.repository_entries.insert( + db_repository_entry.work_directory_id as u64, + proto::RepositoryEntry { + work_directory_id: db_repository_entry.work_directory_id as u64, + branch: db_repository_entry.branch, + removed_worktree_repo_paths: Default::default(), + updated_worktree_statuses: Default::default(), + }, + ); + } + } + } + + { + let mut db_status_entries = worktree_repository_statuses::Entity::find() + .filter( + Condition::all() + .add(worktree_repository_statuses::Column::ProjectId.eq(project_id)) + .add(worktree_repository_statuses::Column::IsDeleted.eq(false)), + ) + .stream(&*tx) + .await?; + + while let Some(db_status_entry) = db_status_entries.next().await { + let db_status_entry = db_status_entry?; + if let Some(worktree) = worktrees.get_mut(&(db_status_entry.worktree_id as u64)) + { + if let Some(repository_entry) = worktree + .repository_entries + .get_mut(&(db_status_entry.work_directory_id as u64)) + { + repository_entry + .updated_worktree_statuses + .push(proto::StatusEntry { + repo_path: db_status_entry.repo_path, + status: db_status_entry.status as i32, + }); + } } } } @@ -3394,7 +3514,7 @@ pub struct Worktree { pub root_name: String, pub visible: bool, pub entries: Vec, - pub repository_entries: Vec, + pub repository_entries: BTreeMap, pub diagnostic_summaries: Vec, pub scan_id: u64, pub completed_scan_id: u64, diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 23935904d3..001f3462d0 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1385,7 +1385,7 @@ async fn join_project( removed_entries: Default::default(), scan_id: worktree.scan_id, is_last_update: worktree.scan_id == worktree.completed_scan_id, - updated_repositories: worktree.repository_entries, + updated_repositories: worktree.repository_entries.into_values().collect(), removed_repositories: Default::default(), }; for update in proto::split_worktree_update(message, MAX_CHUNK_SIZE) { diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 7dd8f86b8e..aefc172268 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2820,6 +2820,8 @@ async fn test_git_status_sync( // And synchronization while joining let project_remote_c = client_c.build_remote_project(project_id, cx_c).await; + deterministic.run_until_parked(); + project_remote_c.read_with(cx_c, |project, cx| { assert_status( &Path::new(A_TXT), From c7166fde3bcb88162862411d6d9e9390fe8c17a4 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 10 May 2023 17:38:29 -0700 Subject: [PATCH 035/168] Bump protocol version --- crates/rpc/src/rpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index e51ded5969..64fbf19462 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 54; +pub const PROTOCOL_VERSION: u32 = 55; From bc5b78198a74a4dcba5dbea20a7944c0f69a2bc8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 May 2023 17:43:10 -0700 Subject: [PATCH 036/168] Define terminal settings in terminal crate --- Cargo.lock | 1 + crates/diagnostics/src/diagnostics.rs | 4 +- crates/editor/src/editor.rs | 6 +- crates/project/src/terminals.rs | 22 +-- crates/settings/src/font_size.rs | 19 +++ crates/settings/src/settings.rs | 149 +----------------- crates/terminal/Cargo.toml | 2 + crates/terminal/src/terminal.rs | 118 ++++++++++++-- crates/terminal_view/src/terminal_element.rs | 82 +++++----- crates/terminal_view/src/terminal_view.rs | 44 ++---- crates/theme_testbench/src/theme_testbench.rs | 2 +- crates/zed/src/main.rs | 12 +- crates/zed/src/zed.rs | 26 +-- 13 files changed, 208 insertions(+), 279 deletions(-) create mode 100644 crates/settings/src/font_size.rs diff --git a/Cargo.lock b/Cargo.lock index ff82c5e67b..748ec5ef49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6748,6 +6748,7 @@ dependencies = [ "ordered-float", "procinfo", "rand 0.8.5", + "schemars", "serde", "serde_derive", "settings", diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index d82c653a09..b9db439e49 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -682,7 +682,9 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { let settings = cx.global::(); let theme = &settings.theme.editor; let style = theme.diagnostic_header.clone(); - let font_size = (style.text_scale_factor * settings.buffer_font_size).round(); + let font_size = (style.text_scale_factor + * settings::font_size_for_setting(settings.buffer_font_size, cx)) + .round(); let icon_width = cx.em_width * style.icon_width_factor; let icon = if diagnostic.severity == DiagnosticSeverity::ERROR { Svg::new("icons/circle_x_mark_12.svg") diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index abea2d8f3a..abd8885d30 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7388,7 +7388,7 @@ fn build_style( let font_id = font_cache .select_font(font_family_id, &font_properties) .unwrap(); - let font_size = settings.buffer_font_size; + let font_size = settings::font_size_for_setting(settings.buffer_font_size, cx); EditorStyle { text: TextStyle { color: settings.theme.editor.text_color, @@ -7561,7 +7561,9 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend let settings = cx.global::(); let theme = &settings.theme.editor; let style = diagnostic_style(diagnostic.severity, is_valid, theme); - let font_size = (style.text_scale_factor * settings.buffer_font_size).round(); + let font_size = (style.text_scale_factor + * settings::font_size_for_setting(settings.buffer_font_size, cx)) + .round(); Flex::column() .with_children(highlighted_lines.iter().map(|(line, highlights)| { Label::new( diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 0f3092ca41..26f2215bea 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -1,10 +1,7 @@ -use std::path::PathBuf; - -use gpui::{ModelContext, ModelHandle, WeakModelHandle}; -use settings::Settings; -use terminal::{Terminal, TerminalBuilder}; - use crate::Project; +use gpui::{ModelContext, ModelHandle, WeakModelHandle}; +use std::path::PathBuf; +use terminal::{Terminal, TerminalBuilder, TerminalSettings}; pub struct Terminals { pub(crate) local_handles: Vec>, @@ -22,17 +19,14 @@ impl Project { "creating terminals as a guest is not supported yet" )); } else { - let settings = cx.global::(); - let shell = settings.terminal_shell(); - let envs = settings.terminal_env(); - let scroll = settings.terminal_scroll(); + let settings = settings::get_setting::(None, cx); let terminal = TerminalBuilder::new( working_directory.clone(), - shell, - envs, - settings.terminal_overrides.blinking.clone(), - scroll, + settings.shell.clone(), + settings.env.clone(), + Some(settings.blinking.clone()), + settings.alternate_scroll, window_id, ) .map(|builder| { diff --git a/crates/settings/src/font_size.rs b/crates/settings/src/font_size.rs new file mode 100644 index 0000000000..79abfeb550 --- /dev/null +++ b/crates/settings/src/font_size.rs @@ -0,0 +1,19 @@ +use gpui::AppContext; + +#[derive(Default)] +pub struct FontSizeDelta(pub f32); + +pub fn adjust_font_size_delta(cx: &mut AppContext, f: fn(&mut f32, cx: &mut AppContext)) { + cx.update_default_global::(|size, cx| { + f(&mut size.0, cx); + }); + cx.refresh_windows(); +} + +pub fn font_size_for_setting(size: f32, cx: &AppContext) -> f32 { + if cx.has_global::() { + size + cx.global::().0 + } else { + size + } +} diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index b1e716abb8..c476024d62 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -1,3 +1,4 @@ +mod font_size; mod keymap_file; mod settings_file; mod settings_store; @@ -22,6 +23,7 @@ use std::{borrow::Cow, collections::HashMap, num::NonZeroU32, path::Path, str, s use theme::{Theme, ThemeRegistry}; use util::ResultExt as _; +pub use font_size::{adjust_font_size_delta, font_size_for_setting}; pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; pub use settings_file::*; pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore}; @@ -35,7 +37,6 @@ pub struct Settings { pub buffer_font_family_name: String, pub buffer_font_features: fonts::Features, pub buffer_font_family: FamilyId, - pub default_buffer_font_size: f32, pub buffer_font_size: f32, pub active_pane_magnification: f32, pub cursor_blink: bool, @@ -50,8 +51,6 @@ pub struct Settings { pub git: GitSettings, pub git_overrides: GitSettings, pub copilot: CopilotSettings, - pub terminal_defaults: TerminalSettings, - pub terminal_overrides: TerminalSettings, pub language_defaults: HashMap, EditorSettings>, pub language_overrides: HashMap, EditorSettings>, pub lsp: HashMap, LspSettings>, @@ -84,7 +83,6 @@ impl Setting for Settings { buffer_font_features, buffer_font_size: defaults.buffer_font_size.unwrap(), active_pane_magnification: defaults.active_pane_magnification.unwrap(), - default_buffer_font_size: defaults.buffer_font_size.unwrap(), confirm_quit: defaults.confirm_quit.unwrap(), cursor_blink: defaults.cursor_blink.unwrap(), hover_popover_enabled: defaults.hover_popover_enabled.unwrap(), @@ -121,8 +119,6 @@ impl Setting for Settings { }, git: defaults.git.unwrap(), git_overrides: Default::default(), - terminal_defaults: defaults.terminal.clone(), - terminal_overrides: Default::default(), language_defaults: defaults.languages.clone(), language_overrides: Default::default(), lsp: defaults.lsp.clone(), @@ -332,104 +328,6 @@ pub enum Autosave { OnWindowChange, } -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -pub struct TerminalSettings { - pub shell: Option, - pub working_directory: Option, - pub font_size: Option, - pub font_family: Option, - pub line_height: Option, - pub font_features: Option, - pub env: Option>, - pub blinking: Option, - pub alternate_scroll: Option, - pub option_as_meta: Option, - pub copy_on_select: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] -#[serde(rename_all = "snake_case")] -pub enum TerminalLineHeight { - #[default] - Comfortable, - Standard, - Custom(f32), -} - -impl TerminalLineHeight { - fn value(&self) -> f32 { - match self { - TerminalLineHeight::Comfortable => 1.618, - TerminalLineHeight::Standard => 1.3, - TerminalLineHeight::Custom(line_height) => *line_height, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum TerminalBlink { - Off, - TerminalControlled, - On, -} - -impl Default for TerminalBlink { - fn default() -> Self { - TerminalBlink::TerminalControlled - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum Shell { - System, - Program(String), - WithArguments { program: String, args: Vec }, -} - -impl Default for Shell { - fn default() -> Self { - Shell::System - } -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum AlternateScroll { - On, - Off, -} - -impl Default for AlternateScroll { - fn default() -> Self { - AlternateScroll::On - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum WorkingDirectory { - CurrentProjectDirectory, - FirstProjectDirectory, - AlwaysHome, - Always { directory: String }, -} - -impl Default for WorkingDirectory { - fn default() -> Self { - Self::CurrentProjectDirectory - } -} - -impl TerminalSettings { - fn line_height(&self) -> Option { - self.line_height - .to_owned() - .map(|line_height| line_height.value()) - } -} - #[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum DockAnchor { @@ -496,8 +394,6 @@ pub struct SettingsFileContent { #[serde(flatten)] pub editor: EditorSettings, #[serde(default)] - pub terminal: TerminalSettings, - #[serde(default)] pub git: Option, #[serde(default)] #[serde(alias = "language_overrides")] @@ -575,7 +471,6 @@ impl Settings { buffer_font_features, buffer_font_size: defaults.buffer_font_size.unwrap(), active_pane_magnification: defaults.active_pane_magnification.unwrap(), - default_buffer_font_size: defaults.buffer_font_size.unwrap(), confirm_quit: defaults.confirm_quit.unwrap(), cursor_blink: defaults.cursor_blink.unwrap(), hover_popover_enabled: defaults.hover_popover_enabled.unwrap(), @@ -613,8 +508,6 @@ impl Settings { }, git: defaults.git.unwrap(), git_overrides: Default::default(), - terminal_defaults: defaults.terminal, - terminal_overrides: Default::default(), language_defaults: defaults.languages, language_overrides: Default::default(), lsp: defaults.lsp.clone(), @@ -627,7 +520,7 @@ impl Settings { } // Fill out the overrride and etc. settings from the user's settings.json - pub fn set_user_settings( + fn set_user_settings( &mut self, data: SettingsFileContent, theme_registry: &ThemeRegistry, @@ -662,7 +555,6 @@ impl Settings { &mut self.active_pane_magnification, data.active_pane_magnification, ); - merge(&mut self.default_buffer_font_size, data.buffer_font_size); merge(&mut self.cursor_blink, data.cursor_blink); merge(&mut self.confirm_quit, data.confirm_quit); merge(&mut self.hover_popover_enabled, data.hover_popover_enabled); @@ -685,9 +577,6 @@ impl Settings { } self.editor_overrides = data.editor; self.git_overrides = data.git.unwrap_or_default(); - self.terminal_defaults.font_size = data.terminal.font_size; - self.terminal_overrides.copy_on_select = data.terminal.copy_on_select; - self.terminal_overrides = data.terminal; self.language_overrides = data.languages; self.lsp = data.lsp; } @@ -799,35 +688,6 @@ impl Settings { }) } - fn terminal_setting(&self, f: F) -> R - where - F: Fn(&TerminalSettings) -> Option, - { - None.or_else(|| f(&self.terminal_overrides)) - .or_else(|| f(&self.terminal_defaults)) - .expect("missing default") - } - - pub fn terminal_line_height(&self) -> f32 { - self.terminal_setting(|terminal_setting| terminal_setting.line_height()) - } - - pub fn terminal_scroll(&self) -> AlternateScroll { - self.terminal_setting(|terminal_setting| terminal_setting.alternate_scroll.to_owned()) - } - - pub fn terminal_shell(&self) -> Shell { - self.terminal_setting(|terminal_setting| terminal_setting.shell.to_owned()) - } - - pub fn terminal_env(&self) -> HashMap { - self.terminal_setting(|terminal_setting| terminal_setting.env.to_owned()) - } - - pub fn terminal_strategy(&self) -> WorkingDirectory { - self.terminal_setting(|terminal_setting| terminal_setting.working_directory.to_owned()) - } - #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &gpui::AppContext) -> Settings { Settings { @@ -839,7 +699,6 @@ impl Settings { .unwrap(), buffer_font_size: 14., active_pane_magnification: 1., - default_buffer_font_size: 14., confirm_quit: false, cursor_blink: true, hover_popover_enabled: true, @@ -862,8 +721,6 @@ impl Settings { }, editor_overrides: Default::default(), copilot: Default::default(), - terminal_defaults: Default::default(), - terminal_overrides: Default::default(), git: Default::default(), git_overrides: Default::default(), language_defaults: Default::default(), diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index 725b102c04..a2902234c5 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -15,6 +15,7 @@ settings = { path = "../settings" } db = { path = "../db" } theme = { path = "../theme" } util = { path = "../util" } + alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "a51dbe25d67e84d6ed4261e640d3954fbdd9be45" } procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false } smallvec.workspace = true @@ -27,6 +28,7 @@ dirs = "4.0.0" shellexpand = "2.1.0" libc = "0.2" anyhow.workspace = true +schemars.workspace = true thiserror.workspace = true lazy_static.workspace = true serde.workspace = true diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 25852875c3..bf4fc46ee3 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -31,8 +31,9 @@ use mappings::mouse::{ }; use procinfo::LocalProcessInfo; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{AlternateScroll, Settings, Shell, TerminalBlink}; +use settings::Settings; use util::truncate_and_trailoff; use std::{ @@ -48,11 +49,12 @@ use std::{ use thiserror::Error; use gpui::{ + fonts, geometry::vector::{vec2f, Vector2F}, keymap_matcher::Keystroke, platform::{MouseButton, MouseMovedEvent, TouchPhase}, scene::{MouseDown, MouseDrag, MouseScrollWheel, MouseUp}, - ClipboardItem, Entity, ModelContext, Task, + AppContext, ClipboardItem, Entity, ModelContext, Task, }; use crate::mappings::{ @@ -114,6 +116,105 @@ impl EventListener for ZedListener { } } +pub fn init(cx: &mut AppContext) { + settings::register_setting::(cx); +} + +#[derive(Deserialize)] +pub struct TerminalSettings { + pub shell: Shell, + pub working_directory: WorkingDirectory, + pub font_size: Option, + pub font_family: Option, + pub line_height: TerminalLineHeight, + pub font_features: Option, + pub env: HashMap, + pub blinking: TerminalBlink, + pub alternate_scroll: AlternateScroll, + pub option_as_meta: bool, + pub copy_on_select: bool, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct TerminalSettingsContent { + pub shell: Option, + pub working_directory: Option, + pub font_size: Option, + pub font_family: Option, + pub line_height: Option, + pub font_features: Option, + pub env: Option>, + pub blinking: Option, + pub alternate_scroll: Option, + pub option_as_meta: Option, + pub copy_on_select: Option, +} + +impl settings::Setting for TerminalSettings { + const KEY: Option<&'static str> = Some("terminal"); + + type FileContent = TerminalSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &AppContext, + ) -> Self { + Self::load_via_json_merge(default_value, user_values) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] +#[serde(rename_all = "snake_case")] +pub enum TerminalLineHeight { + #[default] + Comfortable, + Standard, + Custom(f32), +} + +impl TerminalLineHeight { + pub fn value(&self) -> f32 { + match self { + TerminalLineHeight::Comfortable => 1.618, + TerminalLineHeight::Standard => 1.3, + TerminalLineHeight::Custom(line_height) => *line_height, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum TerminalBlink { + Off, + TerminalControlled, + On, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Shell { + System, + Program(String), + WithArguments { program: String, args: Vec }, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AlternateScroll { + On, + Off, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum WorkingDirectory { + CurrentProjectDirectory, + FirstProjectDirectory, + AlwaysHome, + Always { directory: String }, +} + #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct TerminalSize { pub cell_width: f32, @@ -1049,16 +1150,7 @@ impl Terminal { } pub fn mouse_up(&mut self, e: &MouseUp, origin: Vector2F, cx: &mut ModelContext) { - let settings = cx.global::(); - let copy_on_select = settings - .terminal_overrides - .copy_on_select - .unwrap_or_else(|| { - settings - .terminal_defaults - .copy_on_select - .expect("Should be set in defaults") - }); + let setting = settings::get_setting::(None, cx); let position = e.position.sub(origin); if self.mouse_mode(e.shift) { @@ -1072,7 +1164,7 @@ impl Terminal { self.pty_tx.notify(bytes); } } else { - if e.button == MouseButton::Left && copy_on_select { + if e.button == MouseButton::Left && setting.copy_on_select { self.copy(); } diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index ae2342cd97..93be64b612 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -16,7 +16,7 @@ use gpui::{ use itertools::Itertools; use language::CursorShape; use ordered_float::OrderedFloat; -use settings::Settings; +use settings::{font_size_for_setting, Settings}; use terminal::{ alacritty_terminal::{ ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, @@ -25,7 +25,7 @@ use terminal::{ term::{cell::Flags, TermMode}, }, mappings::colors::convert_color, - IndexedCell, Terminal, TerminalContent, TerminalSize, + IndexedCell, Terminal, TerminalContent, TerminalSettings, TerminalSize, }; use theme::TerminalStyle; use util::ResultExt; @@ -510,47 +510,6 @@ impl TerminalElement { scene.push_mouse_region(region); } - - ///Configures a text style from the current settings. - pub fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle { - let font_family_name = settings - .terminal_overrides - .font_family - .as_ref() - .or(settings.terminal_defaults.font_family.as_ref()) - .unwrap_or(&settings.buffer_font_family_name); - let font_features = settings - .terminal_overrides - .font_features - .as_ref() - .or(settings.terminal_defaults.font_features.as_ref()) - .unwrap_or(&settings.buffer_font_features); - - let family_id = font_cache - .load_family(&[font_family_name], &font_features) - .log_err() - .unwrap_or(settings.buffer_font_family); - - let font_size = settings - .terminal_overrides - .font_size - .or(settings.terminal_defaults.font_size) - .unwrap_or(settings.buffer_font_size); - - let font_id = font_cache - .select_font(family_id, &Default::default()) - .unwrap(); - - TextStyle { - color: settings.theme.editor.text_color, - font_family_id: family_id, - font_family_name: font_cache.family_name(family_id).unwrap(), - font_id, - font_size, - font_properties: Default::default(), - underline: Default::default(), - } - } } impl Element for TerminalElement { @@ -564,19 +523,50 @@ impl Element for TerminalElement { cx: &mut LayoutContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { let settings = cx.global::(); - let font_cache = cx.font_cache(); + let terminal_settings = settings::get_setting::(None, cx); //Setup layout information let terminal_theme = settings.theme.terminal.clone(); //TODO: Try to minimize this clone. let link_style = settings.theme.editor.link_definition; let tooltip_style = settings.theme.tooltip.clone(); - let text_style = TerminalElement::make_text_style(font_cache, settings); + let font_cache = cx.font_cache(); + let font_size = font_size_for_setting( + terminal_settings + .font_size + .unwrap_or(settings.buffer_font_size), + cx, + ); + let font_family_name = terminal_settings + .font_family + .as_ref() + .unwrap_or(&settings.buffer_font_family_name); + let font_features = terminal_settings + .font_features + .as_ref() + .unwrap_or(&settings.buffer_font_features); + let family_id = font_cache + .load_family(&[font_family_name], &font_features) + .log_err() + .unwrap_or(settings.buffer_font_family); + let font_id = font_cache + .select_font(family_id, &Default::default()) + .unwrap(); + + let text_style = TextStyle { + color: settings.theme.editor.text_color, + font_family_id: family_id, + font_family_name: font_cache.family_name(family_id).unwrap(), + font_id, + font_size, + font_properties: Default::default(), + underline: Default::default(), + }; let selection_color = settings.theme.editor.selection.selection; let match_color = settings.theme.search.match_background; let gutter; let dimensions = { - let line_height = text_style.font_size * settings.terminal_line_height(); + let line_height = text_style.font_size * terminal_settings.line_height.value(); let cell_width = font_cache.em_advance(text_style.font_id, text_style.font_size); gutter = cell_width; diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index dfb2334dc5..1615387ca2 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -2,6 +2,7 @@ mod persistence; pub mod terminal_button; pub mod terminal_element; +use crate::{persistence::TERMINAL_DB, terminal_element::TerminalElement}; use context_menu::{ContextMenu, ContextMenuItem}; use dirs::home_dir; use gpui::{ @@ -16,7 +17,6 @@ use gpui::{ }; use project::{LocalWorktree, Project}; use serde::Deserialize; -use settings::{Settings, TerminalBlink, WorkingDirectory}; use smallvec::{smallvec, SmallVec}; use smol::Timer; use std::{ @@ -30,7 +30,7 @@ use terminal::{ index::Point, term::{search::RegexSearch, TermMode}, }, - Event, Terminal, + Event, Terminal, TerminalBlink, WorkingDirectory, }; use util::ResultExt; use workspace::{ @@ -41,7 +41,7 @@ use workspace::{ Pane, ToolbarItemLocation, Workspace, WorkspaceId, }; -use crate::{persistence::TERMINAL_DB, terminal_element::TerminalElement}; +pub use terminal::TerminalSettings; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); @@ -63,6 +63,8 @@ actions!( impl_actions!(terminal, [SendText, SendKeystroke]); pub fn init(cx: &mut AppContext) { + terminal::init(cx); + cx.add_action(TerminalView::deploy); register_deserializable_item::(cx); @@ -101,9 +103,9 @@ impl TerminalView { _: &workspace::NewTerminal, cx: &mut ViewContext, ) { - let strategy = cx.global::().terminal_strategy(); - - let working_directory = get_working_directory(workspace, cx, strategy); + let strategy = settings::get_setting::(None, cx); + let working_directory = + get_working_directory(workspace, cx, strategy.working_directory.clone()); let window_id = cx.window_id(); let terminal = workspace @@ -215,10 +217,7 @@ impl TerminalView { self.terminal.update(cx, |term, cx| { term.try_keystroke( &Keystroke::parse("ctrl-cmd-space").unwrap(), - cx.global::() - .terminal_overrides - .option_as_meta - .unwrap_or(false), + settings::get_setting::(None, cx).option_as_meta, ) }); } @@ -244,16 +243,7 @@ impl TerminalView { return true; } - let setting = { - let settings = cx.global::(); - settings - .terminal_overrides - .blinking - .clone() - .unwrap_or(TerminalBlink::TerminalControlled) - }; - - match setting { + match settings::get_setting::(None, cx).blinking { //If the user requested to never blink, don't blink it. TerminalBlink::Off => true, //If the terminal is controlling it, check terminal mode @@ -346,10 +336,7 @@ impl TerminalView { self.terminal.update(cx, |term, cx| { term.try_keystroke( &keystroke, - cx.global::() - .terminal_overrides - .option_as_meta - .unwrap_or(false), + settings::get_setting::(None, cx).option_as_meta, ); }); } @@ -412,10 +399,7 @@ impl View for TerminalView { self.terminal.update(cx, |term, cx| { term.try_keystroke( &event.keystroke, - cx.global::() - .terminal_overrides - .option_as_meta - .unwrap_or(false), + settings::get_setting::(None, cx).option_as_meta, ) }) } @@ -617,7 +601,9 @@ impl Item for TerminalView { .flatten() .or_else(|| { cx.read(|cx| { - let strategy = cx.global::().terminal_strategy(); + let strategy = settings::get_setting::(None, cx) + .working_directory + .clone(); workspace .upgrade(cx) .map(|workspace| { diff --git a/crates/theme_testbench/src/theme_testbench.rs b/crates/theme_testbench/src/theme_testbench.rs index c18a580d07..125bc523b7 100644 --- a/crates/theme_testbench/src/theme_testbench.rs +++ b/crates/theme_testbench/src/theme_testbench.rs @@ -223,7 +223,7 @@ impl ThemeTestbench { let settings = cx.global::(); let font_cache = cx.font_cache(); let family_id = settings.buffer_font_family; - let font_size = settings.buffer_font_size; + let font_size = settings::font_size_for_setting(settings.buffer_font_size, cx); let font_id = font_cache .select_font(family_id, &Default::default()) .unwrap(); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 465219b8a6..33de725e1b 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -25,7 +25,7 @@ use project::Fs; use serde::{Deserialize, Serialize}; use settings::{ default_settings, handle_keymap_file_changes, handle_settings_file_changes, watch_config_file, - Settings, SettingsStore, WorkingDirectory, + Settings, SettingsStore, }; use simplelog::ConfigBuilder; use smol::process::Command; @@ -42,7 +42,7 @@ use std::{ thread, time::Duration, }; -use terminal_view::{get_working_directory, TerminalView}; +use terminal_view::{get_working_directory, TerminalSettings, TerminalView}; use util::http::{self, HttpClient}; use welcome::{show_welcome_experience, FIRST_OPEN}; @@ -722,13 +722,9 @@ pub fn dock_default_item_factory( workspace: &mut Workspace, cx: &mut ViewContext, ) -> Option> { - let strategy = cx - .global::() - .terminal_overrides + let strategy = settings::get_setting::(None, cx) .working_directory - .clone() - .unwrap_or(WorkingDirectory::CurrentProjectDirectory); - + .clone(); let working_directory = get_working_directory(workspace, cx, strategy); let window_id = cx.window_id(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index e6322ad843..1398369e75 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -29,7 +29,7 @@ use project_panel::ProjectPanel; use search::{BufferSearchBar, ProjectSearchBar}; use serde::Deserialize; use serde_json::to_string_pretty; -use settings::{Settings, DEFAULT_SETTINGS_ASSET_PATH}; +use settings::{adjust_font_size_delta, Settings, DEFAULT_SETTINGS_ASSET_PATH}; use std::{borrow::Cow, str, sync::Arc}; use terminal_view::terminal_button::TerminalButton; use util::{channel::ReleaseChannel, paths, ResultExt}; @@ -117,29 +117,17 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { cx.add_global_action(quit); cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| { - cx.update_global::(|settings, cx| { - settings.buffer_font_size = (settings.buffer_font_size + 1.0).max(MIN_FONT_SIZE); - if let Some(terminal_font_size) = settings.terminal_overrides.font_size.as_mut() { - *terminal_font_size = (*terminal_font_size + 1.0).max(MIN_FONT_SIZE); - } - cx.refresh_windows(); - }); + adjust_font_size_delta(cx, |size, _| *size += 1.0) }); cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| { - cx.update_global::(|settings, cx| { - settings.buffer_font_size = (settings.buffer_font_size - 1.0).max(MIN_FONT_SIZE); - if let Some(terminal_font_size) = settings.terminal_overrides.font_size.as_mut() { - *terminal_font_size = (*terminal_font_size - 1.0).max(MIN_FONT_SIZE); + adjust_font_size_delta(cx, |size, cx| { + if cx.global::().buffer_font_size + *size > MIN_FONT_SIZE { + *size -= 1.0; } - cx.refresh_windows(); - }); + }) }); cx.add_global_action(move |_: &ResetBufferFontSize, cx| { - cx.update_global::(|settings, cx| { - settings.buffer_font_size = settings.default_buffer_font_size; - settings.terminal_overrides.font_size = settings.terminal_defaults.font_size; - cx.refresh_windows(); - }); + adjust_font_size_delta(cx, |size, _| *size = 0.0) }); cx.add_global_action(move |_: &install_cli::Install, cx| { cx.spawn(|cx| async move { From 18becabfa593ec2da7735c6c7edec75ff18c6445 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 10 May 2023 17:50:30 -0700 Subject: [PATCH 037/168] Add postgres migration --- ...20230511004019_add_repository_statuses.sql | 15 ++++++++++++ .../src/db/worktree_repository_statuses.rs | 23 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 crates/collab/migrations/20230511004019_add_repository_statuses.sql create mode 100644 crates/collab/src/db/worktree_repository_statuses.rs diff --git a/crates/collab/migrations/20230511004019_add_repository_statuses.sql b/crates/collab/migrations/20230511004019_add_repository_statuses.sql new file mode 100644 index 0000000000..862561c686 --- /dev/null +++ b/crates/collab/migrations/20230511004019_add_repository_statuses.sql @@ -0,0 +1,15 @@ +CREATE TABLE "worktree_repository_statuses" ( + "project_id" INTEGER NOT NULL, + "worktree_id" INT8 NOT NULL, + "work_directory_id" INT8 NOT NULL, + "repo_path" VARCHAR NOT NULL, + "status" INT8 NOT NULL, + "scan_id" INT8 NOT NULL, + "is_deleted" BOOL NOT NULL, + PRIMARY KEY(project_id, worktree_id, work_directory_id, repo_path), + FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE, + FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE +); +CREATE INDEX "index_wt_repos_statuses_on_project_id" ON "worktree_repository_statuses" ("project_id"); +CREATE INDEX "index_wt_repos_statuses_on_project_id_and_wt_id" ON "worktree_repository_statuses" ("project_id", "worktree_id"); +CREATE INDEX "index_wt_repos_statuses_on_project_id_and_wt_id_and_wd_id" ON "worktree_repository_statuses" ("project_id", "worktree_id", "work_directory_id"); diff --git a/crates/collab/src/db/worktree_repository_statuses.rs b/crates/collab/src/db/worktree_repository_statuses.rs new file mode 100644 index 0000000000..fc15efc816 --- /dev/null +++ b/crates/collab/src/db/worktree_repository_statuses.rs @@ -0,0 +1,23 @@ +use super::ProjectId; +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "worktree_repository_statuses")] +pub struct Model { + #[sea_orm(primary_key)] + pub project_id: ProjectId, + #[sea_orm(primary_key)] + pub worktree_id: i64, + #[sea_orm(primary_key)] + pub work_directory_id: i64, + #[sea_orm(primary_key)] + pub repo_path: String, + pub status: i64, + pub scan_id: i64, + pub is_deleted: bool, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} From f55ca7ae3c65d18581c36ba4515ad6f725e88477 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 10 May 2023 17:52:23 -0700 Subject: [PATCH 038/168] Fix incorrect import --- crates/fs/src/fs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 09ddce2ffa..3285eb328a 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -7,7 +7,7 @@ use git2::Repository as LibGitRepository; use lazy_static::lazy_static; use parking_lot::Mutex; use regex::Regex; -use repository::{GitFileStatus, GitRepository}; +use repository::GitRepository; use rope::Rope; use smol::io::{AsyncReadExt, AsyncWriteExt}; use std::borrow::Cow; @@ -27,7 +27,7 @@ use util::ResultExt; #[cfg(any(test, feature = "test-support"))] use collections::{btree_map, BTreeMap}; #[cfg(any(test, feature = "test-support"))] -use repository::FakeGitRepositoryState; +use repository::{FakeGitRepositoryState, GitFileStatus}; #[cfg(any(test, feature = "test-support"))] use std::sync::Weak; From 9800a149a61480cd3464acc373c3e48a728e465b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 10 May 2023 17:59:33 -0700 Subject: [PATCH 039/168] Remove some external context from git status test --- crates/project/src/worktree.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 0d4a02775d..a970067230 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3556,6 +3556,7 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry { mod tests { use super::*; use fs::{FakeFs, RealFs}; + use git2::Signature; use gpui::{executor::Deterministic, TestAppContext}; use pretty_assertions::assert_eq; use rand::prelude::*; @@ -3894,7 +3895,7 @@ mod tests { #[track_caller] fn git_commit(msg: &'static str, repo: &git2::Repository) { - let signature = repo.signature().unwrap(); + let signature = Signature::now("test", "test@zed.dev").unwrap(); let oid = repo.index().unwrap().write_tree().unwrap(); let tree = repo.find_tree(oid).unwrap(); if let Some(head) = repo.head().ok() { From fca3bb3b93a38119771e04272d67c60f3e062783 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 10 May 2023 19:21:27 -0700 Subject: [PATCH 040/168] Add randomized test for git statuses --- crates/collab/src/db.rs | 39 +++ .../src/tests/randomized_integration_tests.rs | 259 +++++++++++------- crates/fs/src/repository.rs | 3 +- 3 files changed, 207 insertions(+), 94 deletions(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index cc85d4f369..e881121758 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -1576,6 +1576,45 @@ impl Database { } } + // Repository Status Entries + for repository in worktree.updated_repositories.iter_mut() { + let repository_status_entry_filter = + if let Some(rejoined_worktree) = rejoined_worktree { + worktree_repository_statuses::Column::ScanId + .gt(rejoined_worktree.scan_id) + } else { + worktree_repository_statuses::Column::IsDeleted.eq(false) + }; + + let mut db_repository_statuses = + worktree_repository_statuses::Entity::find() + .filter( + Condition::all() + .add( + worktree_repository_statuses::Column::WorktreeId + .eq(worktree.id), + ) + .add( + worktree_repository_statuses::Column::WorkDirectoryId + .eq(repository.work_directory_id), + ) + .add(repository_status_entry_filter), + ) + .stream(&*tx) + .await?; + + while let Some(db_status_entry) = db_repository_statuses.next().await { + let db_status_entry = db_status_entry?; + if db_status_entry.is_deleted { + repository.removed_worktree_repo_paths.push(db_status_entry.repo_path); + } else { + repository.updated_worktree_statuses.push(proto::StatusEntry { + repo_path: db_status_entry.repo_path, status: db_status_entry.status as i32 + }); + } + } + } + worktrees.push(worktree); } diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index c4326be101..d5ed47675a 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -8,7 +8,7 @@ use call::ActiveCall; use client::RECEIVE_TIMEOUT; use collections::BTreeMap; use editor::Bias; -use fs::{FakeFs, Fs as _}; +use fs::{repository::GitFileStatus, FakeFs, Fs as _}; use futures::StreamExt as _; use gpui::{executor::Deterministic, ModelHandle, Task, TestAppContext}; use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf16}; @@ -32,6 +32,7 @@ use std::{ }, }; use util::ResultExt; +use pretty_assertions::assert_eq; lazy_static::lazy_static! { static ref PLAN_LOAD_PATH: Option = path_env_var("LOAD_PLAN"); @@ -763,53 +764,81 @@ async fn apply_client_operation( } } - ClientOperation::WriteGitIndex { - repo_path, - contents, - } => { - if !client.fs.directories().contains(&repo_path) { - return Err(TestError::Inapplicable); - } - - log::info!( - "{}: writing git index for repo {:?}: {:?}", - client.username, + ClientOperation::GitOperation { operation } => match operation { + GitOperation::WriteGitIndex { repo_path, - contents - ); + contents, + } => { + if !client.fs.directories().contains(&repo_path) { + return Err(TestError::Inapplicable); + } - let dot_git_dir = repo_path.join(".git"); - let contents = contents - .iter() - .map(|(path, contents)| (path.as_path(), contents.clone())) - .collect::>(); - if client.fs.metadata(&dot_git_dir).await?.is_none() { - client.fs.create_dir(&dot_git_dir).await?; + log::info!( + "{}: writing git index for repo {:?}: {:?}", + client.username, + repo_path, + contents + ); + + let dot_git_dir = repo_path.join(".git"); + let contents = contents + .iter() + .map(|(path, contents)| (path.as_path(), contents.clone())) + .collect::>(); + if client.fs.metadata(&dot_git_dir).await?.is_none() { + client.fs.create_dir(&dot_git_dir).await?; + } + client.fs.set_index_for_repo(&dot_git_dir, &contents).await; } - client.fs.set_index_for_repo(&dot_git_dir, &contents).await; - } - - ClientOperation::WriteGitBranch { - repo_path, - new_branch, - } => { - if !client.fs.directories().contains(&repo_path) { - return Err(TestError::Inapplicable); - } - - log::info!( - "{}: writing git branch for repo {:?}: {:?}", - client.username, + GitOperation::WriteGitBranch { repo_path, - new_branch - ); + new_branch, + } => { + if !client.fs.directories().contains(&repo_path) { + return Err(TestError::Inapplicable); + } - let dot_git_dir = repo_path.join(".git"); - if client.fs.metadata(&dot_git_dir).await?.is_none() { - client.fs.create_dir(&dot_git_dir).await?; + log::info!( + "{}: writing git branch for repo {:?}: {:?}", + client.username, + repo_path, + new_branch + ); + + let dot_git_dir = repo_path.join(".git"); + if client.fs.metadata(&dot_git_dir).await?.is_none() { + client.fs.create_dir(&dot_git_dir).await?; + } + client.fs.set_branch_name(&dot_git_dir, new_branch).await; } - client.fs.set_branch_name(&dot_git_dir, new_branch).await; - } + GitOperation::WriteGitStatuses { + repo_path, + statuses, + } => { + if !client.fs.directories().contains(&repo_path) { + return Err(TestError::Inapplicable); + } + + log::info!( + "{}: writing git statuses for repo {:?}: {:?}", + client.username, + repo_path, + statuses + ); + + let dot_git_dir = repo_path.join(".git"); + + let statuses = statuses.iter() + .map(|(path, val)| (path.as_path(), val.clone())) + .collect::>(); + + if client.fs.metadata(&dot_git_dir).await?.is_none() { + client.fs.create_dir(&dot_git_dir).await?; + } + + client.fs.set_status_for_repo(&dot_git_dir, statuses.as_slice()).await; + }, + }, } Ok(()) } @@ -1178,6 +1207,13 @@ enum ClientOperation { is_dir: bool, content: String, }, + GitOperation { + operation: GitOperation, + }, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +enum GitOperation { WriteGitIndex { repo_path: PathBuf, contents: Vec<(PathBuf, String)>, @@ -1186,6 +1222,10 @@ enum ClientOperation { repo_path: PathBuf, new_branch: Option, }, + WriteGitStatuses { + repo_path: PathBuf, + statuses: Vec<(PathBuf, GitFileStatus)>, + }, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -1698,57 +1738,10 @@ impl TestPlan { } } - // Update a git index - 91..=93 => { - let repo_path = client - .fs - .directories() - .into_iter() - .choose(&mut self.rng) - .unwrap() - .clone(); - - let mut file_paths = client - .fs - .files() - .into_iter() - .filter(|path| path.starts_with(&repo_path)) - .collect::>(); - let count = self.rng.gen_range(0..=file_paths.len()); - file_paths.shuffle(&mut self.rng); - file_paths.truncate(count); - - let mut contents = Vec::new(); - for abs_child_file_path in &file_paths { - let child_file_path = abs_child_file_path - .strip_prefix(&repo_path) - .unwrap() - .to_path_buf(); - let new_base = Alphanumeric.sample_string(&mut self.rng, 16); - contents.push((child_file_path, new_base)); - } - - break ClientOperation::WriteGitIndex { - repo_path, - contents, - }; - } - - // Update a git branch - 94..=95 => { - let repo_path = client - .fs - .directories() - .choose(&mut self.rng) - .unwrap() - .clone(); - - let new_branch = (self.rng.gen_range(0..10) > 3) - .then(|| Alphanumeric.sample_string(&mut self.rng, 8)); - - break ClientOperation::WriteGitBranch { - repo_path, - new_branch, + // Update a git related action + 91..=95 => { + break ClientOperation::GitOperation { + operation: self.generate_git_operation(client), }; } @@ -1786,6 +1779,86 @@ impl TestPlan { }) } + fn generate_git_operation(&mut self, client: &TestClient) -> GitOperation { + fn generate_file_paths( + repo_path: &Path, + rng: &mut StdRng, + client: &TestClient, + ) -> Vec { + let mut paths = client + .fs + .files() + .into_iter() + .filter(|path| path.starts_with(repo_path)) + .collect::>(); + + let count = rng.gen_range(0..=paths.len()); + paths.shuffle(rng); + paths.truncate(count); + + paths + .iter() + .map(|path| path.strip_prefix(repo_path).unwrap().to_path_buf()) + .collect::>() + } + + let repo_path = client + .fs + .directories() + .choose(&mut self.rng) + .unwrap() + .clone(); + + match self.rng.gen_range(0..100_u32) { + 0..=25 => { + let file_paths = generate_file_paths(&repo_path, &mut self.rng, client); + + let contents = file_paths + .into_iter() + .map(|path| (path, Alphanumeric.sample_string(&mut self.rng, 16))) + .collect(); + + GitOperation::WriteGitIndex { + repo_path, + contents, + } + } + 26..=63 => { + let new_branch = (self.rng.gen_range(0..10) > 3) + .then(|| Alphanumeric.sample_string(&mut self.rng, 8)); + + GitOperation::WriteGitBranch { + repo_path, + new_branch, + } + } + 64..=100 => { + let file_paths = generate_file_paths(&repo_path, &mut self.rng, client); + + let statuses = file_paths + .into_iter() + .map(|paths| { + ( + paths, + match self.rng.gen_range(0..3_u32) { + 0 => GitFileStatus::Added, + 1 => GitFileStatus::Modified, + 2 => GitFileStatus::Conflict, + _ => unreachable!(), + }, + ) + }) + .collect::>(); + + GitOperation::WriteGitStatuses { + repo_path, + statuses, + } + } + _ => unreachable!(), + } + } + fn next_root_dir_name(&mut self, user_id: UserId) -> String { let user_ix = self .users diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 90b3761677..6bf0a43230 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -1,6 +1,7 @@ use anyhow::Result; use collections::HashMap; use parking_lot::Mutex; +use serde_derive::{Serialize, Deserialize}; use std::{ ffi::OsStr, os::unix::prelude::OsStrExt, @@ -183,7 +184,7 @@ fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum GitFileStatus { Added, Modified, From f5c633e80cc22583b7e72369f29388982170f19d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 10 May 2023 19:54:02 -0700 Subject: [PATCH 041/168] Fixed bug in status deletion marking --- crates/collab/src/db.rs | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index e881121758..cac84a0ccc 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -2472,38 +2472,6 @@ impl Database { .exec(&*tx) .await?; } - - if !repository.removed_worktree_repo_paths.is_empty() { - worktree_repository_statuses::Entity::update_many() - .filter( - worktree_repository_statuses::Column::ProjectId - .eq(project_id) - .and( - worktree_repository_statuses::Column::WorktreeId - .eq(worktree_id), - ) - .and( - worktree_repository_statuses::Column::WorkDirectoryId - .eq(repository.work_directory_id), - ) - .and( - worktree_repository_statuses::Column::RepoPath.is_in( - repository - .removed_worktree_repo_paths - .iter() - .cloned() - .collect::>(), - ), - ), - ) - .set(worktree_repository_statuses::ActiveModel { - is_deleted: ActiveValue::Set(true), - scan_id: ActiveValue::Set(update.scan_id as i64), - ..Default::default() - }) - .exec(&*tx) - .await?; - } } } From adfbbf21b2267eef6e7f6deeb39731e8e7133e27 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 10 May 2023 20:09:37 -0700 Subject: [PATCH 042/168] fmt --- crates/collab/src/db.rs | 13 +++++++++---- .../src/tests/randomized_integration_tests.rs | 12 ++++++++---- crates/fs/src/repository.rs | 2 +- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index cac84a0ccc..b95bb49b4e 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -1606,11 +1606,16 @@ impl Database { while let Some(db_status_entry) = db_repository_statuses.next().await { let db_status_entry = db_status_entry?; if db_status_entry.is_deleted { - repository.removed_worktree_repo_paths.push(db_status_entry.repo_path); + repository + .removed_worktree_repo_paths + .push(db_status_entry.repo_path); } else { - repository.updated_worktree_statuses.push(proto::StatusEntry { - repo_path: db_status_entry.repo_path, status: db_status_entry.status as i32 - }); + repository + .updated_worktree_statuses + .push(proto::StatusEntry { + repo_path: db_status_entry.repo_path, + status: db_status_entry.status as i32, + }); } } } diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index d5ed47675a..fe4b6190ed 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -14,6 +14,7 @@ use gpui::{executor::Deterministic, ModelHandle, Task, TestAppContext}; use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf16}; use lsp::FakeLanguageServer; use parking_lot::Mutex; +use pretty_assertions::assert_eq; use project::{search::SearchQuery, Project, ProjectPath}; use rand::{ distributions::{Alphanumeric, DistString}, @@ -32,7 +33,6 @@ use std::{ }, }; use util::ResultExt; -use pretty_assertions::assert_eq; lazy_static::lazy_static! { static ref PLAN_LOAD_PATH: Option = path_env_var("LOAD_PLAN"); @@ -828,7 +828,8 @@ async fn apply_client_operation( let dot_git_dir = repo_path.join(".git"); - let statuses = statuses.iter() + let statuses = statuses + .iter() .map(|(path, val)| (path.as_path(), val.clone())) .collect::>(); @@ -836,8 +837,11 @@ async fn apply_client_operation( client.fs.create_dir(&dot_git_dir).await?; } - client.fs.set_status_for_repo(&dot_git_dir, statuses.as_slice()).await; - }, + client + .fs + .set_status_for_repo(&dot_git_dir, statuses.as_slice()) + .await; + } }, } Ok(()) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 6bf0a43230..2fe31f5569 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -1,7 +1,7 @@ use anyhow::Result; use collections::HashMap; use parking_lot::Mutex; -use serde_derive::{Serialize, Deserialize}; +use serde_derive::{Deserialize, Serialize}; use std::{ ffi::OsStr, os::unix::prelude::OsStrExt, From 0f34af50a82e9c3c77ddc0c3509f6ebdc1ddf032 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 10 May 2023 23:37:02 -0400 Subject: [PATCH 043/168] Use path list generated during entry reload of a refresh request --- crates/project/src/worktree.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 9d03169072..895eafac30 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2523,7 +2523,15 @@ impl BackgroundScanner { } async fn process_refresh_request(&self, paths: Vec, barrier: barrier::Sender) -> bool { - self.reload_entries_for_paths(paths, None).await; + if let Some(mut paths) = self.reload_entries_for_paths(paths, None).await { + paths.sort_unstable(); + util::extend_sorted( + &mut self.prev_state.lock().event_paths, + paths, + usize::MAX, + Ord::cmp, + ); + } self.send_status_update(false, Some(barrier)) } From 0ab94551f41869fd613d5de4e32b913ae398456b Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Thu, 11 May 2023 11:37:34 -0400 Subject: [PATCH 044/168] Revert "More keybindings in macOs modals with buttons" This reverts commit 1398a1206299cbaf5e14b9de30d2fbfe83f04334. --- crates/gpui/src/platform/mac/window.rs | 39 ++------------------------ 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index bcff08d005..d96f9bc4ae 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -699,31 +699,6 @@ impl platform::Window for Window { msg: &str, answers: &[&str], ) -> oneshot::Receiver { - // macOs applies overrides to modal window buttons after they are added. - // Two most important for this logic are: - // * Buttons with "Cancel" title will be displayed as the last buttons in the modal - // * Last button added to the modal via `addButtonWithTitle` stays focused - // * Focused buttons react on "space"/" " keypresses - // * Usage of `keyEquivalent`, `makeFirstResponder` or `setInitialFirstResponder` does not change the focus - // - // See also https://developer.apple.com/documentation/appkit/nsalert/1524532-addbuttonwithtitle#discussion - // ``` - // By default, the first button has a key equivalent of Return, - // any button with a title of “Cancel” has a key equivalent of Escape, - // and any button with the title “Don’t Save” has a key equivalent of Command-D (but only if it’s not the first button). - // ``` - // - // To avoid situations when the last element added is "Cancel" and it gets the focus - // (hence stealing both ESC and Space shortcuts), we find and add one non-Cancel button - // last, so it gets focus and a Space shortcut. - // This way, "Save this file? Yes/No/Cancel"-ish modals will get all three buttons mapped with a key. - let latest_non_cancel_label = answers - .iter() - .enumerate() - .rev() - .find(|(_, &label)| label != "Cancel") - .filter(|&(label_index, _)| label_index > 0); - unsafe { let alert: id = msg_send![class!(NSAlert), alloc]; let alert: id = msg_send![alert, init]; @@ -734,20 +709,10 @@ impl platform::Window for Window { }; let _: () = msg_send![alert, setAlertStyle: alert_style]; let _: () = msg_send![alert, setMessageText: ns_string(msg)]; - - for (ix, answer) in answers - .iter() - .enumerate() - .filter(|&(ix, _)| Some(ix) != latest_non_cancel_label.map(|(ix, _)| ix)) - { + for (ix, answer) in answers.iter().enumerate() { let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)]; let _: () = msg_send![button, setTag: ix as NSInteger]; } - if let Some((ix, answer)) = latest_non_cancel_label { - let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)]; - let _: () = msg_send![button, setTag: ix as NSInteger]; - } - let (done_tx, done_rx) = oneshot::channel(); let done_tx = Cell::new(Some(done_tx)); let block = ConcreteBlock::new(move |answer: NSInteger| { @@ -755,7 +720,7 @@ impl platform::Window for Window { let _ = postage::sink::Sink::try_send(&mut done_tx, answer.try_into().unwrap()); } }); - + let block = block.copy(); let native_window = self.0.borrow().native_window; self.0 .borrow() From 191ac86f0926d317ff77498558ecb95e98e4da9f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 11 May 2023 09:24:36 -0700 Subject: [PATCH 045/168] Remove the CORRECT, overly agressive deletion codepath --- crates/collab/src/db.rs | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index b95bb49b4e..69b561c054 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -2477,6 +2477,26 @@ impl Database { .exec(&*tx) .await?; } + if !repository.removed_worktree_repo_paths.is_empty() { + worktree_repository_statuses::Entity::update_many() + .filter( + worktree_repository_statuses::Column::ProjectId + .eq(project_id) + .and(worktree_repository_statuses::Column::WorktreeId.eq(worktree_id)) + .and(worktree_repository_statuses::Column::WorkDirectoryId.eq(repository.work_directory_id as i64)) + .and( + worktree_repository_statuses::Column::RepoPath + .is_in(repository.removed_worktree_repo_paths.iter().map(String::as_str)), + ), + ) + .set(worktree_repository_statuses::ActiveModel { + is_deleted: ActiveValue::Set(true), + scan_id: ActiveValue::Set(update.scan_id as i64), + ..Default::default() + }) + .exec(&*tx) + .await?; + } } } @@ -2498,25 +2518,6 @@ impl Database { }) .exec(&*tx) .await?; - - // Flip all status entries associated with a given repository_entry - worktree_repository_statuses::Entity::update_many() - .filter( - worktree_repository_statuses::Column::ProjectId - .eq(project_id) - .and(worktree_repository_statuses::Column::WorktreeId.eq(worktree_id)) - .and( - worktree_repository_statuses::Column::WorkDirectoryId - .is_in(update.removed_repositories.iter().map(|id| *id as i64)), - ), - ) - .set(worktree_repository_statuses::ActiveModel { - is_deleted: ActiveValue::Set(true), - scan_id: ActiveValue::Set(update.scan_id as i64), - ..Default::default() - }) - .exec(&*tx) - .await?; } let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?; From 3550110e577c25f7194090618265cec118ff3610 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 May 2023 09:43:13 -0700 Subject: [PATCH 046/168] ci: clear the target dir if it gets too big --- .github/workflows/ci.yml | 6 ++++++ script/clear-target-dir-if-larger-than | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100755 script/clear-target-dir-if-larger-than diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b7cb97efa..27af9e1164 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,6 +62,9 @@ jobs: clean: false submodules: 'recursive' + - name: Limit target directory size + run: script/clear-target-dir-if-larger-than 70 + - name: Run check run: cargo check --workspace @@ -110,6 +113,9 @@ jobs: clean: false submodules: 'recursive' + - name: Limit target directory size + run: script/clear-target-dir-if-larger-than 70 + - name: Determine version and release channel if: ${{ startsWith(github.ref, 'refs/tags/v') }} run: | diff --git a/script/clear-target-dir-if-larger-than b/script/clear-target-dir-if-larger-than new file mode 100755 index 0000000000..59c07f77f7 --- /dev/null +++ b/script/clear-target-dir-if-larger-than @@ -0,0 +1,20 @@ +#!/bin/bash + +set -eu + +if [[ $# < 1 ]]; then + echo "usage: $0 " + exit 1 +fi + +max_size_gb=$1 + +current_size=$(du -s target | cut -f1) +current_size_gb=$(expr ${current_size} / 1024 / 1024) + +echo "target directory size: ${current_size_gb}gb. max size: ${max_size_gb}gb" + +if [[ ${current_size_gb} -gt ${max_size_gb} ]]; then + echo "clearing target directory" + rm -rf target +fi From 5accf7cf4e5c745ca4cff0bf40cc94a37aecd472 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 11 May 2023 10:21:25 -0700 Subject: [PATCH 047/168] Update is_deleted when sending new repositories --- crates/collab/src/db.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 69b561c054..f5175a16a9 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -2471,12 +2471,14 @@ impl Database { .update_columns([ worktree_repository_statuses::Column::ScanId, worktree_repository_statuses::Column::Status, + worktree_repository_statuses::Column::IsDeleted, ]) .to_owned(), ) .exec(&*tx) .await?; } + if !repository.removed_worktree_repo_paths.is_empty() { worktree_repository_statuses::Entity::update_many() .filter( From f12dffa60c75404f32870cf555d57e3f7288ed14 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 May 2023 20:59:10 +0300 Subject: [PATCH 048/168] Reintroduce more accesible modal keybindings Brings commit 475fc409232775d83797215870256fd5772e299f back --- crates/gpui/src/platform/mac/window.rs | 37 +++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index d96f9bc4ae..50fcec52ec 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -699,6 +699,31 @@ impl platform::Window for Window { msg: &str, answers: &[&str], ) -> oneshot::Receiver { + // macOs applies overrides to modal window buttons after they are added. + // Two most important for this logic are: + // * Buttons with "Cancel" title will be displayed as the last buttons in the modal + // * Last button added to the modal via `addButtonWithTitle` stays focused + // * Focused buttons react on "space"/" " keypresses + // * Usage of `keyEquivalent`, `makeFirstResponder` or `setInitialFirstResponder` does not change the focus + // + // See also https://developer.apple.com/documentation/appkit/nsalert/1524532-addbuttonwithtitle#discussion + // ``` + // By default, the first button has a key equivalent of Return, + // any button with a title of “Cancel” has a key equivalent of Escape, + // and any button with the title “Don’t Save” has a key equivalent of Command-D (but only if it’s not the first button). + // ``` + // + // To avoid situations when the last element added is "Cancel" and it gets the focus + // (hence stealing both ESC and Space shortcuts), we find and add one non-Cancel button + // last, so it gets focus and a Space shortcut. + // This way, "Save this file? Yes/No/Cancel"-ish modals will get all three buttons mapped with a key. + let latest_non_cancel_label = answers + .iter() + .enumerate() + .rev() + .find(|(_, &label)| label != "Cancel") + .filter(|&(label_index, _)| label_index > 0); + unsafe { let alert: id = msg_send![class!(NSAlert), alloc]; let alert: id = msg_send![alert, init]; @@ -709,10 +734,20 @@ impl platform::Window for Window { }; let _: () = msg_send![alert, setAlertStyle: alert_style]; let _: () = msg_send![alert, setMessageText: ns_string(msg)]; - for (ix, answer) in answers.iter().enumerate() { + + for (ix, answer) in answers + .iter() + .enumerate() + .filter(|&(ix, _)| Some(ix) != latest_non_cancel_label.map(|(ix, _)| ix)) + { let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)]; let _: () = msg_send![button, setTag: ix as NSInteger]; } + if let Some((ix, answer)) = latest_non_cancel_label { + let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)]; + let _: () = msg_send![button, setTag: ix as NSInteger]; + } + let (done_tx, done_rx) = oneshot::channel(); let done_tx = Cell::new(Some(done_tx)); let block = ConcreteBlock::new(move |answer: NSInteger| { From 5b2ee63f80d8b83583713b65e8bfe2ee5f0589b2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 11 May 2023 12:01:42 -0700 Subject: [PATCH 049/168] Added status trickle up --- Cargo.lock | 7 ++++ crates/collab/src/tests/integration_tests.rs | 2 +- crates/fs/src/repository.rs | 2 +- crates/project/src/worktree.rs | 28 ++++++++++++- crates/project_panel/src/project_panel.rs | 10 +++-- crates/sum_tree/src/tree_map.rs | 44 +++++++++++++++++++- crates/util/Cargo.toml | 1 + crates/util/src/util.rs | 2 + 8 files changed, 88 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0190b4d8f5..81e4a4e025 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6537,6 +6537,12 @@ dependencies = [ "winx", ] +[[package]] +name = "take-until" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb" + [[package]] name = "target-lexicon" version = "0.12.5" @@ -7596,6 +7602,7 @@ dependencies = [ "serde", "serde_json", "smol", + "take-until", "tempdir", "url", ] diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index aefc172268..47455c0a70 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2760,7 +2760,7 @@ async fn test_git_status_sync( let worktree = worktrees[0].clone(); let snapshot = worktree.read(cx).snapshot(); let root_entry = snapshot.root_git_entry().unwrap(); - assert_eq!(root_entry.status_for(&snapshot, file), status); + assert_eq!(root_entry.status_for_file(&snapshot, file), status); } // Smoke test status reading diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 2fe31f5569..13f55b9c94 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -184,7 +184,7 @@ fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum GitFileStatus { Added, Modified, diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index a970067230..07302b4e2e 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -55,7 +55,7 @@ use std::{ time::{Duration, SystemTime}, }; use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet}; -use util::{paths::HOME, ResultExt, TryFutureExt}; +use util::{paths::HOME, ResultExt, TakeUntilExt, TryFutureExt}; #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] pub struct WorktreeId(usize); @@ -173,13 +173,37 @@ impl RepositoryEntry { self.work_directory.contains(snapshot, path) } - pub fn status_for(&self, snapshot: &Snapshot, path: &Path) -> Option { + pub fn status_for_file(&self, snapshot: &Snapshot, path: &Path) -> Option { self.work_directory .relativize(snapshot, path) .and_then(|repo_path| self.worktree_statuses.get(&repo_path)) .cloned() } + pub fn status_for_path(&self, snapshot: &Snapshot, path: &Path) -> Option { + self.work_directory + .relativize(snapshot, path) + .and_then(|repo_path| { + self.worktree_statuses + .get_from_while(&repo_path, |repo_path, key, _| key.starts_with(repo_path)) + .map(|(_, status)| status) + // Short circut once we've found the highest level + .take_until(|status| status == &&GitFileStatus::Conflict) + .reduce( + |status_first, status_second| match (status_first, status_second) { + (GitFileStatus::Conflict, _) | (_, GitFileStatus::Conflict) => { + &GitFileStatus::Conflict + } + (GitFileStatus::Added, _) | (_, GitFileStatus::Added) => { + &GitFileStatus::Added + } + _ => &GitFileStatus::Modified, + }, + ) + .copied() + }) + } + pub fn build_update(&self, other: &Self) -> proto::RepositoryEntry { let mut updated_statuses: Vec = Vec::new(); let mut removed_statuses: Vec = Vec::new(); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 49741ea49f..1066875022 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1013,9 +1013,13 @@ impl ProjectPanel { let entry_range = range.start.saturating_sub(ix)..end_ix - ix; for entry in &visible_worktree_entries[entry_range] { let path = &entry.path; - let status = snapshot - .repo_for(path) - .and_then(|entry| entry.status_for(&snapshot, path)); + let status = (entry.path.parent().is_some() && !entry.is_ignored) + .then(|| { + snapshot + .repo_for(path) + .and_then(|entry| entry.status_for_path(&snapshot, path)) + }) + .flatten(); let mut details = EntryDetails { filename: entry diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index 3942d00b29..e59b05f00f 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, fmt::Debug}; +use std::{cmp::Ordering, fmt::Debug, iter}; use crate::{Bias, Dimension, Edit, Item, KeyedItem, SeekTarget, SumTree, Summary}; @@ -111,6 +111,26 @@ impl TreeMap { self.0 = new_tree; } + + pub fn get_from_while<'tree, F>(&'tree self, from: &'tree K, mut f: F) -> impl Iterator + '_ + where + F: FnMut(&K, &K, &V) -> bool + 'tree, + { + let mut cursor = self.0.cursor::>(); + let from_key = MapKeyRef(Some(from)); + cursor.seek(&from_key, Bias::Left, &()); + + iter::from_fn(move || { + let result = cursor.item().and_then(|item| { + (f(from, &item.key, &item.value)) + .then(|| (&item.key, &item.value)) + }); + cursor.next(&()); + result + }) + } + + pub fn update(&mut self, key: &K, f: F) -> Option where F: FnOnce(&mut V) -> T, @@ -354,6 +374,28 @@ mod tests { assert_eq!(map.get(&"c"), Some(&5)); } + #[test] + fn test_get_from_while() { + let mut map = TreeMap::default(); + + map.insert("a", 1); + map.insert("b", 2); + map.insert("baa", 3); + map.insert("baaab", 4); + map.insert("c", 5); + + let result = map.get_from_while(&"ba", |key, _| key.starts_with(&"ba")).collect::>(); + + assert_eq!(result.len(), 2); + assert!(result.iter().find(|(k, _)| k == &&"baa").is_some()); + assert!(result.iter().find(|(k, _)| k == &&"baaab").is_some()); + + let result = map.get_from_while(&"c", |key, _| key.starts_with(&"c")).collect::>(); + + assert_eq!(result.len(), 1); + assert!(result.iter().find(|(k, _)| k == &&"c").is_some()); + } + #[test] fn test_insert_tree() { let mut map = TreeMap::default(); diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index 319d815d17..4ec8f7553c 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -26,6 +26,7 @@ serde.workspace = true serde_json.workspace = true git2 = { version = "0.15", default-features = false, optional = true } dirs = "3.0" +take-until = "0.2.0" [dev-dependencies] tempdir.workspace = true diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 903b0eec59..63b2d5f279 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -17,6 +17,8 @@ pub use backtrace::Backtrace; use futures::Future; use rand::{seq::SliceRandom, Rng}; +pub use take_until::*; + #[macro_export] macro_rules! debug_panic { ( $($fmt_arg:tt)* ) => { From dfb6a2f7fca54d22b42ded92dc9fc1fca4cbfcdc Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 11 May 2023 12:02:25 -0700 Subject: [PATCH 050/168] fmt --- crates/collab/src/db.rs | 18 ++++++++++--- crates/sum_tree/src/tree_map.rs | 45 ++++++++++++++++++--------------- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index f5175a16a9..1047b207b9 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -2484,11 +2484,21 @@ impl Database { .filter( worktree_repository_statuses::Column::ProjectId .eq(project_id) - .and(worktree_repository_statuses::Column::WorktreeId.eq(worktree_id)) - .and(worktree_repository_statuses::Column::WorkDirectoryId.eq(repository.work_directory_id as i64)) .and( - worktree_repository_statuses::Column::RepoPath - .is_in(repository.removed_worktree_repo_paths.iter().map(String::as_str)), + worktree_repository_statuses::Column::WorktreeId + .eq(worktree_id), + ) + .and( + worktree_repository_statuses::Column::WorkDirectoryId + .eq(repository.work_directory_id as i64), + ) + .and( + worktree_repository_statuses::Column::RepoPath.is_in( + repository + .removed_worktree_repo_paths + .iter() + .map(String::as_str), + ), ), ) .set(worktree_repository_statuses::ActiveModel { diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index e59b05f00f..b18af3633a 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -111,25 +111,26 @@ impl TreeMap { self.0 = new_tree; } + pub fn get_from_while<'tree, F>( + &'tree self, + from: &'tree K, + mut f: F, + ) -> impl Iterator + '_ + where + F: FnMut(&K, &K, &V) -> bool + 'tree, + { + let mut cursor = self.0.cursor::>(); + let from_key = MapKeyRef(Some(from)); + cursor.seek(&from_key, Bias::Left, &()); - pub fn get_from_while<'tree, F>(&'tree self, from: &'tree K, mut f: F) -> impl Iterator + '_ - where - F: FnMut(&K, &K, &V) -> bool + 'tree, - { - let mut cursor = self.0.cursor::>(); - let from_key = MapKeyRef(Some(from)); - cursor.seek(&from_key, Bias::Left, &()); - - iter::from_fn(move || { - let result = cursor.item().and_then(|item| { - (f(from, &item.key, &item.value)) - .then(|| (&item.key, &item.value)) - }); - cursor.next(&()); - result - }) - } - + iter::from_fn(move || { + let result = cursor.item().and_then(|item| { + (f(from, &item.key, &item.value)).then(|| (&item.key, &item.value)) + }); + cursor.next(&()); + result + }) + } pub fn update(&mut self, key: &K, f: F) -> Option where @@ -384,13 +385,17 @@ mod tests { map.insert("baaab", 4); map.insert("c", 5); - let result = map.get_from_while(&"ba", |key, _| key.starts_with(&"ba")).collect::>(); + let result = map + .get_from_while(&"ba", |key, _| key.starts_with(&"ba")) + .collect::>(); assert_eq!(result.len(), 2); assert!(result.iter().find(|(k, _)| k == &&"baa").is_some()); assert!(result.iter().find(|(k, _)| k == &&"baaab").is_some()); - let result = map.get_from_while(&"c", |key, _| key.starts_with(&"c")).collect::>(); + let result = map + .get_from_while(&"c", |key, _| key.starts_with(&"c")) + .collect::>(); assert_eq!(result.len(), 1); assert!(result.iter().find(|(k, _)| k == &&"c").is_some()); From 1bb34e08bb14a48d7a06efa6490568b74ca30af6 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 11 May 2023 12:03:39 -0700 Subject: [PATCH 051/168] Fix test --- crates/sum_tree/src/tree_map.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index b18af3633a..bfc8db5bf6 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -386,7 +386,7 @@ mod tests { map.insert("c", 5); let result = map - .get_from_while(&"ba", |key, _| key.starts_with(&"ba")) + .get_from_while(&"ba", |_, key, _| key.starts_with(&"ba")) .collect::>(); assert_eq!(result.len(), 2); @@ -394,7 +394,7 @@ mod tests { assert!(result.iter().find(|(k, _)| k == &&"baaab").is_some()); let result = map - .get_from_while(&"c", |key, _| key.starts_with(&"c")) + .get_from_while(&"c", |_, key, _| key.starts_with(&"c")) .collect::>(); assert_eq!(result.len(), 1); From 6f87f9c51f0d8f3602d6e97113da1192b5712eb2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 11 May 2023 13:25:07 -0700 Subject: [PATCH 052/168] Don't scan for statuses in files that are ignored --- crates/project/src/worktree.rs | 59 ++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 07302b4e2e..8ce087e9d9 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3065,6 +3065,15 @@ impl BackgroundScanner { entry.worktree_statuses = statuses; }); } else { + if snapshot + .entry_for_path(&path) + .map(|entry| entry.is_ignored) + .unwrap_or(false) + { + self.remove_repo_path(&path, snapshot); + return None; + } + let repo = snapshot.repo_for(&path)?; let repo_path = repo.work_directory.relativize(&snapshot, &path)?; @@ -3580,7 +3589,6 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry { mod tests { use super::*; use fs::{FakeFs, RealFs}; - use git2::Signature; use gpui::{executor::Deterministic, TestAppContext}; use pretty_assertions::assert_eq; use rand::prelude::*; @@ -3919,6 +3927,8 @@ mod tests { #[track_caller] fn git_commit(msg: &'static str, repo: &git2::Repository) { + use git2::Signature; + let signature = Signature::now("test", "test@zed.dev").unwrap(); let oid = repo.index().unwrap().write_tree().unwrap(); let tree = repo.find_tree(oid).unwrap(); @@ -3944,7 +3954,9 @@ mod tests { #[track_caller] fn git_stash(repo: &mut git2::Repository) { - let signature = repo.signature().unwrap(); + use git2::Signature; + + let signature = Signature::now("test", "test@zed.dev").unwrap(); repo.stash_save(&signature, "N/A", None) .expect("Failed to stash"); } @@ -3976,6 +3988,8 @@ mod tests { .collect() } + const IGNORE_RULE: &'static str = "**/target"; + let root = temp_tree(json!({ "project": { "a.txt": "a", @@ -3984,7 +3998,12 @@ mod tests { "d": { "e.txt": "eee" } - } + }, + "f.txt": "ffff", + "target": { + "build_file": "???" + }, + ".gitignore": IGNORE_RULE }, })); @@ -4008,12 +4027,16 @@ mod tests { const A_TXT: &'static str = "a.txt"; const B_TXT: &'static str = "b.txt"; const E_TXT: &'static str = "c/d/e.txt"; + const F_TXT: &'static str = "f.txt"; + const DOTGITIGNORE: &'static str = ".gitignore"; + const BUILD_FILE: &'static str = "target/build_file"; let work_dir = root.path().join("project"); - let mut repo = git_init(work_dir.as_path()); + repo.add_ignore_rule(IGNORE_RULE).unwrap(); git_add(Path::new(A_TXT), &repo); git_add(Path::new(E_TXT), &repo); + git_add(Path::new(DOTGITIGNORE), &repo); git_commit("Initial commit", &repo); std::fs::write(work_dir.join(A_TXT), "aa").unwrap(); @@ -4027,7 +4050,7 @@ mod tests { let (dir, repo) = snapshot.repository_entries.iter().next().unwrap(); assert_eq!(dir.0.as_ref(), Path::new("project")); - assert_eq!(repo.worktree_statuses.iter().count(), 2); + assert_eq!(repo.worktree_statuses.iter().count(), 3); assert_eq!( repo.worktree_statuses.get(&Path::new(A_TXT).into()), Some(&GitFileStatus::Modified) @@ -4036,6 +4059,10 @@ mod tests { repo.worktree_statuses.get(&Path::new(B_TXT).into()), Some(&GitFileStatus::Added) ); + assert_eq!( + repo.worktree_statuses.get(&Path::new(F_TXT).into()), + Some(&GitFileStatus::Added) + ); }); git_add(Path::new(A_TXT), &repo); @@ -4048,15 +4075,20 @@ mod tests { let snapshot = tree.snapshot(); let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - assert_eq!(repo.worktree_statuses.iter().count(), 0); + assert_eq!(repo.worktree_statuses.iter().count(), 1); assert_eq!(repo.worktree_statuses.get(&Path::new(A_TXT).into()), None); assert_eq!(repo.worktree_statuses.get(&Path::new(B_TXT).into()), None); + assert_eq!( + repo.worktree_statuses.get(&Path::new(F_TXT).into()), + Some(&GitFileStatus::Added) + ); }); git_reset(0, &repo); git_remove_index(Path::new(B_TXT), &repo); git_stash(&mut repo); std::fs::write(work_dir.join(E_TXT), "eeee").unwrap(); + std::fs::write(work_dir.join(BUILD_FILE), "this should be ignored").unwrap(); tree.flush_fs_events(cx).await; // Check that more complex repo changes are tracked @@ -4064,7 +4096,7 @@ mod tests { let snapshot = tree.snapshot(); let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - assert_eq!(repo.worktree_statuses.iter().count(), 2); + assert_eq!(repo.worktree_statuses.iter().count(), 3); assert_eq!(repo.worktree_statuses.get(&Path::new(A_TXT).into()), None); assert_eq!( repo.worktree_statuses.get(&Path::new(B_TXT).into()), @@ -4074,22 +4106,35 @@ mod tests { repo.worktree_statuses.get(&Path::new(E_TXT).into()), Some(&GitFileStatus::Modified) ); + assert_eq!( + repo.worktree_statuses.get(&Path::new(F_TXT).into()), + Some(&GitFileStatus::Added) + ); }); std::fs::remove_file(work_dir.join(B_TXT)).unwrap(); std::fs::remove_dir_all(work_dir.join("c")).unwrap(); + std::fs::write(work_dir.join(DOTGITIGNORE), [IGNORE_RULE, "f.txt"].join("\n")).unwrap(); + + git_add(Path::new(DOTGITIGNORE), &repo); + git_commit("Committing modified git ignore", &repo); tree.flush_fs_events(cx).await; + dbg!(git_status(&repo)); + // Check that non-repo behavior is tracked tree.read_with(cx, |tree, _cx| { let snapshot = tree.snapshot(); let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + dbg!(&repo.worktree_statuses); + assert_eq!(repo.worktree_statuses.iter().count(), 0); assert_eq!(repo.worktree_statuses.get(&Path::new(A_TXT).into()), None); assert_eq!(repo.worktree_statuses.get(&Path::new(B_TXT).into()), None); assert_eq!(repo.worktree_statuses.get(&Path::new(E_TXT).into()), None); + assert_eq!(repo.worktree_statuses.get(&Path::new(F_TXT).into()), None); }); } From 72655fc41d154684b674f2d4cba9ff24c32641d6 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 11 May 2023 13:25:57 -0700 Subject: [PATCH 053/168] fmt --- crates/project/src/worktree.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 8ce087e9d9..2112bd92b1 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -4114,7 +4114,11 @@ mod tests { std::fs::remove_file(work_dir.join(B_TXT)).unwrap(); std::fs::remove_dir_all(work_dir.join("c")).unwrap(); - std::fs::write(work_dir.join(DOTGITIGNORE), [IGNORE_RULE, "f.txt"].join("\n")).unwrap(); + std::fs::write( + work_dir.join(DOTGITIGNORE), + [IGNORE_RULE, "f.txt"].join("\n"), + ) + .unwrap(); git_add(Path::new(DOTGITIGNORE), &repo); git_commit("Committing modified git ignore", &repo); From d538994c7f03618cfc1df60c54c01a7b166e8a1b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 11 May 2023 16:06:25 -0700 Subject: [PATCH 054/168] Use more efficient sum tree traversals for removal and improve ergonomics with iter_from co-authored-by: Nathan --- crates/project/src/worktree.rs | 5 +- crates/sum_tree/src/tree_map.rs | 120 +++++++++++++++++++++----------- 2 files changed, 84 insertions(+), 41 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 2112bd92b1..305bcbbf16 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -185,7 +185,8 @@ impl RepositoryEntry { .relativize(snapshot, path) .and_then(|repo_path| { self.worktree_statuses - .get_from_while(&repo_path, |repo_path, key, _| key.starts_with(repo_path)) + .iter_from(&repo_path) + .take_while(|(key, _)| key.starts_with(&repo_path)) .map(|(_, status)| status) // Short circut once we've found the highest level .take_until(|status| status == &&GitFileStatus::Conflict) @@ -3022,7 +3023,7 @@ impl BackgroundScanner { snapshot.repository_entries.update(&work_dir, |entry| { entry .worktree_statuses - .remove_from_while(&repo_path, |stored_path, _| { + .remove_by(&repo_path, |stored_path, _| { stored_path.starts_with(&repo_path) }) }); diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index bfc8db5bf6..fdafdaeb3a 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, fmt::Debug, iter}; +use std::{cmp::Ordering, fmt::Debug}; use crate::{Bias, Dimension, Edit, Item, KeyedItem, SeekTarget, SumTree, Summary}; @@ -93,43 +93,14 @@ impl TreeMap { self.0 = new_tree; } - pub fn remove_from_while(&mut self, from: &K, mut f: F) - where - F: FnMut(&K, &V) -> bool, - { - let mut cursor = self.0.cursor::>(); - let from_key = MapKeyRef(Some(from)); - let mut new_tree = cursor.slice(&from_key, Bias::Left, &()); - while let Some(item) = cursor.item() { - if !f(&item.key, &item.value) { - break; - } - cursor.next(&()); - } - new_tree.push_tree(cursor.suffix(&()), &()); - drop(cursor); - self.0 = new_tree; - } - - pub fn get_from_while<'tree, F>( - &'tree self, - from: &'tree K, - mut f: F, - ) -> impl Iterator + '_ - where - F: FnMut(&K, &K, &V) -> bool + 'tree, - { + pub fn iter_from<'a>(&'a self, from: &'a K) -> impl Iterator + '_ { let mut cursor = self.0.cursor::>(); let from_key = MapKeyRef(Some(from)); cursor.seek(&from_key, Bias::Left, &()); - iter::from_fn(move || { - let result = cursor.item().and_then(|item| { - (f(from, &item.key, &item.value)).then(|| (&item.key, &item.value)) - }); - cursor.next(&()); - result - }) + cursor + .into_iter() + .map(|map_entry| (&map_entry.key, &map_entry.value)) } pub fn update(&mut self, key: &K, f: F) -> Option @@ -189,6 +160,51 @@ impl TreeMap { self.0.edit(edits, &()); } + + pub fn remove_by(&mut self, key: &K, f: F) + where + F: Fn(&K) -> bool, + { + let mut cursor = self.0.cursor::>(); + let key = MapKeyRef(Some(key)); + let mut new_tree = cursor.slice(&key, Bias::Left, &()); + let until = RemoveByTarget(key, &f); + cursor.seek_forward(&until, Bias::Right, &()); + new_tree.push_tree(cursor.suffix(&()), &()); + drop(cursor); + self.0 = new_tree; + } +} + +struct RemoveByTarget<'a, K>(MapKeyRef<'a, K>, &'a dyn Fn(&K) -> bool); + +impl<'a, K: Debug> Debug for RemoveByTarget<'a, K> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RemoveByTarget") + .field("key", &self.0) + .field("F", &"<...>") + .finish() + } +} + +impl<'a, K: Debug + Clone + Default + Ord> SeekTarget<'a, MapKey, MapKeyRef<'a, K>> + for RemoveByTarget<'_, K> +{ + fn cmp( + &self, + cursor_location: &MapKeyRef<'a, K>, + _cx: & as Summary>::Context, + ) -> Ordering { + if let Some(cursor_location) = cursor_location.0 { + if (self.1)(cursor_location) { + Ordering::Equal + } else { + self.0 .0.unwrap().cmp(cursor_location) + } + } else { + Ordering::Greater + } + } } impl Default for TreeMap @@ -357,26 +373,50 @@ mod tests { } #[test] - fn test_remove_from_while() { + fn test_remove_by() { let mut map = TreeMap::default(); map.insert("a", 1); + map.insert("aa", 1); map.insert("b", 2); map.insert("baa", 3); map.insert("baaab", 4); map.insert("c", 5); + map.insert("ca", 6); - map.remove_from_while(&"ba", |key, _| key.starts_with(&"ba")); + map.remove_by(&"ba", |key| key.starts_with("ba")); assert_eq!(map.get(&"a"), Some(&1)); + assert_eq!(map.get(&"aa"), Some(&1)); assert_eq!(map.get(&"b"), Some(&2)); assert_eq!(map.get(&"baaa"), None); assert_eq!(map.get(&"baaaab"), None); assert_eq!(map.get(&"c"), Some(&5)); + assert_eq!(map.get(&"ca"), Some(&6)); + + + map.remove_by(&"c", |key| key.starts_with("c")); + + assert_eq!(map.get(&"a"), Some(&1)); + assert_eq!(map.get(&"aa"), Some(&1)); + assert_eq!(map.get(&"b"), Some(&2)); + assert_eq!(map.get(&"c"), None); + assert_eq!(map.get(&"ca"), None); + + map.remove_by(&"a", |key| key.starts_with("a")); + + assert_eq!(map.get(&"a"), None); + assert_eq!(map.get(&"aa"), None); + assert_eq!(map.get(&"b"), Some(&2)); + + map.remove_by(&"b", |key| key.starts_with("b")); + + assert_eq!(map.get(&"b"), None); + } #[test] - fn test_get_from_while() { + fn test_iter_from() { let mut map = TreeMap::default(); map.insert("a", 1); @@ -386,7 +426,8 @@ mod tests { map.insert("c", 5); let result = map - .get_from_while(&"ba", |_, key, _| key.starts_with(&"ba")) + .iter_from(&"ba") + .take_while(|(key, _)| key.starts_with(&"ba")) .collect::>(); assert_eq!(result.len(), 2); @@ -394,7 +435,8 @@ mod tests { assert!(result.iter().find(|(k, _)| k == &&"baaab").is_some()); let result = map - .get_from_while(&"c", |_, key, _| key.starts_with(&"c")) + .iter_from(&"c") + .take_while(|(key, _)| key.starts_with(&"c")) .collect::>(); assert_eq!(result.len(), 1); From d526fa6f1fb8d84958ee345178880fe649150113 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 11 May 2023 16:06:56 -0700 Subject: [PATCH 055/168] fmt --- crates/sum_tree/src/tree_map.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index fdafdaeb3a..509a79ec47 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -394,7 +394,6 @@ mod tests { assert_eq!(map.get(&"c"), Some(&5)); assert_eq!(map.get(&"ca"), Some(&6)); - map.remove_by(&"c", |key| key.starts_with("c")); assert_eq!(map.get(&"a"), Some(&1)); @@ -412,7 +411,6 @@ mod tests { map.remove_by(&"b", |key| key.starts_with("b")); assert_eq!(map.get(&"b"), None); - } #[test] From 5fe8b73f0491ec4f62a2b3204484918a10f5aa53 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 11 May 2023 16:07:41 -0700 Subject: [PATCH 056/168] =?UTF-8?q?compile=20error=20=F0=9F=98=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/project/src/worktree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 305bcbbf16..fcda45fd6f 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3023,7 +3023,7 @@ impl BackgroundScanner { snapshot.repository_entries.update(&work_dir, |entry| { entry .worktree_statuses - .remove_by(&repo_path, |stored_path, _| { + .remove_by(&repo_path, |stored_path| { stored_path.starts_with(&repo_path) }) }); From 9ae10a5dd9e92d2536f180106dcfd2abb961d498 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 May 2023 14:39:43 -0700 Subject: [PATCH 057/168] Add a better API for updating settings in the SettingsStore in tests --- crates/settings/src/settings_file.rs | 10 +++--- crates/settings/src/settings_store.rs | 43 ++++++++++++++++++------- crates/vim/src/test/vim_test_context.rs | 12 +++---- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 990ccf0249..936f3a7099 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -172,12 +172,10 @@ pub fn update_settings_file( }) .await?; - let edits = cx.read(|cx| cx.global::().update::(&old_text, update)); - - let mut new_text = old_text; - for (range, replacement) in edits.into_iter().rev() { - new_text.replace_range(range, &replacement); - } + let new_text = cx.read(|cx| { + cx.global::() + .new_text_for_update::(old_text, update) + }); cx.background() .spawn(async move { fs.atomic_write(paths::SETTINGS.clone(), new_text).await }) diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 59f7bee10f..caa77a3603 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -184,22 +184,43 @@ impl SettingsStore { this } - /// Override the global value for a particular setting. + /// Update the value of a setting in the user's global configuration. /// /// This is only for tests. Normally, settings are only loaded from /// JSON files. #[cfg(any(test, feature = "test-support"))] - pub fn replace_value(&mut self, value: T) { - self.setting_values - .get_mut(&TypeId::of::()) - .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::())) - .set_global_value(Box::new(value)) + pub fn update_user_settings( + &mut self, + cx: &AppContext, + update: impl FnOnce(&mut T::FileContent), + ) { + let old_text = if let Some(user_settings) = &self.user_deserialized_settings { + serde_json::to_string(&user_settings.untyped).unwrap() + } else { + String::new() + }; + let new_text = self.new_text_for_update::(old_text, update); + self.set_user_settings(&new_text, cx).unwrap(); } - /// Update the value of a setting. - /// - /// Returns a list of edits to apply to the JSON file. - pub fn update( + /// Update the value of a setting in a JSON file, returning the new text + /// for that JSON file. + pub fn new_text_for_update( + &self, + old_text: String, + update: impl FnOnce(&mut T::FileContent), + ) -> String { + let edits = self.edits_for_update::(&old_text, update); + let mut new_text = old_text; + for (range, replacement) in edits.into_iter().rev() { + new_text.replace_range(range, &replacement); + } + new_text + } + + /// Update the value of a setting in a JSON file, returning a list + /// of edits to apply to the JSON file. + pub fn edits_for_update( &self, text: &str, update: impl FnOnce(&mut T::FileContent), @@ -1129,7 +1150,7 @@ mod tests { cx: &mut AppContext, ) { store.set_user_settings(&old_json, cx).ok(); - let edits = store.update::(&old_json, update); + let edits = store.edits_for_update::(&old_json, update); let mut new_json = old_json; for (range, replacement) in edits.into_iter().rev() { new_json.replace_range(range, &replacement); diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index b426cea717..ac86a08235 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -21,8 +21,8 @@ impl<'a> VimTestContext<'a> { search::init(cx); crate::init(cx); - cx.update_global(|store: &mut SettingsStore, _| { - store.replace_value(VimModeSetting(enabled)); + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |s| *s = Some(enabled)); }); settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap(); @@ -53,16 +53,16 @@ impl<'a> VimTestContext<'a> { pub fn enable_vim(&mut self) { self.cx.update(|cx| { - cx.update_global(|store: &mut SettingsStore, _| { - store.replace_value(VimModeSetting(true)) + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |s| *s = Some(true)); }); }) } pub fn disable_vim(&mut self) { self.cx.update(|cx| { - cx.update_global(|store: &mut SettingsStore, _| { - store.replace_value(VimModeSetting(false)) + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |s| *s = Some(false)); }); }) } From ee3637216eb44bd146d7426d22e5e0d8ad149a51 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 11 May 2023 23:20:20 -0600 Subject: [PATCH 058/168] Add TreeMap::remove_between that can take abstract start and end points This commit introduces a new adaptor trait for SeekTarget that works around frustrating issues with lifetimes. It wraps the arguments in a newtype wrapper that lives on the stack to avoid the lifetime getting extended to the caller of the method. This allows us to introduce a PathSuccessor object that can be passed as the end argument of remove_between to remove a whole subtree. --- crates/project/src/worktree.rs | 6 +- crates/sum_tree/src/sum_tree.rs | 4 +- crates/sum_tree/src/tree_map.rs | 198 +++++++++++++++----------------- 3 files changed, 94 insertions(+), 114 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index fcda45fd6f..249559de88 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -54,7 +54,7 @@ use std::{ }, time::{Duration, SystemTime}, }; -use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet}; +use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet, PathDescendants}; use util::{paths::HOME, ResultExt, TakeUntilExt, TryFutureExt}; #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] @@ -3023,9 +3023,7 @@ impl BackgroundScanner { snapshot.repository_entries.update(&work_dir, |entry| { entry .worktree_statuses - .remove_by(&repo_path, |stored_path| { - stored_path.starts_with(&repo_path) - }) + .remove_range(&repo_path, &PathDescendants(&repo_path)) }); } diff --git a/crates/sum_tree/src/sum_tree.rs b/crates/sum_tree/src/sum_tree.rs index 3e916ccd1b..6b6eacda59 100644 --- a/crates/sum_tree/src/sum_tree.rs +++ b/crates/sum_tree/src/sum_tree.rs @@ -5,7 +5,7 @@ use arrayvec::ArrayVec; pub use cursor::{Cursor, FilterCursor, Iter}; use std::marker::PhantomData; use std::{cmp::Ordering, fmt, iter::FromIterator, sync::Arc}; -pub use tree_map::{TreeMap, TreeSet}; +pub use tree_map::{TreeMap, TreeSet, PathDescendants}; #[cfg(test)] const TREE_BASE: usize = 2; @@ -47,7 +47,7 @@ impl<'a, T: Summary> Dimension<'a, T> for T { } pub trait SeekTarget<'a, S: Summary, D: Dimension<'a, S>>: fmt::Debug { - fn cmp(&self, cursor_location: &D, cx: &S::Context) -> Ordering; + fn cmp(&self, cursor_location: &D, cx: &S::Context) -> Ordering; } impl<'a, S: Summary, D: Dimension<'a, S> + Ord> SeekTarget<'a, S, D> for D { diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index 509a79ec47..3d49c48998 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -1,4 +1,8 @@ -use std::{cmp::Ordering, fmt::Debug}; +use std::{ + cmp::Ordering, + fmt::Debug, + path::{Path, PathBuf}, +}; use crate::{Bias, Dimension, Edit, Item, KeyedItem, SeekTarget, SumTree, Summary}; @@ -73,6 +77,17 @@ impl TreeMap { removed } + pub fn remove_range(&mut self, start: &impl MapSeekTarget, end: &impl MapSeekTarget) { + let start = MapSeekTargetAdaptor(start); + let end = MapSeekTargetAdaptor(end); + let mut cursor = self.0.cursor::>(); + let mut new_tree = cursor.slice(&start, Bias::Left, &()); + cursor.seek(&end, Bias::Left, &()); + new_tree.push_tree(cursor.suffix(&()), &()); + drop(cursor); + self.0 = new_tree; + } + /// Returns the key-value pair with the greatest key less than or equal to the given key. pub fn closest(&self, key: &K) -> Option<(&K, &V)> { let mut cursor = self.0.cursor::>(); @@ -82,17 +97,6 @@ impl TreeMap { cursor.item().map(|item| (&item.key, &item.value)) } - pub fn remove_between(&mut self, from: &K, until: &K) { - let mut cursor = self.0.cursor::>(); - let from_key = MapKeyRef(Some(from)); - let mut new_tree = cursor.slice(&from_key, Bias::Left, &()); - let until_key = MapKeyRef(Some(until)); - cursor.seek_forward(&until_key, Bias::Left, &()); - new_tree.push_tree(cursor.suffix(&()), &()); - drop(cursor); - self.0 = new_tree; - } - pub fn iter_from<'a>(&'a self, from: &'a K) -> impl Iterator + '_ { let mut cursor = self.0.cursor::>(); let from_key = MapKeyRef(Some(from)); @@ -160,46 +164,43 @@ impl TreeMap { self.0.edit(edits, &()); } - - pub fn remove_by(&mut self, key: &K, f: F) - where - F: Fn(&K) -> bool, - { - let mut cursor = self.0.cursor::>(); - let key = MapKeyRef(Some(key)); - let mut new_tree = cursor.slice(&key, Bias::Left, &()); - let until = RemoveByTarget(key, &f); - cursor.seek_forward(&until, Bias::Right, &()); - new_tree.push_tree(cursor.suffix(&()), &()); - drop(cursor); - self.0 = new_tree; - } } -struct RemoveByTarget<'a, K>(MapKeyRef<'a, K>, &'a dyn Fn(&K) -> bool); +#[derive(Debug)] +struct MapSeekTargetAdaptor<'a, T>(&'a T); -impl<'a, K: Debug> Debug for RemoveByTarget<'a, K> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("RemoveByTarget") - .field("key", &self.0) - .field("F", &"<...>") - .finish() - } -} - -impl<'a, K: Debug + Clone + Default + Ord> SeekTarget<'a, MapKey, MapKeyRef<'a, K>> - for RemoveByTarget<'_, K> +impl<'a, K: Debug + Clone + Default + Ord, T: MapSeekTarget> + SeekTarget<'a, MapKey, MapKeyRef<'a, K>> for MapSeekTargetAdaptor<'_, T> { - fn cmp( - &self, - cursor_location: &MapKeyRef<'a, K>, - _cx: & as Summary>::Context, - ) -> Ordering { - if let Some(cursor_location) = cursor_location.0 { - if (self.1)(cursor_location) { - Ordering::Equal + fn cmp(&self, cursor_location: &MapKeyRef, _: &()) -> Ordering { + MapSeekTarget::cmp(self.0, cursor_location) + } +} + +pub trait MapSeekTarget: Debug { + fn cmp(&self, cursor_location: &MapKeyRef) -> Ordering; +} + +impl MapSeekTarget for K { + fn cmp(&self, cursor_location: &MapKeyRef) -> Ordering { + if let Some(key) = &cursor_location.0 { + self.cmp(key) + } else { + Ordering::Greater + } + } +} + +#[derive(Debug)] +pub struct PathDescendants<'a>(&'a Path); + +impl MapSeekTarget for PathDescendants<'_> { + fn cmp(&self, cursor_location: &MapKeyRef) -> Ordering { + if let Some(key) = &cursor_location.0 { + if key.starts_with(&self.0) { + Ordering::Greater } else { - self.0 .0.unwrap().cmp(cursor_location) + self.0.cmp(key) } } else { Ordering::Greater @@ -266,7 +267,7 @@ where K: Clone + Debug + Default + Ord, { fn cmp(&self, cursor_location: &MapKeyRef, _: &()) -> Ordering { - self.0.cmp(&cursor_location.0) + Ord::cmp(&self.0, &cursor_location.0) } } @@ -353,66 +354,6 @@ mod tests { assert_eq!(map.iter().collect::>(), vec![(&4, &"d"), (&6, &"f")]); } - #[test] - fn test_remove_between() { - let mut map = TreeMap::default(); - - map.insert("a", 1); - map.insert("b", 2); - map.insert("baa", 3); - map.insert("baaab", 4); - map.insert("c", 5); - - map.remove_between(&"ba", &"bb"); - - assert_eq!(map.get(&"a"), Some(&1)); - assert_eq!(map.get(&"b"), Some(&2)); - assert_eq!(map.get(&"baaa"), None); - assert_eq!(map.get(&"baaaab"), None); - assert_eq!(map.get(&"c"), Some(&5)); - } - - #[test] - fn test_remove_by() { - let mut map = TreeMap::default(); - - map.insert("a", 1); - map.insert("aa", 1); - map.insert("b", 2); - map.insert("baa", 3); - map.insert("baaab", 4); - map.insert("c", 5); - map.insert("ca", 6); - - map.remove_by(&"ba", |key| key.starts_with("ba")); - - assert_eq!(map.get(&"a"), Some(&1)); - assert_eq!(map.get(&"aa"), Some(&1)); - assert_eq!(map.get(&"b"), Some(&2)); - assert_eq!(map.get(&"baaa"), None); - assert_eq!(map.get(&"baaaab"), None); - assert_eq!(map.get(&"c"), Some(&5)); - assert_eq!(map.get(&"ca"), Some(&6)); - - map.remove_by(&"c", |key| key.starts_with("c")); - - assert_eq!(map.get(&"a"), Some(&1)); - assert_eq!(map.get(&"aa"), Some(&1)); - assert_eq!(map.get(&"b"), Some(&2)); - assert_eq!(map.get(&"c"), None); - assert_eq!(map.get(&"ca"), None); - - map.remove_by(&"a", |key| key.starts_with("a")); - - assert_eq!(map.get(&"a"), None); - assert_eq!(map.get(&"aa"), None); - assert_eq!(map.get(&"b"), Some(&2)); - - map.remove_by(&"b", |key| key.starts_with("b")); - - assert_eq!(map.get(&"b"), None); - } - #[test] fn test_iter_from() { let mut map = TreeMap::default(); @@ -461,4 +402,45 @@ mod tests { assert_eq!(map.get(&"c"), Some(&3)); assert_eq!(map.get(&"d"), Some(&4)); } + + #[test] + fn test_remove_between_and_path_successor() { + let mut map = TreeMap::default(); + + map.insert(PathBuf::from("a"), 1); + map.insert(PathBuf::from("a/a"), 1); + map.insert(PathBuf::from("b"), 2); + map.insert(PathBuf::from("b/a/a"), 3); + map.insert(PathBuf::from("b/a/a/a/b"), 4); + map.insert(PathBuf::from("c"), 5); + map.insert(PathBuf::from("c/a"), 6); + + map.remove_range(&PathBuf::from("b/a"), &PathDescendants(&PathBuf::from("b/a"))); + + assert_eq!(map.get(&PathBuf::from("a")), Some(&1)); + assert_eq!(map.get(&PathBuf::from("a/a")), Some(&1)); + assert_eq!(map.get(&PathBuf::from("b")), Some(&2)); + assert_eq!(map.get(&PathBuf::from("b/a/a")), None); + assert_eq!(map.get(&PathBuf::from("b/a/a/a/b")), None); + assert_eq!(map.get(&PathBuf::from("c")), Some(&5)); + assert_eq!(map.get(&PathBuf::from("c/a")), Some(&6)); + + map.remove_range(&PathBuf::from("c"), &PathDescendants(&PathBuf::from("c"))); + + assert_eq!(map.get(&PathBuf::from("a")), Some(&1)); + assert_eq!(map.get(&PathBuf::from("a/a")), Some(&1)); + assert_eq!(map.get(&PathBuf::from("b")), Some(&2)); + assert_eq!(map.get(&PathBuf::from("c")), None); + assert_eq!(map.get(&PathBuf::from("c/a")), None); + + map.remove_range(&PathBuf::from("a"), &PathDescendants(&PathBuf::from("a"))); + + assert_eq!(map.get(&PathBuf::from("a")), None); + assert_eq!(map.get(&PathBuf::from("a/a")), None); + assert_eq!(map.get(&PathBuf::from("b")), Some(&2)); + + map.remove_range(&PathBuf::from("b"), &PathDescendants(&PathBuf::from("b"))); + + assert_eq!(map.get(&PathBuf::from("b")), None); + } } From 6ef0f70528959d0a04b8baff78b74c22752241c1 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 12 May 2023 08:37:07 -0700 Subject: [PATCH 059/168] Made the map seek target a publicly implementable interface Integrated remove_range with the existing git code co-authored-by: Nathan --- crates/fs/src/repository.rs | 16 ++++++++- crates/project/src/worktree.rs | 6 ++-- crates/sum_tree/src/sum_tree.rs | 4 +-- crates/sum_tree/src/tree_map.rs | 59 ++++++++++++++++----------------- 4 files changed, 48 insertions(+), 37 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 13f55b9c94..51b69b8bc7 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -3,12 +3,13 @@ use collections::HashMap; use parking_lot::Mutex; use serde_derive::{Deserialize, Serialize}; use std::{ + cmp::Ordering, ffi::OsStr, os::unix::prelude::OsStrExt, path::{Component, Path, PathBuf}, sync::Arc, }; -use sum_tree::TreeMap; +use sum_tree::{MapSeekTarget, TreeMap}; use util::ResultExt; pub use git2::Repository as LibGitRepository; @@ -233,3 +234,16 @@ impl std::ops::Deref for RepoPath { &self.0 } } + +#[derive(Debug)] +pub struct RepoPathDescendants<'a>(pub &'a Path); + +impl<'a> MapSeekTarget for RepoPathDescendants<'a> { + fn cmp_cursor(&self, key: &RepoPath) -> Ordering { + if key.starts_with(&self.0) { + Ordering::Greater + } else { + self.0.cmp(key) + } + } +} diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 249559de88..e713eed58d 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -7,7 +7,7 @@ use client::{proto, Client}; use clock::ReplicaId; use collections::{HashMap, VecDeque}; use fs::{ - repository::{GitFileStatus, GitRepository, RepoPath}, + repository::{GitFileStatus, GitRepository, RepoPath, RepoPathDescendants}, Fs, LineEnding, }; use futures::{ @@ -54,7 +54,7 @@ use std::{ }, time::{Duration, SystemTime}, }; -use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet, PathDescendants}; +use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet}; use util::{paths::HOME, ResultExt, TakeUntilExt, TryFutureExt}; #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] @@ -3023,7 +3023,7 @@ impl BackgroundScanner { snapshot.repository_entries.update(&work_dir, |entry| { entry .worktree_statuses - .remove_range(&repo_path, &PathDescendants(&repo_path)) + .remove_range(&repo_path, &RepoPathDescendants(&repo_path)) }); } diff --git a/crates/sum_tree/src/sum_tree.rs b/crates/sum_tree/src/sum_tree.rs index 6b6eacda59..36f0f926cd 100644 --- a/crates/sum_tree/src/sum_tree.rs +++ b/crates/sum_tree/src/sum_tree.rs @@ -5,7 +5,7 @@ use arrayvec::ArrayVec; pub use cursor::{Cursor, FilterCursor, Iter}; use std::marker::PhantomData; use std::{cmp::Ordering, fmt, iter::FromIterator, sync::Arc}; -pub use tree_map::{TreeMap, TreeSet, PathDescendants}; +pub use tree_map::{MapSeekTarget, TreeMap, TreeSet}; #[cfg(test)] const TREE_BASE: usize = 2; @@ -47,7 +47,7 @@ impl<'a, T: Summary> Dimension<'a, T> for T { } pub trait SeekTarget<'a, S: Summary, D: Dimension<'a, S>>: fmt::Debug { - fn cmp(&self, cursor_location: &D, cx: &S::Context) -> Ordering; + fn cmp(&self, cursor_location: &D, cx: &S::Context) -> Ordering; } impl<'a, S: Summary, D: Dimension<'a, S> + Ord> SeekTarget<'a, S, D> for D { diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index 3d49c48998..ea69fb0dca 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -1,8 +1,4 @@ -use std::{ - cmp::Ordering, - fmt::Debug, - path::{Path, PathBuf}, -}; +use std::{cmp::Ordering, fmt::Debug}; use crate::{Bias, Dimension, Edit, Item, KeyedItem, SeekTarget, SumTree, Summary}; @@ -173,38 +169,21 @@ impl<'a, K: Debug + Clone + Default + Ord, T: MapSeekTarget> SeekTarget<'a, MapKey, MapKeyRef<'a, K>> for MapSeekTargetAdaptor<'_, T> { fn cmp(&self, cursor_location: &MapKeyRef, _: &()) -> Ordering { - MapSeekTarget::cmp(self.0, cursor_location) + if let Some(key) = &cursor_location.0 { + MapSeekTarget::cmp_cursor(self.0, key) + } else { + Ordering::Greater + } } } pub trait MapSeekTarget: Debug { - fn cmp(&self, cursor_location: &MapKeyRef) -> Ordering; + fn cmp_cursor(&self, cursor_location: &K) -> Ordering; } impl MapSeekTarget for K { - fn cmp(&self, cursor_location: &MapKeyRef) -> Ordering { - if let Some(key) = &cursor_location.0 { - self.cmp(key) - } else { - Ordering::Greater - } - } -} - -#[derive(Debug)] -pub struct PathDescendants<'a>(&'a Path); - -impl MapSeekTarget for PathDescendants<'_> { - fn cmp(&self, cursor_location: &MapKeyRef) -> Ordering { - if let Some(key) = &cursor_location.0 { - if key.starts_with(&self.0) { - Ordering::Greater - } else { - self.0.cmp(key) - } - } else { - Ordering::Greater - } + fn cmp_cursor(&self, cursor_location: &K) -> Ordering { + self.cmp(cursor_location) } } @@ -405,6 +384,21 @@ mod tests { #[test] fn test_remove_between_and_path_successor() { + use std::path::{Path, PathBuf}; + + #[derive(Debug)] + pub struct PathDescendants<'a>(&'a Path); + + impl MapSeekTarget for PathDescendants<'_> { + fn cmp_cursor(&self, key: &PathBuf) -> Ordering { + if key.starts_with(&self.0) { + Ordering::Greater + } else { + self.0.cmp(key) + } + } + } + let mut map = TreeMap::default(); map.insert(PathBuf::from("a"), 1); @@ -415,7 +409,10 @@ mod tests { map.insert(PathBuf::from("c"), 5); map.insert(PathBuf::from("c/a"), 6); - map.remove_range(&PathBuf::from("b/a"), &PathDescendants(&PathBuf::from("b/a"))); + map.remove_range( + &PathBuf::from("b/a"), + &PathDescendants(&PathBuf::from("b/a")), + ); assert_eq!(map.get(&PathBuf::from("a")), Some(&1)); assert_eq!(map.get(&PathBuf::from("a/a")), Some(&1)); From 60320c6b095f9de717773c15d581e16495c816ea Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 12 May 2023 09:37:02 -0700 Subject: [PATCH 060/168] Send the root branch along with it's entry --- crates/rpc/src/proto.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 32f40ad7db..d74ed5e46c 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -487,17 +487,37 @@ pub fn split_worktree_update( let mut done_files = false; let mut done_statuses = false; let mut repository_index = 0; + let mut root_repo_found = false; iter::from_fn(move || { if done_files && done_statuses { return None; } let updated_entries_chunk_size = cmp::min(message.updated_entries.len(), max_chunk_size); - let updated_entries = message + let updated_entries: Vec<_> = message .updated_entries .drain(..updated_entries_chunk_size) .collect(); + let mut updated_repositories: Vec<_> = Default::default(); + + if !root_repo_found { + for entry in updated_entries.iter() { + if let Some(repo) = message.updated_repositories.get(0) { + if repo.work_directory_id == entry.id { + root_repo_found = true; + updated_repositories.push(RepositoryEntry { + work_directory_id: repo.work_directory_id, + branch: repo.branch.clone(), + removed_worktree_repo_paths: Default::default(), + updated_worktree_statuses: Default::default(), + }); + break; + } + } + } + } + let removed_entries_chunk_size = cmp::min(message.removed_entries.len(), max_chunk_size); let removed_entries = message .removed_entries @@ -508,9 +528,8 @@ pub fn split_worktree_update( // Wait to send repositories until after we've guaranteed that their associated entries // will be read - let updated_repositories = if done_files { + if done_files { let mut total_statuses = 0; - let mut updated_repositories = Vec::new(); while total_statuses < max_chunk_size && repository_index < message.updated_repositories.len() { @@ -555,8 +574,6 @@ pub fn split_worktree_update( repository_index += 1; } } - - updated_repositories } else { Default::default() }; From e71846c653ea797f03aea6db6d93052625fefa81 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 12 May 2023 10:12:47 -0700 Subject: [PATCH 061/168] Create pull_request_template.md --- .github/workflows/pull_request_template.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/workflows/pull_request_template.md diff --git a/.github/workflows/pull_request_template.md b/.github/workflows/pull_request_template.md new file mode 100644 index 0000000000..cd15ab5114 --- /dev/null +++ b/.github/workflows/pull_request_template.md @@ -0,0 +1,4 @@ +[[PR Description here]] + +Release Notes: +* [[Added support for foo / Fixed bar / No notes]] From 4663ac8abff7a8e7ab7d89b305b164797bc4550a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 12 May 2023 10:14:54 -0700 Subject: [PATCH 062/168] Create pull_request_template.md --- .github/pull_request_template.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..8d16a59bc1 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,5 @@ +[[PR Description]] + +Release Notes: + +* [[Added foo / Fixed bar / No notes]] From ad7ed56e6bb99cd8bcce9ee7c4ff5ab3092aafa0 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 12 May 2023 10:15:13 -0700 Subject: [PATCH 063/168] Delete pull_request_template.md --- .github/workflows/pull_request_template.md | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .github/workflows/pull_request_template.md diff --git a/.github/workflows/pull_request_template.md b/.github/workflows/pull_request_template.md deleted file mode 100644 index cd15ab5114..0000000000 --- a/.github/workflows/pull_request_template.md +++ /dev/null @@ -1,4 +0,0 @@ -[[PR Description here]] - -Release Notes: -* [[Added support for foo / Fixed bar / No notes]] From b70c874a0e695923b81fb176ce7e49b9130d5ebc Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Fri, 12 May 2023 14:04:36 -0400 Subject: [PATCH 064/168] Update release links --- .github/workflows/release_actions.yml | 2 +- crates/auto_update/src/auto_update.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index 4a9d777769..5feb29e469 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -14,7 +14,7 @@ jobs: content: | 📣 Zed ${{ github.event.release.tag_name }} was just released! - Restart your Zed or head to https://zed.dev/releases/latest to grab it. + Restart your Zed or head to https://zed.dev/releases/stable/latest to grab it. ```md # Changelog diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 68d3776e1c..89b70acec7 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -102,7 +102,7 @@ fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) { { format!("{server_url}/releases/preview/latest") } else { - format!("{server_url}/releases/latest") + format!("{server_url}/releases/stable/latest") }; cx.platform().open_url(&latest_release_url); } From 41bef2e444b96b012b5ebf7c156562921caf3702 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Sat, 13 May 2023 02:26:45 -0700 Subject: [PATCH 065/168] Refactor out git status into FileName component Integrate file name component into the editor's tab content --- Cargo.lock | 1 + crates/editor/src/items.rs | 23 ++++++++- crates/gpui/src/elements.rs | 9 ++++ crates/project_panel/src/project_panel.rs | 33 +++++-------- crates/theme/Cargo.toml | 1 + crates/theme/src/ui.rs | 57 +++++++++++++++++++++-- 6 files changed, 98 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 81e4a4e025..e009cfd342 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6688,6 +6688,7 @@ name = "theme" version = "0.1.0" dependencies = [ "anyhow", + "fs", "gpui", "indexmap", "parking_lot 0.11.2", diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index e971af943a..80c1009aa4 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -14,7 +14,7 @@ use language::{ proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, SelectionGoal, }; -use project::{FormatTrigger, Item as _, Project, ProjectPath}; +use project::{repository::GitFileStatus, FormatTrigger, Item as _, Project, ProjectPath}; use rpc::proto::{self, update_view}; use settings::Settings; use smallvec::SmallVec; @@ -27,6 +27,7 @@ use std::{ path::{Path, PathBuf}, }; use text::Selection; +use theme::ui::FileName; use util::{ResultExt, TryFutureExt}; use workspace::item::{BreadcrumbText, FollowableItemHandle}; use workspace::{ @@ -565,8 +566,25 @@ impl Item for Editor { style: &theme::Tab, cx: &AppContext, ) -> AnyElement { + fn git_file_status(this: &Editor, cx: &AppContext) -> Option { + let project_entry_id = this + .buffer() + .read(cx) + .as_singleton()? + .read(cx) + .entry_id(cx)?; + let project = this.project.as_ref()?.read(cx); + let path = project.path_for_entry(project_entry_id, cx)?.path; + let worktree = project.worktree_for_entry(project_entry_id, cx)?.read(cx); + worktree.repo_for(&path)?.status_for_path(&worktree, &path) + } + Flex::row() - .with_child(Label::new(self.title(cx).to_string(), style.label.clone()).aligned()) + .with_child(ComponentHost::new(FileName::new( + self.title(cx).to_string(), + git_file_status(self, cx), + FileName::style(style.label.clone(), &cx.global::().theme), + ))) .with_children(detail.and_then(|detail| { let path = path_for_buffer(&self.buffer, detail, false, cx)?; let description = path.to_string_lossy(); @@ -580,6 +598,7 @@ impl Item for Editor { .aligned(), ) })) + .align_children_center() .into_any() } diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index e2c4af143c..27b01a8db2 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -578,6 +578,15 @@ pub struct ComponentHost> { view_type: PhantomData, } +impl> ComponentHost { + pub fn new(c: C) -> Self { + Self { + component: c, + view_type: PhantomData, + } + } +} + impl> Deref for ComponentHost { type Target = C; diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 1066875022..bb7f97fbf8 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -6,7 +6,7 @@ use gpui::{ actions, anyhow::{anyhow, Result}, elements::{ - AnchorCorner, ChildView, ContainerStyle, Empty, Flex, Label, MouseEventHandler, + AnchorCorner, ChildView, ComponentHost, ContainerStyle, Empty, Flex, MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, }, geometry::vector::Vector2F, @@ -29,7 +29,7 @@ use std::{ path::Path, sync::Arc, }; -use theme::ProjectPanelEntry; +use theme::{ui::FileName, ProjectPanelEntry}; use unicase::UniCase; use workspace::Workspace; @@ -1083,19 +1083,6 @@ impl ProjectPanel { let kind = details.kind; let show_editor = details.is_editing && !details.is_processing; - // Prepare colors for git statuses - let editor_theme = &cx.global::().theme.editor; - let mut filename_text_style = style.text.clone(); - filename_text_style.color = details - .git_status - .as_ref() - .map(|status| match status { - GitFileStatus::Added => editor_theme.diff.inserted, - GitFileStatus::Modified => editor_theme.diff.modified, - GitFileStatus::Conflict => editor_theme.diff.deleted, - }) - .unwrap_or(style.text.color); - Flex::row() .with_child( if kind == EntryKind::Dir { @@ -1123,12 +1110,16 @@ impl ProjectPanel { .flex(1.0, true) .into_any() } else { - Label::new(details.filename.clone(), filename_text_style) - .contained() - .with_margin_left(style.icon_spacing) - .aligned() - .left() - .into_any() + ComponentHost::new(FileName::new( + details.filename.clone(), + details.git_status, + FileName::style(style.text.clone(), &cx.global::().theme), + )) + .contained() + .with_margin_left(style.icon_spacing) + .aligned() + .left() + .into_any() }) .constrained() .with_height(style.height) diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index 67a28397e2..c7dc2938ed 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -10,6 +10,7 @@ doctest = false [dependencies] gpui = { path = "../gpui" } +fs = { path = "../fs" } anyhow.workspace = true indexmap = "1.6.2" parking_lot.workspace = true diff --git a/crates/theme/src/ui.rs b/crates/theme/src/ui.rs index b86bfca8c4..e4df24c89f 100644 --- a/crates/theme/src/ui.rs +++ b/crates/theme/src/ui.rs @@ -1,9 +1,10 @@ use std::borrow::Cow; +use fs::repository::GitFileStatus; use gpui::{ color::Color, elements::{ - ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label, + ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label, LabelStyle, MouseEventHandler, ParentElement, Stack, Svg, }, fonts::TextStyle, @@ -11,11 +12,11 @@ use gpui::{ platform, platform::MouseButton, scene::MouseClick, - Action, Element, EventContext, MouseState, View, ViewContext, + Action, AnyElement, Element, EventContext, MouseState, View, ViewContext, }; use serde::Deserialize; -use crate::{ContainedText, Interactive}; +use crate::{ContainedText, Interactive, Theme}; #[derive(Clone, Deserialize, Default)] pub struct CheckboxStyle { @@ -252,3 +253,53 @@ where .constrained() .with_height(style.dimensions().y()) } + +pub struct FileName { + filename: String, + git_status: Option, + style: FileNameStyle, +} + +pub struct FileNameStyle { + template_style: LabelStyle, + git_inserted: Color, + git_modified: Color, + git_deleted: Color, +} + +impl FileName { + pub fn new(filename: String, git_status: Option, style: FileNameStyle) -> Self { + FileName { + filename, + git_status, + style, + } + } + + pub fn style>(style: I, theme: &Theme) -> FileNameStyle { + FileNameStyle { + template_style: style.into(), + git_inserted: theme.editor.diff.inserted, + git_modified: theme.editor.diff.modified, + git_deleted: theme.editor.diff.deleted, + } + } +} + +impl gpui::elements::Component for FileName { + fn render(&self, _: &mut V, _: &mut ViewContext) -> AnyElement { + // Prepare colors for git statuses + let mut filename_text_style = self.style.template_style.text.clone(); + filename_text_style.color = self + .git_status + .as_ref() + .map(|status| match status { + GitFileStatus::Added => self.style.git_inserted, + GitFileStatus::Modified => self.style.git_modified, + GitFileStatus::Conflict => self.style.git_deleted, + }) + .unwrap_or(self.style.template_style.text.color); + + Label::new(self.filename.clone(), filename_text_style).into_any() + } +} From 62c445da570e9a65a791c3535c4e371a3e395822 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Sat, 13 May 2023 02:30:59 -0700 Subject: [PATCH 066/168] Match priority of folder highlights to vscode --- crates/project/src/worktree.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index e713eed58d..b391ff829c 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -195,10 +195,10 @@ impl RepositoryEntry { (GitFileStatus::Conflict, _) | (_, GitFileStatus::Conflict) => { &GitFileStatus::Conflict } - (GitFileStatus::Added, _) | (_, GitFileStatus::Added) => { - &GitFileStatus::Added + (GitFileStatus::Modified, _) | (_, GitFileStatus::Modified) => { + &GitFileStatus::Modified } - _ => &GitFileStatus::Modified, + _ => &GitFileStatus::Added, }, ) .copied() From 04041af78b4831761b02ff91b4c714154fde60cc Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Sat, 13 May 2023 02:40:22 -0700 Subject: [PATCH 067/168] Fixed bug with failing to clear git file status --- crates/project/src/worktree.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index b391ff829c..c1f5178399 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3086,7 +3086,7 @@ impl BackgroundScanner { } let git_ptr = local_repo.repo_ptr.lock(); - git_ptr.worktree_status(&repo_path)? + git_ptr.worktree_status(&repo_path) }; let work_dir = repo.work_directory(snapshot)?; @@ -3097,7 +3097,11 @@ impl BackgroundScanner { .update(&work_dir_id, |entry| entry.scan_id = scan_id); snapshot.repository_entries.update(&work_dir, |entry| { - entry.worktree_statuses.insert(repo_path, status) + if let Some(status) = status { + entry.worktree_statuses.insert(repo_path, status); + } else { + entry.worktree_statuses.remove(&repo_path); + } }); } From 5e2aaf45a072f47cac3a7dd193f7a6b798fea422 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Sat, 13 May 2023 10:38:24 -0700 Subject: [PATCH 068/168] Fix repository initialization bug --- crates/editor/src/editor.rs | 15 +++++++++++++++ crates/project/src/worktree.rs | 13 ++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2bb9869e6d..b6d44397a9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1248,6 +1248,16 @@ impl Editor { let soft_wrap_mode_override = (mode == EditorMode::SingleLine).then(|| settings::SoftWrap::None); + + let mut project_subscription = None; + if mode == EditorMode::Full && buffer.read(cx).is_singleton() { + if let Some(project) = project.as_ref() { + project_subscription = Some(cx.observe(project, |_, _, cx| { + cx.emit(Event::TitleChanged); + })) + } + } + let mut this = Self { handle: cx.weak_handle(), buffer: buffer.clone(), @@ -1304,6 +1314,11 @@ impl Editor { cx.observe_global::(Self::settings_changed), ], }; + + if let Some(project_subscription) = project_subscription { + this._subscriptions.push(project_subscription); + } + this.end_selection(cx); this.scroll_manager.show_scrollbar(cx); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index c1f5178399..e5a1f9c93f 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2973,7 +2973,7 @@ impl BackgroundScanner { fs_entry.is_ignored = ignore_stack.is_all(); snapshot.insert_entry(fs_entry, self.fs.as_ref()); - self.reload_repo_for_path(&path, &mut snapshot); + self.reload_repo_for_path(&path, &mut snapshot, self.fs.as_ref()); if let Some(scan_queue_tx) = &scan_queue_tx { let mut ancestor_inodes = snapshot.ancestor_inodes_for_path(&path); @@ -3030,7 +3030,7 @@ impl BackgroundScanner { Some(()) } - fn reload_repo_for_path(&self, path: &Path, snapshot: &mut LocalSnapshot) -> Option<()> { + fn reload_repo_for_path(&self, path: &Path, snapshot: &mut LocalSnapshot, fs: &dyn Fs) -> Option<()> { let scan_id = snapshot.scan_id; if path @@ -3038,7 +3038,14 @@ impl BackgroundScanner { .any(|component| component.as_os_str() == *DOT_GIT) { let (entry_id, repo_ptr) = { - let (entry_id, repo) = snapshot.repo_for_metadata(&path)?; + let Some((entry_id, repo)) = snapshot.repo_for_metadata(&path) else { + let dot_git_dir = path.ancestors() + .skip_while(|ancestor| ancestor.file_name() != Some(&*DOT_GIT)) + .next()?; + + snapshot.build_repo(dot_git_dir.into(), fs); + return None; + }; if repo.full_scan_id == scan_id { return None; } From a6a4b846bc75554f3dda0984b930185395f1a2e3 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Sat, 13 May 2023 10:43:16 -0700 Subject: [PATCH 069/168] fmt --- crates/project/src/worktree.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index e5a1f9c93f..9b965eeea4 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3030,7 +3030,12 @@ impl BackgroundScanner { Some(()) } - fn reload_repo_for_path(&self, path: &Path, snapshot: &mut LocalSnapshot, fs: &dyn Fs) -> Option<()> { + fn reload_repo_for_path( + &self, + path: &Path, + snapshot: &mut LocalSnapshot, + fs: &dyn Fs, + ) -> Option<()> { let scan_id = snapshot.scan_id; if path From fa32adecd5e8970d2b876ad64646a44c2c4bf49b Mon Sep 17 00:00:00 2001 From: Julia Date: Sun, 14 May 2023 12:05:50 -0400 Subject: [PATCH 070/168] Fixup more, tests finally pass --- crates/project/src/project.rs | 168 ++++++++++++++++++--------------- crates/project/src/worktree.rs | 29 +++--- 2 files changed, 110 insertions(+), 87 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f4b5e728fb..b34cd0e1ce 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -123,6 +123,7 @@ pub struct Project { HashMap, Shared, Arc>>>>, opened_buffers: HashMap, local_buffer_ids_by_path: HashMap, + local_buffer_ids_by_entry_id: HashMap, /// A mapping from a buffer ID to None means that we've started waiting for an ID but haven't finished loading it. /// Used for re-issuing buffer requests when peers temporarily disconnect incomplete_remote_buffers: HashMap>>, @@ -451,6 +452,7 @@ impl Project { loading_buffers_by_path: Default::default(), loading_local_worktrees: Default::default(), local_buffer_ids_by_path: Default::default(), + local_buffer_ids_by_entry_id: Default::default(), buffer_snapshots: Default::default(), join_project_response_message_id: 0, client_state: None, @@ -520,6 +522,7 @@ impl Project { incomplete_remote_buffers: Default::default(), loading_local_worktrees: Default::default(), local_buffer_ids_by_path: Default::default(), + local_buffer_ids_by_entry_id: Default::default(), active_entry: None, collaborators: Default::default(), join_project_response_message_id: response.message_id, @@ -1640,6 +1643,9 @@ impl Project { }, remote_id, ); + + self.local_buffer_ids_by_entry_id + .insert(file.entry_id, remote_id); } } @@ -4574,96 +4580,106 @@ impl Project { fn update_local_worktree_buffers( &mut self, worktree_handle: &ModelHandle, - changes: &HashMap, PathChange>, + changes: &HashMap<(Arc, ProjectEntryId), PathChange>, cx: &mut ModelContext, ) { let snapshot = worktree_handle.read(cx).snapshot(); let mut renamed_buffers = Vec::new(); - for path in changes.keys() { + for (path, entry_id) in changes.keys() { let worktree_id = worktree_handle.read(cx).id(); let project_path = ProjectPath { worktree_id, path: path.clone(), }; - if let Some(&buffer_id) = self.local_buffer_ids_by_path.get(&project_path) { - if let Some(buffer) = self - .opened_buffers - .get(&buffer_id) - .and_then(|buffer| buffer.upgrade(cx)) - { - buffer.update(cx, |buffer, cx| { - if let Some(old_file) = File::from_dyn(buffer.file()) { - if old_file.worktree != *worktree_handle { - return; - } + let buffer_id = match self.local_buffer_ids_by_entry_id.get(entry_id) { + Some(&buffer_id) => buffer_id, + None => match self.local_buffer_ids_by_path.get(&project_path) { + Some(&buffer_id) => buffer_id, + None => continue, + }, + }; - let new_file = - if let Some(entry) = snapshot.entry_for_id(old_file.entry_id) { - File { - is_local: true, - entry_id: entry.id, - mtime: entry.mtime, - path: entry.path.clone(), - worktree: worktree_handle.clone(), - is_deleted: false, - } - } else if let Some(entry) = - snapshot.entry_for_path(old_file.path().as_ref()) - { - File { - is_local: true, - entry_id: entry.id, - mtime: entry.mtime, - path: entry.path.clone(), - worktree: worktree_handle.clone(), - is_deleted: false, - } - } else { - File { - is_local: true, - entry_id: old_file.entry_id, - path: old_file.path().clone(), - mtime: old_file.mtime(), - worktree: worktree_handle.clone(), - is_deleted: true, - } - }; + let open_buffer = self.opened_buffers.get(&buffer_id); + let buffer = if let Some(buffer) = open_buffer.and_then(|buffer| buffer.upgrade(cx)) { + buffer + } else { + self.opened_buffers.remove(&buffer_id); + self.local_buffer_ids_by_path.remove(&project_path); + self.local_buffer_ids_by_entry_id.remove(entry_id); + continue; + }; - let old_path = old_file.abs_path(cx); - if new_file.abs_path(cx) != old_path { - renamed_buffers.push((cx.handle(), old_file.clone())); - self.local_buffer_ids_by_path.remove(&project_path); - self.local_buffer_ids_by_path.insert( - ProjectPath { - worktree_id, - path: path.clone(), - }, - buffer_id, - ); - } + buffer.update(cx, |buffer, cx| { + if let Some(old_file) = File::from_dyn(buffer.file()) { + if old_file.worktree != *worktree_handle { + return; + } - if new_file != *old_file { - if let Some(project_id) = self.remote_id() { - self.client - .send(proto::UpdateBufferFile { - project_id, - buffer_id: buffer_id as u64, - file: Some(new_file.to_proto()), - }) - .log_err(); - } - - buffer.file_updated(Arc::new(new_file), cx).detach(); - } + let new_file = if let Some(entry) = snapshot.entry_for_id(old_file.entry_id) { + File { + is_local: true, + entry_id: entry.id, + mtime: entry.mtime, + path: entry.path.clone(), + worktree: worktree_handle.clone(), + is_deleted: false, } - }); - } else { - self.opened_buffers.remove(&buffer_id); - self.local_buffer_ids_by_path.remove(&project_path); + } else if let Some(entry) = snapshot.entry_for_path(old_file.path().as_ref()) { + File { + is_local: true, + entry_id: entry.id, + mtime: entry.mtime, + path: entry.path.clone(), + worktree: worktree_handle.clone(), + is_deleted: false, + } + } else { + File { + is_local: true, + entry_id: old_file.entry_id, + path: old_file.path().clone(), + mtime: old_file.mtime(), + worktree: worktree_handle.clone(), + is_deleted: true, + } + }; + + let old_path = old_file.abs_path(cx); + if new_file.abs_path(cx) != old_path { + renamed_buffers.push((cx.handle(), old_file.clone())); + self.local_buffer_ids_by_path.remove(&project_path); + self.local_buffer_ids_by_path.insert( + ProjectPath { + worktree_id, + path: path.clone(), + }, + buffer_id, + ); + } + + if new_file.entry_id != *entry_id { + self.local_buffer_ids_by_entry_id.remove(entry_id); + self.local_buffer_ids_by_entry_id + .insert(new_file.entry_id, buffer_id); + } + + if new_file != *old_file { + if let Some(project_id) = self.remote_id() { + self.client + .send(proto::UpdateBufferFile { + project_id, + buffer_id: buffer_id as u64, + file: Some(new_file.to_proto()), + }) + .log_err(); + } + + buffer.file_updated(Arc::new(new_file), cx).detach(); + } } - } + }); } for (buffer, old_file) in renamed_buffers { @@ -4676,7 +4692,7 @@ impl Project { fn update_local_worktree_language_servers( &mut self, worktree_handle: &ModelHandle, - changes: &HashMap, PathChange>, + changes: &HashMap<(Arc, ProjectEntryId), PathChange>, cx: &mut ModelContext, ) { let worktree_id = worktree_handle.read(cx).id(); @@ -4693,7 +4709,7 @@ impl Project { let params = lsp::DidChangeWatchedFilesParams { changes: changes .iter() - .filter_map(|(path, change)| { + .filter_map(|((path, _), change)| { let path = abs_path.join(path); if watched_paths.matches(&path) { Some(lsp::FileEvent { diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 895eafac30..a07673e95c 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -265,7 +265,7 @@ enum ScanState { Started, Updated { snapshot: LocalSnapshot, - changes: HashMap, PathChange>, + changes: HashMap<(Arc, ProjectEntryId), PathChange>, barrier: Option, scanning: bool, }, @@ -279,7 +279,7 @@ struct ShareState { } pub enum Event { - UpdatedEntries(HashMap, PathChange>), + UpdatedEntries(HashMap<(Arc, ProjectEntryId), PathChange>), UpdatedGitRepositories(HashMap, LocalRepositoryEntry>), } @@ -3039,7 +3039,7 @@ impl BackgroundScanner { old_snapshot: &Snapshot, new_snapshot: &Snapshot, event_paths: &[Arc], - ) -> HashMap, PathChange> { + ) -> HashMap<(Arc, ProjectEntryId), PathChange> { use PathChange::{Added, AddedOrUpdated, Removed, Updated}; let mut changes = HashMap::default(); @@ -3065,7 +3065,7 @@ impl BackgroundScanner { match Ord::cmp(&old_entry.path, &new_entry.path) { Ordering::Less => { - changes.insert(old_entry.path.clone(), Removed); + changes.insert((old_entry.path.clone(), old_entry.id), Removed); old_paths.next(&()); } Ordering::Equal => { @@ -3073,31 +3073,35 @@ impl BackgroundScanner { // If the worktree was not fully initialized when this event was generated, // we can't know whether this entry was added during the scan or whether // it was merely updated. - changes.insert(new_entry.path.clone(), AddedOrUpdated); + changes.insert( + (new_entry.path.clone(), new_entry.id), + AddedOrUpdated, + ); } else if old_entry.mtime != new_entry.mtime { - changes.insert(new_entry.path.clone(), Updated); + changes.insert((new_entry.path.clone(), new_entry.id), Updated); } old_paths.next(&()); new_paths.next(&()); } Ordering::Greater => { - changes.insert(new_entry.path.clone(), Added); + changes.insert((new_entry.path.clone(), new_entry.id), Added); new_paths.next(&()); } } } (Some(old_entry), None) => { - changes.insert(old_entry.path.clone(), Removed); + changes.insert((old_entry.path.clone(), old_entry.id), Removed); old_paths.next(&()); } (None, Some(new_entry)) => { - changes.insert(new_entry.path.clone(), Added); + changes.insert((new_entry.path.clone(), new_entry.id), Added); new_paths.next(&()); } (None, None) => break, } } } + changes } @@ -3937,7 +3941,7 @@ mod tests { cx.subscribe(&worktree, move |tree, _, event, _| { if let Event::UpdatedEntries(changes) = event { - for (path, change_type) in changes.iter() { + for ((path, _), change_type) in changes.iter() { let path = path.clone(); let ix = match paths.binary_search(&path) { Ok(ix) | Err(ix) => ix, @@ -3947,13 +3951,16 @@ mod tests { assert_ne!(paths.get(ix), Some(&path)); paths.insert(ix, path); } + PathChange::Removed => { assert_eq!(paths.get(ix), Some(&path)); paths.remove(ix); } + PathChange::Updated => { assert_eq!(paths.get(ix), Some(&path)); } + PathChange::AddedOrUpdated => { if paths[ix] != path { paths.insert(ix, path); @@ -3961,6 +3968,7 @@ mod tests { } } } + let new_paths = tree.paths().cloned().collect::>(); assert_eq!(paths, new_paths, "incorrect changes: {:?}", changes); } @@ -3970,7 +3978,6 @@ mod tests { let mut snapshots = Vec::new(); let mut mutations_len = operations; - fs.as_fake().pause_events().await; while mutations_len > 1 { if rng.gen_bool(0.2) { worktree From 4f36ba3b1ed22fcf7dcc449a57593571a255caf0 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 14 May 2023 22:06:33 +0300 Subject: [PATCH 071/168] Add a job to build Zed images from current main The job triggers on every commit to `main` or every PR with `run-build-dmg` label and produces an install-ready *.dmg artifact attached to the corresponding CI run. --- .github/workflows/build_dmg.yml | 49 +++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/build_dmg.yml diff --git a/.github/workflows/build_dmg.yml b/.github/workflows/build_dmg.yml new file mode 100644 index 0000000000..6558790fae --- /dev/null +++ b/.github/workflows/build_dmg.yml @@ -0,0 +1,49 @@ +on: + push: + branches: + - main + pull_request: + +defaults: + run: + shell: bash -euxo pipefail {0} + +concurrency: + # Allow only one workflow per any non-`main` branch. + group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }} + cancel-in-progress: true + +env: + RUST_BACKTRACE: 1 + COPT: '-Werror' + +jobs: + build-dmg: + if: github.ref_name == 'main' || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') + env: + SHA: ${{ github.event.pull_request.head.sha || github.sha }} + runs-on: + - self-hosted + - test + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + clean: false + submodules: 'recursive' + + - name: Install Rust + run: | + rustup set profile minimal + rustup update stable + + + - name: Build dmg bundle + run: ./script/bundle + + - name: Upload the build stats + uses: actions/upload-artifact@v3 + with: + name: zed-main-$SHA.dmg + path: ./target/release/Zed.dmg From 5465948f200b55dd4dca0a2afeb1045ef9be359b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 14 May 2023 22:09:28 +0300 Subject: [PATCH 072/168] Build Zed dmg --- .github/workflows/build_dmg.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_dmg.yml b/.github/workflows/build_dmg.yml index 6558790fae..989914e5e8 100644 --- a/.github/workflows/build_dmg.yml +++ b/.github/workflows/build_dmg.yml @@ -1,7 +1,10 @@ +name: Build Zed.dmg + on: push: branches: - main + - "v[0-9]+.[0-9]+.x" pull_request: defaults: @@ -20,8 +23,6 @@ env: jobs: build-dmg: if: github.ref_name == 'main' || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') - env: - SHA: ${{ github.event.pull_request.head.sha || github.sha }} runs-on: - self-hosted - test @@ -38,12 +39,16 @@ jobs: rustup set profile minimal rustup update stable + - name: Install node + uses: actions/setup-node@v3 + with: + node-version: 18 - name: Build dmg bundle run: ./script/bundle - - name: Upload the build stats + - name: Upload the build artifact uses: actions/upload-artifact@v3 with: - name: zed-main-$SHA.dmg + name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg path: ./target/release/Zed.dmg From 18e0ee44a68454e8e2abd94225da422f7aa6d1ec Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 14 May 2023 13:37:03 +0300 Subject: [PATCH 073/168] Remove redundant scopes and actions to fix the focus toggle on ESC co-authored-by: Antonio --- assets/keymaps/default.json | 6 +++--- crates/gpui/src/keymap_matcher/binding.rs | 13 +++++++++++++ crates/workspace/src/workspace.rs | 6 ------ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index d2fd4107e4..01a09e0cba 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -191,7 +191,7 @@ } }, { - "context": "BufferSearchBar > Editor", + "context": "BufferSearchBar", "bindings": { "escape": "buffer_search::Dismiss", "tab": "buffer_search::FocusEditor", @@ -200,13 +200,13 @@ } }, { - "context": "ProjectSearchBar > Editor", + "context": "ProjectSearchBar", "bindings": { "escape": "project_search::ToggleFocus" } }, { - "context": "ProjectSearchView > Editor", + "context": "ProjectSearchView", "bindings": { "escape": "project_search::ToggleFocus" } diff --git a/crates/gpui/src/keymap_matcher/binding.rs b/crates/gpui/src/keymap_matcher/binding.rs index aa40e8c6af..4d8334128b 100644 --- a/crates/gpui/src/keymap_matcher/binding.rs +++ b/crates/gpui/src/keymap_matcher/binding.rs @@ -11,6 +11,19 @@ pub struct Binding { context_predicate: Option, } +impl std::fmt::Debug for Binding { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Binding {{ keystrokes: {:?}, action: {}::{}, context_predicate: {:?} }}", + self.keystrokes, + self.action.namespace(), + self.action.name(), + self.context_predicate + ) + } +} + impl Clone for Binding { fn clone(&self) -> Self { Self { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8a62a28c11..6350b43415 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -234,7 +234,6 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { }, ); cx.add_action(Workspace::toggle_sidebar_item); - cx.add_action(Workspace::focus_center); cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { workspace.activate_previous_pane(cx) }); @@ -1415,11 +1414,6 @@ impl Workspace { cx.notify(); } - pub fn focus_center(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { - cx.focus_self(); - cx.notify(); - } - fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { let pane = cx.add_view(|cx| { Pane::new( From 6a7d7183814a33b188bd60094d2046fc2dfcc3f4 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 15 May 2023 14:12:02 -0400 Subject: [PATCH 074/168] Update jetbrains keymap --- assets/keymaps/jetbrains.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assets/keymaps/jetbrains.json b/assets/keymaps/jetbrains.json index 59e069e7f7..383de07904 100644 --- a/assets/keymaps/jetbrains.json +++ b/assets/keymaps/jetbrains.json @@ -11,6 +11,7 @@ "ctrl->": "zed::IncreaseBufferFontSize", "ctrl-<": "zed::DecreaseBufferFontSize", "cmd-d": "editor::DuplicateLine", + "cmd-backspace": "editor::DeleteLine", "cmd-pagedown": "editor::MovePageDown", "cmd-pageup": "editor::MovePageUp", "ctrl-alt-shift-b": "editor::SelectToPreviousWordStart", @@ -33,6 +34,7 @@ ], "shift-alt-up": "editor::MoveLineUp", "shift-alt-down": "editor::MoveLineDown", + "cmd-alt-l": "editor::Format", "cmd-[": "pane::GoBack", "cmd-]": "pane::GoForward", "alt-f7": "editor::FindAllReferences", @@ -63,6 +65,7 @@ { "context": "Workspace", "bindings": { + "cmd-shift-o": "file_finder::Toggle", "cmd-shift-a": "command_palette::Toggle", "cmd-alt-o": "project_symbols::Toggle", "cmd-1": "workspace::ToggleLeftSidebar", From 2b18975cdc077e77880867f84d764328351d6335 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 May 2023 09:41:56 -0700 Subject: [PATCH 075/168] Change folder styling from a reduce over all child files to a simple 'always modified' Remove git status from tab titles --- crates/editor/src/items.rs | 22 ++-------------------- crates/project/src/worktree.rs | 26 ++++++++------------------ 2 files changed, 10 insertions(+), 38 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 80c1009aa4..d2b9c20803 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -14,7 +14,7 @@ use language::{ proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, SelectionGoal, }; -use project::{repository::GitFileStatus, FormatTrigger, Item as _, Project, ProjectPath}; +use project::{FormatTrigger, Item as _, Project, ProjectPath}; use rpc::proto::{self, update_view}; use settings::Settings; use smallvec::SmallVec; @@ -27,7 +27,6 @@ use std::{ path::{Path, PathBuf}, }; use text::Selection; -use theme::ui::FileName; use util::{ResultExt, TryFutureExt}; use workspace::item::{BreadcrumbText, FollowableItemHandle}; use workspace::{ @@ -566,25 +565,8 @@ impl Item for Editor { style: &theme::Tab, cx: &AppContext, ) -> AnyElement { - fn git_file_status(this: &Editor, cx: &AppContext) -> Option { - let project_entry_id = this - .buffer() - .read(cx) - .as_singleton()? - .read(cx) - .entry_id(cx)?; - let project = this.project.as_ref()?.read(cx); - let path = project.path_for_entry(project_entry_id, cx)?.path; - let worktree = project.worktree_for_entry(project_entry_id, cx)?.read(cx); - worktree.repo_for(&path)?.status_for_path(&worktree, &path) - } - Flex::row() - .with_child(ComponentHost::new(FileName::new( - self.title(cx).to_string(), - git_file_status(self, cx), - FileName::style(style.label.clone(), &cx.global::().theme), - ))) + .with_child(Label::new(self.title(cx).to_string(), style.label.clone()).into_any()) .with_children(detail.and_then(|detail| { let path = path_for_buffer(&self.buffer, detail, false, cx)?; let description = path.to_string_lossy(); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index bfd4eaa43f..cb00fc5c41 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -55,7 +55,7 @@ use std::{ time::{Duration, SystemTime}, }; use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet}; -use util::{paths::HOME, ResultExt, TakeUntilExt, TryFutureExt}; +use util::{paths::HOME, ResultExt, TryFutureExt}; #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] pub struct WorktreeId(usize); @@ -187,20 +187,12 @@ impl RepositoryEntry { self.worktree_statuses .iter_from(&repo_path) .take_while(|(key, _)| key.starts_with(&repo_path)) - .map(|(_, status)| status) - // Short circut once we've found the highest level - .take_until(|status| status == &&GitFileStatus::Conflict) - .reduce( - |status_first, status_second| match (status_first, status_second) { - (GitFileStatus::Conflict, _) | (_, GitFileStatus::Conflict) => { - &GitFileStatus::Conflict - } - (GitFileStatus::Modified, _) | (_, GitFileStatus::Modified) => { - &GitFileStatus::Modified - } - _ => &GitFileStatus::Added, - }, - ) + .map(|(path, status)| if path == &repo_path { + status + } else { + &GitFileStatus::Modified + }) + .next() .copied() }) } @@ -4170,15 +4162,13 @@ mod tests { tree.flush_fs_events(cx).await; - dbg!(git_status(&repo)); + git_status(&repo); // Check that non-repo behavior is tracked tree.read_with(cx, |tree, _cx| { let snapshot = tree.snapshot(); let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - dbg!(&repo.worktree_statuses); - assert_eq!(repo.worktree_statuses.iter().count(), 0); assert_eq!(repo.worktree_statuses.get(&Path::new(A_TXT).into()), None); assert_eq!(repo.worktree_statuses.get(&Path::new(B_TXT).into()), None); From 6c26f3d0e4d0135e49f7073f8a0412f175df5abf Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 May 2023 09:48:27 -0700 Subject: [PATCH 076/168] Fixed formatting --- crates/project/src/worktree.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index cb00fc5c41..5216db76f6 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -187,10 +187,12 @@ impl RepositoryEntry { self.worktree_statuses .iter_from(&repo_path) .take_while(|(key, _)| key.starts_with(&repo_path)) - .map(|(path, status)| if path == &repo_path { - status - } else { - &GitFileStatus::Modified + .map(|(path, status)| { + if path == &repo_path { + status + } else { + &GitFileStatus::Modified + } }) .next() .copied() @@ -4162,8 +4164,6 @@ mod tests { tree.flush_fs_events(cx).await; - git_status(&repo); - // Check that non-repo behavior is tracked tree.read_with(cx, |tree, _cx| { let snapshot = tree.snapshot(); From 1e4ab6cd75f83dfe7d920da4b23d7bd663a842ea Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 May 2023 12:00:12 -0700 Subject: [PATCH 077/168] Add index tracking to status --- crates/fs/src/repository.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 51b69b8bc7..4163dbab90 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -100,9 +100,9 @@ impl GitRepository for LibGitRepository { fn read_status(status: git2::Status) -> Option { if status.contains(git2::Status::CONFLICTED) { Some(GitFileStatus::Conflict) - } else if status.intersects(git2::Status::WT_MODIFIED | git2::Status::WT_RENAMED) { + } else if status.intersects(git2::Status::WT_MODIFIED | git2::Status::WT_RENAMED | git2::Status::INDEX_MODIFIED | git2::Status::INDEX_RENAMED) { Some(GitFileStatus::Modified) - } else if status.intersects(git2::Status::WT_NEW) { + } else if status.intersects(git2::Status::WT_NEW | git2::Status::INDEX_NEW) { Some(GitFileStatus::Added) } else { None From 307dd2b83e240ab23594735a6638fe3bd1bdd5fc Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 May 2023 13:40:55 -0700 Subject: [PATCH 078/168] Update proto names to reflect new status info --- crates/collab/src/db.rs | 54 +++++++++------------ crates/fs/src/repository.rs | 19 +++++--- crates/project/src/worktree.rs | 86 +++++++++++++++++----------------- crates/rpc/proto/zed.proto | 4 +- crates/rpc/src/proto.rs | 17 ++++--- 5 files changed, 88 insertions(+), 92 deletions(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 1047b207b9..453aa82b53 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -1569,8 +1569,8 @@ impl Database { worktree.updated_repositories.push(proto::RepositoryEntry { work_directory_id: db_repository.work_directory_id as u64, branch: db_repository.branch, - removed_worktree_repo_paths: Default::default(), - updated_worktree_statuses: Default::default(), + removed_repo_paths: Default::default(), + updated_statuses: Default::default(), }); } } @@ -1607,15 +1607,13 @@ impl Database { let db_status_entry = db_status_entry?; if db_status_entry.is_deleted { repository - .removed_worktree_repo_paths + .removed_repo_paths .push(db_status_entry.repo_path); } else { - repository - .updated_worktree_statuses - .push(proto::StatusEntry { - repo_path: db_status_entry.repo_path, - status: db_status_entry.status as i32, - }); + repository.updated_statuses.push(proto::StatusEntry { + repo_path: db_status_entry.repo_path, + status: db_status_entry.status as i32, + }); } } } @@ -2444,12 +2442,10 @@ impl Database { .await?; for repository in update.updated_repositories.iter() { - if !repository.updated_worktree_statuses.is_empty() { + if !repository.updated_statuses.is_empty() { worktree_repository_statuses::Entity::insert_many( - repository - .updated_worktree_statuses - .iter() - .map(|status_entry| worktree_repository_statuses::ActiveModel { + repository.updated_statuses.iter().map(|status_entry| { + worktree_repository_statuses::ActiveModel { project_id: ActiveValue::set(project_id), worktree_id: ActiveValue::set(worktree_id), work_directory_id: ActiveValue::set( @@ -2459,7 +2455,8 @@ impl Database { status: ActiveValue::set(status_entry.status as i64), scan_id: ActiveValue::set(update.scan_id as i64), is_deleted: ActiveValue::set(false), - }), + } + }), ) .on_conflict( OnConflict::columns([ @@ -2479,7 +2476,7 @@ impl Database { .await?; } - if !repository.removed_worktree_repo_paths.is_empty() { + if !repository.removed_repo_paths.is_empty() { worktree_repository_statuses::Entity::update_many() .filter( worktree_repository_statuses::Column::ProjectId @@ -2492,14 +2489,9 @@ impl Database { worktree_repository_statuses::Column::WorkDirectoryId .eq(repository.work_directory_id as i64), ) - .and( - worktree_repository_statuses::Column::RepoPath.is_in( - repository - .removed_worktree_repo_paths - .iter() - .map(String::as_str), - ), - ), + .and(worktree_repository_statuses::Column::RepoPath.is_in( + repository.removed_repo_paths.iter().map(String::as_str), + )), ) .set(worktree_repository_statuses::ActiveModel { is_deleted: ActiveValue::Set(true), @@ -2765,8 +2757,8 @@ impl Database { proto::RepositoryEntry { work_directory_id: db_repository_entry.work_directory_id as u64, branch: db_repository_entry.branch, - removed_worktree_repo_paths: Default::default(), - updated_worktree_statuses: Default::default(), + removed_repo_paths: Default::default(), + updated_statuses: Default::default(), }, ); } @@ -2791,12 +2783,10 @@ impl Database { .repository_entries .get_mut(&(db_status_entry.work_directory_id as u64)) { - repository_entry - .updated_worktree_statuses - .push(proto::StatusEntry { - repo_path: db_status_entry.repo_path, - status: db_status_entry.status as i32, - }); + repository_entry.updated_statuses.push(proto::StatusEntry { + repo_path: db_status_entry.repo_path, + status: db_status_entry.status as i32, + }); } } } diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 4163dbab90..2c309351fc 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -22,9 +22,9 @@ pub trait GitRepository: Send { fn branch_name(&self) -> Option; - fn worktree_statuses(&self) -> Option>; + fn statuses(&self) -> Option>; - fn worktree_status(&self, path: &RepoPath) -> Option; + fn status(&self, path: &RepoPath) -> Option; } impl std::fmt::Debug for dyn GitRepository { @@ -71,7 +71,7 @@ impl GitRepository for LibGitRepository { Some(branch.to_string()) } - fn worktree_statuses(&self) -> Option> { + fn statuses(&self) -> Option> { let statuses = self.statuses(None).log_err()?; let mut map = TreeMap::default(); @@ -91,7 +91,7 @@ impl GitRepository for LibGitRepository { Some(map) } - fn worktree_status(&self, path: &RepoPath) -> Option { + fn status(&self, path: &RepoPath) -> Option { let status = self.status_file(path).log_err()?; read_status(status) } @@ -100,7 +100,12 @@ impl GitRepository for LibGitRepository { fn read_status(status: git2::Status) -> Option { if status.contains(git2::Status::CONFLICTED) { Some(GitFileStatus::Conflict) - } else if status.intersects(git2::Status::WT_MODIFIED | git2::Status::WT_RENAMED | git2::Status::INDEX_MODIFIED | git2::Status::INDEX_RENAMED) { + } else if status.intersects( + git2::Status::WT_MODIFIED + | git2::Status::WT_RENAMED + | git2::Status::INDEX_MODIFIED + | git2::Status::INDEX_RENAMED, + ) { Some(GitFileStatus::Modified) } else if status.intersects(git2::Status::WT_NEW | git2::Status::INDEX_NEW) { Some(GitFileStatus::Added) @@ -141,7 +146,7 @@ impl GitRepository for FakeGitRepository { state.branch_name.clone() } - fn worktree_statuses(&self) -> Option> { + fn statuses(&self) -> Option> { let state = self.state.lock(); let mut map = TreeMap::default(); for (repo_path, status) in state.worktree_statuses.iter() { @@ -150,7 +155,7 @@ impl GitRepository for FakeGitRepository { Some(map) } - fn worktree_status(&self, path: &RepoPath) -> Option { + fn status(&self, path: &RepoPath) -> Option { let state = self.state.lock(); state.worktree_statuses.get(path).cloned() } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 5216db76f6..9c214b7ecf 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -143,7 +143,7 @@ impl Snapshot { pub struct RepositoryEntry { pub(crate) work_directory: WorkDirectoryEntry, pub(crate) branch: Option>, - pub(crate) worktree_statuses: TreeMap, + pub(crate) statuses: TreeMap, } fn read_git_status(git_status: i32) -> Option { @@ -176,7 +176,7 @@ impl RepositoryEntry { pub fn status_for_file(&self, snapshot: &Snapshot, path: &Path) -> Option { self.work_directory .relativize(snapshot, path) - .and_then(|repo_path| self.worktree_statuses.get(&repo_path)) + .and_then(|repo_path| self.statuses.get(&repo_path)) .cloned() } @@ -184,7 +184,7 @@ impl RepositoryEntry { self.work_directory .relativize(snapshot, path) .and_then(|repo_path| { - self.worktree_statuses + self.statuses .iter_from(&repo_path) .take_while(|(key, _)| key.starts_with(&repo_path)) .map(|(path, status)| { @@ -203,8 +203,8 @@ impl RepositoryEntry { let mut updated_statuses: Vec = Vec::new(); let mut removed_statuses: Vec = Vec::new(); - let mut self_statuses = self.worktree_statuses.iter().peekable(); - let mut other_statuses = other.worktree_statuses.iter().peekable(); + let mut self_statuses = self.statuses.iter().peekable(); + let mut other_statuses = other.statuses.iter().peekable(); loop { match (self_statuses.peek(), other_statuses.peek()) { (Some((self_repo_path, self_status)), Some((other_repo_path, other_status))) => { @@ -243,8 +243,8 @@ impl RepositoryEntry { proto::RepositoryEntry { work_directory_id: self.work_directory_id().to_proto(), branch: self.branch.as_ref().map(|str| str.to_string()), - removed_worktree_repo_paths: removed_statuses, - updated_worktree_statuses: updated_statuses, + removed_repo_paths: removed_statuses, + updated_statuses: updated_statuses, } } } @@ -269,12 +269,12 @@ impl From<&RepositoryEntry> for proto::RepositoryEntry { proto::RepositoryEntry { work_directory_id: value.work_directory.to_proto(), branch: value.branch.as_ref().map(|str| str.to_string()), - updated_worktree_statuses: value - .worktree_statuses + updated_statuses: value + .statuses .iter() .map(|(repo_path, status)| make_status_entry(repo_path, status)) .collect(), - removed_worktree_repo_paths: Default::default(), + removed_repo_paths: Default::default(), } } } @@ -1540,7 +1540,7 @@ impl Snapshot { if let Some(entry) = self.entry_for_id(*work_directory_entry) { let mut statuses = TreeMap::default(); - for status_entry in repository.updated_worktree_statuses { + for status_entry in repository.updated_statuses { let Some(git_file_status) = read_git_status(status_entry.status) else { continue; }; @@ -1553,11 +1553,11 @@ impl Snapshot { if self.repository_entries.get(&work_directory).is_some() { self.repository_entries.update(&work_directory, |repo| { repo.branch = repository.branch.map(Into::into); - repo.worktree_statuses.insert_tree(statuses); + repo.statuses.insert_tree(statuses); - for repo_path in repository.removed_worktree_repo_paths { + for repo_path in repository.removed_repo_paths { let repo_path = RepoPath::new(repo_path.into()); - repo.worktree_statuses.remove(&repo_path); + repo.statuses.remove(&repo_path); } }); } else { @@ -1566,7 +1566,7 @@ impl Snapshot { RepositoryEntry { work_directory: work_directory_entry, branch: repository.branch.map(Into::into), - worktree_statuses: statuses, + statuses, }, ) } @@ -1982,7 +1982,7 @@ impl LocalSnapshot { RepositoryEntry { work_directory: work_dir_id.into(), branch: repo_lock.branch_name().map(Into::into), - worktree_statuses: repo_lock.worktree_statuses().unwrap_or_default(), + statuses: repo_lock.statuses().unwrap_or_default(), }, ); drop(repo_lock); @@ -2681,6 +2681,8 @@ impl BackgroundScanner { self.update_ignore_statuses().await; + // + let mut snapshot = self.snapshot.lock(); let mut git_repositories = mem::take(&mut snapshot.git_repositories); @@ -2993,7 +2995,7 @@ impl BackgroundScanner { fs_entry.is_ignored = ignore_stack.is_all(); snapshot.insert_entry(fs_entry, self.fs.as_ref()); - self.reload_repo_for_path(&path, &mut snapshot, self.fs.as_ref()); + self.reload_repo_for_file_path(&path, &mut snapshot, self.fs.as_ref()); if let Some(scan_queue_tx) = &scan_queue_tx { let mut ancestor_inodes = snapshot.ancestor_inodes_for_path(&path); @@ -3042,7 +3044,7 @@ impl BackgroundScanner { snapshot.repository_entries.update(&work_dir, |entry| { entry - .worktree_statuses + .statuses .remove_range(&repo_path, &RepoPathDescendants(&repo_path)) }); } @@ -3050,7 +3052,7 @@ impl BackgroundScanner { Some(()) } - fn reload_repo_for_path( + fn reload_repo_for_file_path( &self, path: &Path, snapshot: &mut LocalSnapshot, @@ -3084,7 +3086,7 @@ impl BackgroundScanner { let repo = repo_ptr.lock(); repo.reload_index(); let branch = repo.branch_name(); - let statuses = repo.worktree_statuses().unwrap_or_default(); + let statuses = repo.statuses().unwrap_or_default(); snapshot.git_repositories.update(&entry_id, |entry| { entry.scan_id = scan_id; @@ -3093,7 +3095,7 @@ impl BackgroundScanner { snapshot.repository_entries.update(&work_dir, |entry| { entry.branch = branch.map(Into::into); - entry.worktree_statuses = statuses; + entry.statuses = statuses; }); } else { if snapshot @@ -3118,7 +3120,7 @@ impl BackgroundScanner { } let git_ptr = local_repo.repo_ptr.lock(); - git_ptr.worktree_status(&repo_path) + git_ptr.status(&repo_path) }; let work_dir = repo.work_directory(snapshot)?; @@ -3130,9 +3132,9 @@ impl BackgroundScanner { snapshot.repository_entries.update(&work_dir, |entry| { if let Some(status) = status { - entry.worktree_statuses.insert(repo_path, status); + entry.statuses.insert(repo_path, status); } else { - entry.worktree_statuses.remove(&repo_path); + entry.statuses.remove(&repo_path); } }); } @@ -4089,17 +4091,17 @@ mod tests { let (dir, repo) = snapshot.repository_entries.iter().next().unwrap(); assert_eq!(dir.0.as_ref(), Path::new("project")); - assert_eq!(repo.worktree_statuses.iter().count(), 3); + assert_eq!(repo.statuses.iter().count(), 3); assert_eq!( - repo.worktree_statuses.get(&Path::new(A_TXT).into()), + repo.statuses.get(&Path::new(A_TXT).into()), Some(&GitFileStatus::Modified) ); assert_eq!( - repo.worktree_statuses.get(&Path::new(B_TXT).into()), + repo.statuses.get(&Path::new(B_TXT).into()), Some(&GitFileStatus::Added) ); assert_eq!( - repo.worktree_statuses.get(&Path::new(F_TXT).into()), + repo.statuses.get(&Path::new(F_TXT).into()), Some(&GitFileStatus::Added) ); }); @@ -4114,11 +4116,11 @@ mod tests { let snapshot = tree.snapshot(); let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - assert_eq!(repo.worktree_statuses.iter().count(), 1); - assert_eq!(repo.worktree_statuses.get(&Path::new(A_TXT).into()), None); - assert_eq!(repo.worktree_statuses.get(&Path::new(B_TXT).into()), None); + assert_eq!(repo.statuses.iter().count(), 1); + assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); + assert_eq!(repo.statuses.get(&Path::new(B_TXT).into()), None); assert_eq!( - repo.worktree_statuses.get(&Path::new(F_TXT).into()), + repo.statuses.get(&Path::new(F_TXT).into()), Some(&GitFileStatus::Added) ); }); @@ -4135,18 +4137,18 @@ mod tests { let snapshot = tree.snapshot(); let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - assert_eq!(repo.worktree_statuses.iter().count(), 3); - assert_eq!(repo.worktree_statuses.get(&Path::new(A_TXT).into()), None); + assert_eq!(repo.statuses.iter().count(), 3); + assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); assert_eq!( - repo.worktree_statuses.get(&Path::new(B_TXT).into()), + repo.statuses.get(&Path::new(B_TXT).into()), Some(&GitFileStatus::Added) ); assert_eq!( - repo.worktree_statuses.get(&Path::new(E_TXT).into()), + repo.statuses.get(&Path::new(E_TXT).into()), Some(&GitFileStatus::Modified) ); assert_eq!( - repo.worktree_statuses.get(&Path::new(F_TXT).into()), + repo.statuses.get(&Path::new(F_TXT).into()), Some(&GitFileStatus::Added) ); }); @@ -4169,11 +4171,11 @@ mod tests { let snapshot = tree.snapshot(); let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - assert_eq!(repo.worktree_statuses.iter().count(), 0); - assert_eq!(repo.worktree_statuses.get(&Path::new(A_TXT).into()), None); - assert_eq!(repo.worktree_statuses.get(&Path::new(B_TXT).into()), None); - assert_eq!(repo.worktree_statuses.get(&Path::new(E_TXT).into()), None); - assert_eq!(repo.worktree_statuses.get(&Path::new(F_TXT).into()), None); + assert_eq!(repo.statuses.iter().count(), 0); + assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); + assert_eq!(repo.statuses.get(&Path::new(B_TXT).into()), None); + assert_eq!(repo.statuses.get(&Path::new(E_TXT).into()), None); + assert_eq!(repo.statuses.get(&Path::new(F_TXT).into()), None); }); } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 8e45435b89..eca5fda306 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -986,8 +986,8 @@ message Entry { message RepositoryEntry { uint64 work_directory_id = 1; optional string branch = 2; - repeated string removed_worktree_repo_paths = 3; - repeated StatusEntry updated_worktree_statuses = 4; + repeated string removed_repo_paths = 3; + repeated StatusEntry updated_statuses = 4; } message StatusEntry { diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index d74ed5e46c..efaaaea52e 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -509,8 +509,8 @@ pub fn split_worktree_update( updated_repositories.push(RepositoryEntry { work_directory_id: repo.work_directory_id, branch: repo.branch.clone(), - removed_worktree_repo_paths: Default::default(), - updated_worktree_statuses: Default::default(), + removed_repo_paths: Default::default(), + updated_statuses: Default::default(), }); break; } @@ -535,26 +535,25 @@ pub fn split_worktree_update( { let updated_statuses_chunk_size = cmp::min( message.updated_repositories[repository_index] - .updated_worktree_statuses + .updated_statuses .len(), max_chunk_size - total_statuses, ); let updated_statuses: Vec<_> = message.updated_repositories[repository_index] - .updated_worktree_statuses + .updated_statuses .drain(..updated_statuses_chunk_size) .collect(); total_statuses += updated_statuses.len(); let done_this_repo = message.updated_repositories[repository_index] - .updated_worktree_statuses + .updated_statuses .is_empty(); let removed_repo_paths = if done_this_repo { mem::take( - &mut message.updated_repositories[repository_index] - .removed_worktree_repo_paths, + &mut message.updated_repositories[repository_index].removed_repo_paths, ) } else { Default::default() @@ -566,8 +565,8 @@ pub fn split_worktree_update( branch: message.updated_repositories[repository_index] .branch .clone(), - updated_worktree_statuses: updated_statuses, - removed_worktree_repo_paths: removed_repo_paths, + updated_statuses, + removed_repo_paths, }); if done_this_repo { From 68078853b7f44325743710cc86f8540bcda01cd3 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 May 2023 15:50:24 -0700 Subject: [PATCH 079/168] Made status tracking resilient to folder renames co-authored-by: max --- crates/project/src/worktree.rs | 269 ++++++++++++++++++++++++++++----- 1 file changed, 228 insertions(+), 41 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 9c214b7ecf..cea308d7c1 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1663,6 +1663,30 @@ impl Snapshot { } } + fn descendent_entries<'a>( + &'a self, + include_dirs: bool, + include_ignored: bool, + parent_path: &'a Path, + ) -> DescendentEntriesIter<'a> { + let mut cursor = self.entries_by_path.cursor(); + cursor.seek(&TraversalTarget::Path(parent_path), Bias::Left, &()); + let mut traversal = Traversal { + cursor, + include_dirs, + include_ignored, + }; + + if traversal.end_offset() == traversal.start_offset() { + traversal.advance(); + } + + DescendentEntriesIter { + traversal, + parent_path, + } + } + pub fn root_entry(&self) -> Option<&Entry> { self.entry_for_path("") } @@ -2664,14 +2688,13 @@ impl BackgroundScanner { async fn process_events(&mut self, paths: Vec) { let (scan_job_tx, scan_job_rx) = channel::unbounded(); - if let Some(mut paths) = self + let paths = self .reload_entries_for_paths(paths, Some(scan_job_tx.clone())) - .await - { - paths.sort_unstable(); + .await; + if let Some(paths) = &paths { util::extend_sorted( &mut self.prev_state.lock().event_paths, - paths, + paths.iter().cloned(), usize::MAX, Ord::cmp, ); @@ -2681,10 +2704,14 @@ impl BackgroundScanner { self.update_ignore_statuses().await; - // - let mut snapshot = self.snapshot.lock(); + if let Some(paths) = paths { + for path in paths { + self.reload_repo_for_file_path(&path, &mut *snapshot, self.fs.as_ref()); + } + } + let mut git_repositories = mem::take(&mut snapshot.git_repositories); git_repositories.retain(|work_directory_id, _| { snapshot @@ -2995,8 +3022,6 @@ impl BackgroundScanner { fs_entry.is_ignored = ignore_stack.is_all(); snapshot.insert_entry(fs_entry, self.fs.as_ref()); - self.reload_repo_for_file_path(&path, &mut snapshot, self.fs.as_ref()); - if let Some(scan_queue_tx) = &scan_queue_tx { let mut ancestor_inodes = snapshot.ancestor_inodes_for_path(&path); if metadata.is_dir && !ancestor_inodes.contains(&metadata.inode) { @@ -3109,34 +3134,36 @@ impl BackgroundScanner { let repo = snapshot.repo_for(&path)?; - let repo_path = repo.work_directory.relativize(&snapshot, &path)?; - - let status = { - let local_repo = snapshot.get_local_repo(&repo)?; - - // Short circuit if we've already scanned everything - if local_repo.full_scan_id == scan_id { - return None; - } - - let git_ptr = local_repo.repo_ptr.lock(); - git_ptr.status(&repo_path) - }; - let work_dir = repo.work_directory(snapshot)?; - let work_dir_id = repo.work_directory; + let work_dir_id = repo.work_directory.clone(); snapshot .git_repositories .update(&work_dir_id, |entry| entry.scan_id = scan_id); - snapshot.repository_entries.update(&work_dir, |entry| { + let local_repo = snapshot.get_local_repo(&repo)?.to_owned(); + + // Short circuit if we've already scanned everything + if local_repo.full_scan_id == scan_id { + return None; + } + + let mut repository = snapshot.repository_entries.remove(&work_dir)?; + + for entry in snapshot.descendent_entries(false, false, path) { + let Some(repo_path) = repo.work_directory.relativize(snapshot, &entry.path) else { + continue; + }; + + let status = local_repo.repo_ptr.lock().status(&repo_path); if let Some(status) = status { - entry.statuses.insert(repo_path, status); + repository.statuses.insert(repo_path.clone(), status); } else { - entry.statuses.remove(&repo_path); + repository.statuses.remove(&repo_path); } - }); + } + + snapshot.repository_entries.insert(work_dir, repository) } Some(()) @@ -3471,17 +3498,13 @@ pub struct Traversal<'a> { impl<'a> Traversal<'a> { pub fn advance(&mut self) -> bool { - self.advance_to_offset(self.offset() + 1) - } - - pub fn advance_to_offset(&mut self, offset: usize) -> bool { self.cursor.seek_forward( &TraversalTarget::Count { - count: offset, + count: self.end_offset() + 1, include_dirs: self.include_dirs, include_ignored: self.include_ignored, }, - Bias::Right, + Bias::Left, &(), ) } @@ -3508,11 +3531,17 @@ impl<'a> Traversal<'a> { self.cursor.item() } - pub fn offset(&self) -> usize { + pub fn start_offset(&self) -> usize { self.cursor .start() .count(self.include_dirs, self.include_ignored) } + + pub fn end_offset(&self) -> usize { + self.cursor + .end(&()) + .count(self.include_dirs, self.include_ignored) + } } impl<'a> Iterator for Traversal<'a> { @@ -3581,6 +3610,25 @@ impl<'a> Iterator for ChildEntriesIter<'a> { } } +struct DescendentEntriesIter<'a> { + parent_path: &'a Path, + traversal: Traversal<'a>, +} + +impl<'a> Iterator for DescendentEntriesIter<'a> { + type Item = &'a Entry; + + fn next(&mut self) -> Option { + if let Some(item) = self.traversal.entry() { + if item.path.starts_with(&self.parent_path) { + self.traversal.advance(); + return Some(item); + } + } + None + } +} + impl<'a> From<&'a Entry> for proto::Entry { fn from(entry: &'a Entry) -> Self { Self { @@ -3695,6 +3743,105 @@ mod tests { }) } + #[gpui::test] + async fn test_descendent_entries(cx: &mut TestAppContext) { + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/root", + json!({ + "a": "", + "b": { + "c": { + "d": "" + }, + "e": {} + }, + "f": "", + "g": { + "h": {} + }, + "i": { + "j": { + "k": "" + }, + "l": { + + } + }, + ".gitignore": "i/j\n", + }), + ) + .await; + + let http_client = FakeHttpClient::with_404_response(); + let client = cx.read(|cx| Client::new(http_client, cx)); + + let tree = Worktree::local( + client, + Path::new("/root"), + true, + fs, + Default::default(), + &mut cx.to_async(), + ) + .await + .unwrap(); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + + tree.read_with(cx, |tree, _| { + assert_eq!( + tree.descendent_entries(false, false, Path::new("b")) + .map(|entry| entry.path.as_ref()) + .collect::>(), + vec![Path::new("b/c/d"),] + ); + assert_eq!( + tree.descendent_entries(true, false, Path::new("b")) + .map(|entry| entry.path.as_ref()) + .collect::>(), + vec![ + Path::new("b"), + Path::new("b/c"), + Path::new("b/c/d"), + Path::new("b/e"), + ] + ); + + assert_eq!( + tree.descendent_entries(false, false, Path::new("g")) + .map(|entry| entry.path.as_ref()) + .collect::>(), + Vec::::new() + ); + assert_eq!( + tree.descendent_entries(true, false, Path::new("g")) + .map(|entry| entry.path.as_ref()) + .collect::>(), + vec![Path::new("g"), Path::new("g/h"),] + ); + + assert_eq!( + tree.descendent_entries(false, false, Path::new("i")) + .map(|entry| entry.path.as_ref()) + .collect::>(), + Vec::::new() + ); + assert_eq!( + tree.descendent_entries(false, true, Path::new("i")) + .map(|entry| entry.path.as_ref()) + .collect::>(), + vec![Path::new("i/j/k")] + ); + assert_eq!( + tree.descendent_entries(true, false, Path::new("i")) + .map(|entry| entry.path.as_ref()) + .collect::>(), + vec![Path::new("i"), Path::new("i/l"),] + ); + }) + } + #[gpui::test(iterations = 10)] async fn test_circular_symlinks(executor: Arc, cx: &mut TestAppContext) { let fs = FakeFs::new(cx.background()); @@ -4117,8 +4264,6 @@ mod tests { let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); assert_eq!(repo.statuses.iter().count(), 1); - assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); - assert_eq!(repo.statuses.get(&Path::new(B_TXT).into()), None); assert_eq!( repo.statuses.get(&Path::new(F_TXT).into()), Some(&GitFileStatus::Added) @@ -4172,10 +4317,52 @@ mod tests { let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); assert_eq!(repo.statuses.iter().count(), 0); - assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); - assert_eq!(repo.statuses.get(&Path::new(B_TXT).into()), None); - assert_eq!(repo.statuses.get(&Path::new(E_TXT).into()), None); - assert_eq!(repo.statuses.get(&Path::new(F_TXT).into()), None); + }); + + let mut renamed_dir_name = "first_directory/second_directory"; + const RENAMED_FILE: &'static str = "rf.txt"; + + std::fs::create_dir_all(work_dir.join(renamed_dir_name)).unwrap(); + std::fs::write( + work_dir.join(renamed_dir_name).join(RENAMED_FILE), + "new-contents", + ) + .unwrap(); + + tree.flush_fs_events(cx).await; + + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + + assert_eq!(repo.statuses.iter().count(), 1); + assert_eq!( + repo.statuses + .get(&Path::new(renamed_dir_name).join(RENAMED_FILE).into()), + Some(&GitFileStatus::Added) + ); + }); + + renamed_dir_name = "new_first_directory/second_directory"; + + std::fs::rename( + work_dir.join("first_directory"), + work_dir.join("new_first_directory"), + ) + .unwrap(); + + tree.flush_fs_events(cx).await; + + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + + assert_eq!(repo.statuses.iter().count(), 1); + assert_eq!( + repo.statuses + .get(&Path::new(renamed_dir_name).join(RENAMED_FILE).into()), + Some(&GitFileStatus::Added) + ); }); } From f59256f761bbb9915c565d6de92338aa116c940e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 May 2023 16:15:41 -0700 Subject: [PATCH 080/168] Update git repositories to be streamed with their entries co-authored-by: max --- crates/rpc/src/proto.rs | 91 +++++++++-------------------------------- 1 file changed, 19 insertions(+), 72 deletions(-) diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index efaaaea52e..cef4e6867c 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -1,6 +1,7 @@ use super::{entity_messages, messages, request_messages, ConnectionId, TypedEnvelope}; use anyhow::{anyhow, Result}; use async_tungstenite::tungstenite::Message as WebSocketMessage; +use collections::HashMap; use futures::{SinkExt as _, StreamExt as _}; use prost::Message as _; use serde::Serialize; @@ -485,11 +486,15 @@ pub fn split_worktree_update( max_chunk_size: usize, ) -> impl Iterator { let mut done_files = false; - let mut done_statuses = false; - let mut repository_index = 0; - let mut root_repo_found = false; + + let mut repository_map = message + .updated_repositories + .into_iter() + .map(|repo| (repo.work_directory_id, repo)) + .collect::>(); + iter::from_fn(move || { - if done_files && done_statuses { + if done_files { return None; } @@ -499,25 +504,6 @@ pub fn split_worktree_update( .drain(..updated_entries_chunk_size) .collect(); - let mut updated_repositories: Vec<_> = Default::default(); - - if !root_repo_found { - for entry in updated_entries.iter() { - if let Some(repo) = message.updated_repositories.get(0) { - if repo.work_directory_id == entry.id { - root_repo_found = true; - updated_repositories.push(RepositoryEntry { - work_directory_id: repo.work_directory_id, - branch: repo.branch.clone(), - removed_repo_paths: Default::default(), - updated_statuses: Default::default(), - }); - break; - } - } - } - } - let removed_entries_chunk_size = cmp::min(message.removed_entries.len(), max_chunk_size); let removed_entries = message .removed_entries @@ -526,64 +512,25 @@ pub fn split_worktree_update( done_files = message.updated_entries.is_empty() && message.removed_entries.is_empty(); - // Wait to send repositories until after we've guaranteed that their associated entries - // will be read - if done_files { - let mut total_statuses = 0; - while total_statuses < max_chunk_size - && repository_index < message.updated_repositories.len() - { - let updated_statuses_chunk_size = cmp::min( - message.updated_repositories[repository_index] - .updated_statuses - .len(), - max_chunk_size - total_statuses, - ); + let mut updated_repositories = Vec::new(); - let updated_statuses: Vec<_> = message.updated_repositories[repository_index] - .updated_statuses - .drain(..updated_statuses_chunk_size) - .collect(); - - total_statuses += updated_statuses.len(); - - let done_this_repo = message.updated_repositories[repository_index] - .updated_statuses - .is_empty(); - - let removed_repo_paths = if done_this_repo { - mem::take( - &mut message.updated_repositories[repository_index].removed_repo_paths, - ) - } else { - Default::default() - }; - - updated_repositories.push(RepositoryEntry { - work_directory_id: message.updated_repositories[repository_index] - .work_directory_id, - branch: message.updated_repositories[repository_index] - .branch - .clone(), - updated_statuses, - removed_repo_paths, - }); - - if done_this_repo { - repository_index += 1; + if !repository_map.is_empty() { + for entry in &updated_entries { + if let Some(repo) = repository_map.remove(&entry.id) { + updated_repositories.push(repo) } } - } else { - Default::default() - }; + } - let removed_repositories = if done_files && done_statuses { + let removed_repositories = if done_files { mem::take(&mut message.removed_repositories) } else { Default::default() }; - done_statuses = repository_index >= message.updated_repositories.len(); + if done_files { + updated_repositories.extend(mem::take(&mut repository_map).into_values()); + } Some(UpdateWorktree { project_id: message.project_id, From 4d40aa5d6fe5505cdf9cb3108e8eeecd20105f10 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 May 2023 16:17:18 -0700 Subject: [PATCH 081/168] Restore trickle up git status to folder co-authored-by: max --- crates/project/src/worktree.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index cea308d7c1..92c3c20c75 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -55,7 +55,7 @@ use std::{ time::{Duration, SystemTime}, }; use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet}; -use util::{paths::HOME, ResultExt, TryFutureExt}; +use util::{paths::HOME, ResultExt, TryFutureExt, TakeUntilExt}; #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] pub struct WorktreeId(usize); @@ -187,14 +187,20 @@ impl RepositoryEntry { self.statuses .iter_from(&repo_path) .take_while(|(key, _)| key.starts_with(&repo_path)) - .map(|(path, status)| { - if path == &repo_path { - status - } else { - &GitFileStatus::Modified - } - }) - .next() + // Short circut once we've found the highest level + .take_until(|(_, status)| status == &&GitFileStatus::Conflict) + .map(|(_, status)| status) + .reduce( + |status_first, status_second| match (status_first, status_second) { + (GitFileStatus::Conflict, _) | (_, GitFileStatus::Conflict) => { + &GitFileStatus::Conflict + } + (GitFileStatus::Modified, _) | (_, GitFileStatus::Modified) => { + &GitFileStatus::Modified + } + _ => &GitFileStatus::Added, + }, + ) .copied() }) } From e4d509adf47758231b0947dcc5daa85457c8bd45 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 May 2023 16:22:52 -0700 Subject: [PATCH 082/168] fmt --- crates/project/src/worktree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 92c3c20c75..cc16ed91b8 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -55,7 +55,7 @@ use std::{ time::{Duration, SystemTime}, }; use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet}; -use util::{paths::HOME, ResultExt, TryFutureExt, TakeUntilExt}; +use util::{paths::HOME, ResultExt, TakeUntilExt, TryFutureExt}; #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] pub struct WorktreeId(usize); From 606d5e36e1016d473f15869757a1efb39c7f45cf Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 May 2023 16:44:09 -0700 Subject: [PATCH 083/168] Add events for copilot suggestion accepting and discarding --- crates/copilot/src/copilot.rs | 36 ++++++++++++++++++++++++++++++++++- crates/editor/src/editor.rs | 16 ++++++++++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 9ccd9c445d..55c4c70f65 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -47,6 +47,10 @@ pub fn init(http: Arc, node_runtime: Arc, cx: &mut }); cx.set_global(copilot.clone()); + ////////////////////////////////////// + // SUBSCRIBE TO COPILOT EVENTS HERE // + ////////////////////////////////////// + cx.observe(&copilot, |handle, cx| { let status = handle.read(cx).status(); cx.update_default_global::(move |filter, _cx| { @@ -270,8 +274,19 @@ pub struct Copilot { buffers: HashMap>, } +pub enum Event { + CompletionAccepted { + uuid: String, + file_type: Option>, + }, + CompletionDiscarded { + uuids: Vec, + file_type: Option>, + }, +} + impl Entity for Copilot { - type Event = (); + type Event = Event; fn app_will_quit( &mut self, @@ -737,18 +752,26 @@ impl Copilot { pub fn accept_completion( &mut self, completion: &Completion, + file_type: Option>, cx: &mut ModelContext, ) -> Task> { let server = match self.server.as_authenticated() { Ok(server) => server, Err(error) => return Task::ready(Err(error)), }; + + cx.emit(Event::CompletionAccepted { + uuid: completion.uuid.clone(), + file_type, + }); + let request = server .lsp .request::(request::NotifyAcceptedParams { uuid: completion.uuid.clone(), }); + cx.background().spawn(async move { request.await?; Ok(()) @@ -758,12 +781,22 @@ impl Copilot { pub fn discard_completions( &mut self, completions: &[Completion], + file_type: Option>, cx: &mut ModelContext, ) -> Task> { let server = match self.server.as_authenticated() { Ok(server) => server, Err(error) => return Task::ready(Err(error)), }; + + cx.emit(Event::CompletionDiscarded { + uuids: completions + .iter() + .map(|completion| completion.uuid.clone()) + .collect(), + file_type: file_type.clone(), + }); + let request = server .lsp @@ -773,6 +806,7 @@ impl Copilot { .map(|completion| completion.uuid.clone()) .collect(), }); + cx.background().spawn(async move { request.await?; Ok(()) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b6d44397a9..221e94370e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3094,8 +3094,14 @@ impl Editor { if let Some((copilot, completion)) = Copilot::global(cx).zip(self.copilot_state.active_completion()) { + let language = self + .language_at(completion.range.start.offset, cx) + .map(|language| language.name()); + copilot - .update(cx, |copilot, cx| copilot.accept_completion(completion, cx)) + .update(cx, |copilot, cx| { + copilot.accept_completion(completion, language, cx) + }) .detach_and_log_err(cx); } self.insert_with_autoindent_mode(&suggestion.text.to_string(), None, cx); @@ -3109,9 +3115,15 @@ impl Editor { fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext) -> bool { if self.has_active_copilot_suggestion(cx) { if let Some(copilot) = Copilot::global(cx) { + let file_type = self.copilot_state + .completions + .get(0) + .and_then(|completion| self.language_at(completion.range.start.offset, cx)) + .map(|language| language.name()); + copilot .update(cx, |copilot, cx| { - copilot.discard_completions(&self.copilot_state.completions, cx) + copilot.discard_completions(&self.copilot_state.completions, file_type, cx) }) .detach_and_log_err(cx); } From ead9ac6f236423ee4f04fe5451e35bbdb9bdd0b8 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 May 2023 16:47:39 -0700 Subject: [PATCH 084/168] Fix typo --- crates/copilot/src/copilot.rs | 4 ++-- crates/editor/src/editor.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 55c4c70f65..b9e850b371 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -279,7 +279,7 @@ pub enum Event { uuid: String, file_type: Option>, }, - CompletionDiscarded { + CompletionsDiscarded { uuids: Vec, file_type: Option>, }, @@ -789,7 +789,7 @@ impl Copilot { Err(error) => return Task::ready(Err(error)), }; - cx.emit(Event::CompletionDiscarded { + cx.emit(Event::CompletionsDiscarded { uuids: completions .iter() .map(|completion| completion.uuid.clone()) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 221e94370e..9c5fe7e940 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3115,7 +3115,8 @@ impl Editor { fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext) -> bool { if self.has_active_copilot_suggestion(cx) { if let Some(copilot) = Copilot::global(cx) { - let file_type = self.copilot_state + let file_type = self + .copilot_state .completions .get(0) .and_then(|completion| self.language_at(completion.range.start.offset, cx)) From a6a2f9360743a9fa12b199c9b0df8eb26caf15cb Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 16 May 2023 00:34:58 -0400 Subject: [PATCH 085/168] Update telemetry client to accept copilot events --- crates/client/src/telemetry.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 7151dcd7bb..d11a78bb62 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -86,6 +86,11 @@ pub enum ClickhouseEvent { copilot_enabled: bool, copilot_enabled_for_language: bool, }, + Copilot { + suggestion_id: String, + suggestion_accepted: bool, + file_extension: Option, + }, } #[derive(Serialize, Debug)] From f50afefed337ca13dc44bb175e90b73dfc2c997e Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 16 May 2023 00:35:21 -0400 Subject: [PATCH 086/168] Subscribe to copilot events (WIP) --- crates/copilot/src/copilot.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index b9e850b371..07c54c7785 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -47,9 +47,21 @@ pub fn init(http: Arc, node_runtime: Arc, cx: &mut }); cx.set_global(copilot.clone()); - ////////////////////////////////////// - // SUBSCRIBE TO COPILOT EVENTS HERE // - ////////////////////////////////////// + cx.subscribe(&copilot, |_, event, _| { + match event { + Event::CompletionAccepted { uuid, file_type } => { + // Build event object and pass it in + // telemetry.report_clickhouse_event(event, settings.telemetry()) + } + Event::CompletionsDiscarded { uuids, file_type } => { + for uuid in uuids { + // Build event object and pass it in + // telemetry.report_clickhouse_event(event, settings.telemetry()) + } + } + }; + }) + .detach(); cx.observe(&copilot, |handle, cx| { let status = handle.read(cx).status(); From a7fc07a8cd62570f9f44bbad1fa4bcaeb97548cf Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 16 May 2023 03:12:39 -0400 Subject: [PATCH 087/168] Init copilot with client instead of http client --- Cargo.lock | 1 + crates/copilot/Cargo.toml | 1 + crates/copilot/src/copilot.rs | 38 ++++++++++++++++++++++------------- crates/zed/src/main.rs | 3 ++- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e009cfd342..2af1a4aa36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1338,6 +1338,7 @@ dependencies = [ "anyhow", "async-compression", "async-tar", + "client", "clock", "collections", "context_menu", diff --git a/crates/copilot/Cargo.toml b/crates/copilot/Cargo.toml index bac335f7b7..1a6ec7968d 100644 --- a/crates/copilot/Cargo.toml +++ b/crates/copilot/Cargo.toml @@ -22,6 +22,7 @@ test-support = [ collections = { path = "../collections" } context_menu = { path = "../context_menu" } gpui = { path = "../gpui" } +client = { path = "../client" } language = { path = "../language" } settings = { path = "../settings" } theme = { path = "../theme" } diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 07c54c7785..f1b547a182 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -4,6 +4,7 @@ mod sign_in; use anyhow::{anyhow, Context, Result}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; +use client::{ClickhouseEvent, Client}; use collections::HashMap; use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt}; use gpui::{ @@ -40,26 +41,35 @@ actions!( [Suggest, NextSuggestion, PreviousSuggestion, Reinstall] ); -pub fn init(http: Arc, node_runtime: Arc, cx: &mut AppContext) { +pub fn init(client: &Client, node_runtime: Arc, cx: &mut AppContext) { let copilot = cx.add_model({ let node_runtime = node_runtime.clone(); - move |cx| Copilot::start(http, node_runtime, cx) + move |cx| Copilot::start(client.http_client(), node_runtime, cx) }); cx.set_global(copilot.clone()); - cx.subscribe(&copilot, |_, event, _| { - match event { - Event::CompletionAccepted { uuid, file_type } => { - // Build event object and pass it in - // telemetry.report_clickhouse_event(event, settings.telemetry()) + let telemetry_settings = cx.global::().telemetry(); + let telemetry = client.telemetry(); + + cx.subscribe(&copilot, move |_, event, _| match event { + Event::CompletionAccepted { uuid, file_type } => { + let event = ClickhouseEvent::Copilot { + suggestion_id: uuid.clone(), + suggestion_accepted: true, + file_extension: file_type.clone().map(|a| a.to_string()), + }; + telemetry.report_clickhouse_event(event, telemetry_settings); + } + Event::CompletionsDiscarded { uuids, file_type } => { + for uuid in uuids { + let event = ClickhouseEvent::Copilot { + suggestion_id: uuid.clone(), + suggestion_accepted: false, + file_extension: file_type.clone().map(|a| a.to_string()), + }; + telemetry.report_clickhouse_event(event, telemetry_settings); } - Event::CompletionsDiscarded { uuids, file_type } => { - for uuid in uuids { - // Build event object and pass it in - // telemetry.report_clickhouse_event(event, settings.telemetry()) - } - } - }; + } }) .detach(); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f498078b52..434234f7f6 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -178,7 +178,6 @@ fn main() { vim::init(cx); terminal_view::init(cx); theme_testbench::init(cx); - copilot::init(http.clone(), node_runtime, cx); cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx)) .detach(); @@ -197,6 +196,8 @@ fn main() { cx.global::().telemetry(), ); + copilot::init(&client, node_runtime, cx); + let app_state = Arc::new(AppState { languages, themes, From 903eed964ab3941b359a3c299a1d34106ead70cd Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 16 May 2023 14:45:50 +0300 Subject: [PATCH 088/168] Allow CLI to start Zed from local sources Zed now is able to behave as if it's being started from CLI (`ZED_FORCE_CLI_MODE` env var) Zed CLI accepts regular binary file path into `-b` parameter (only *.app before), and tries to start it as Zed editor with `ZED_FORCE_CLI_MODE` env var and other params needed. --- crates/cli/src/cli.rs | 4 + crates/cli/src/main.rs | 197 ++++++++++++++++++++++++++++++----------- crates/zed/src/main.rs | 85 ++++++++++++------ 3 files changed, 211 insertions(+), 75 deletions(-) diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index 7cad42b534..de7b14e142 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -20,3 +20,7 @@ pub enum CliResponse { Stderr { message: String }, Exit { status: i32 }, } + +/// When Zed started not as an *.app but as a binary (e.g. local development), +/// there's a possibility to tell it to behave "regularly". +pub const FORCE_CLI_MODE_ENV_VAR_NAME: &str = "ZED_FORCE_CLI_MODE"; diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index a31e59587f..0ae4d2477e 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,6 +1,6 @@ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use clap::Parser; -use cli::{CliRequest, CliResponse, IpcHandshake}; +use cli::{CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME}; use core_foundation::{ array::{CFArray, CFIndex}, string::kCFStringEncodingUTF8, @@ -43,20 +43,10 @@ struct InfoPlist { fn main() -> Result<()> { let args = Args::parse(); - let bundle_path = if let Some(bundle_path) = args.bundle_path { - bundle_path.canonicalize()? - } else { - locate_bundle()? - }; + let bundle = Bundle::detect(args.bundle_path.as_deref()).context("Bundle detection")?; if args.version { - let plist_path = bundle_path.join("Contents/Info.plist"); - let plist = plist::from_file::<_, InfoPlist>(plist_path)?; - println!( - "Zed {} – {}", - plist.bundle_short_version_string, - bundle_path.to_string_lossy() - ); + println!("{}", bundle.zed_version_string()); return Ok(()); } @@ -66,7 +56,7 @@ fn main() -> Result<()> { } } - let (tx, rx) = launch_app(bundle_path)?; + let (tx, rx) = bundle.launch()?; tx.send(CliRequest::Open { paths: args @@ -89,6 +79,148 @@ fn main() -> Result<()> { Ok(()) } +enum Bundle { + App { + app_bundle: PathBuf, + plist: InfoPlist, + }, + LocalPath { + executable: PathBuf, + plist: InfoPlist, + }, +} + +impl Bundle { + fn detect(args_bundle_path: Option<&Path>) -> anyhow::Result { + let bundle_path = if let Some(bundle_path) = args_bundle_path { + bundle_path + .canonicalize() + .with_context(|| format!("Args bundle path {bundle_path:?} canonicalization"))? + } else { + locate_bundle().context("bundle autodiscovery")? + }; + + match bundle_path.extension().and_then(|ext| ext.to_str()) { + Some("app") => { + let plist_path = bundle_path.join("Contents/Info.plist"); + let plist = plist::from_file::<_, InfoPlist>(&plist_path).with_context(|| { + format!("Reading *.app bundle plist file at {plist_path:?}") + })?; + Ok(Self::App { + app_bundle: bundle_path, + plist, + }) + } + _ => { + println!("Bundle path {bundle_path:?} has no *.app extension, attempting to locate a dev build"); + let plist_path = bundle_path + .parent() + .with_context(|| format!("Bundle path {bundle_path:?} has no parent"))? + .join("WebRTC.framework/Resources/Info.plist"); + let plist = plist::from_file::<_, InfoPlist>(&plist_path) + .with_context(|| format!("Reading dev bundle plist file at {plist_path:?}"))?; + Ok(Self::LocalPath { + executable: bundle_path, + plist, + }) + } + } + } + + fn plist(&self) -> &InfoPlist { + match self { + Self::App { plist, .. } => plist, + Self::LocalPath { plist, .. } => plist, + } + } + + fn path(&self) -> &Path { + match self { + Self::App { app_bundle, .. } => app_bundle, + Self::LocalPath { + executable: excutable, + .. + } => excutable, + } + } + + fn launch(&self) -> anyhow::Result<(IpcSender, IpcReceiver)> { + let (server, server_name) = + IpcOneShotServer::::new().context("Handshake before Zed spawn")?; + let url = format!("zed-cli://{server_name}"); + + match self { + Self::App { app_bundle, .. } => { + let app_path = app_bundle; + + let status = unsafe { + let app_url = CFURL::from_path(app_path, true) + .with_context(|| format!("invalid app path {app_path:?}"))?; + let url_to_open = CFURL::wrap_under_create_rule(CFURLCreateWithBytes( + ptr::null(), + url.as_ptr(), + url.len() as CFIndex, + kCFStringEncodingUTF8, + ptr::null(), + )); + let urls_to_open = CFArray::from_copyable(&[url_to_open.as_concrete_TypeRef()]); + LSOpenFromURLSpec( + &LSLaunchURLSpec { + appURL: app_url.as_concrete_TypeRef(), + itemURLs: urls_to_open.as_concrete_TypeRef(), + passThruParams: ptr::null(), + launchFlags: kLSLaunchDefaults, + asyncRefCon: ptr::null_mut(), + }, + ptr::null_mut(), + ) + }; + + anyhow::ensure!( + status == 0, + "cannot start app bundle {}", + self.zed_version_string() + ); + } + Self::LocalPath { executable, .. } => { + let executable_parent = executable + .parent() + .with_context(|| format!("Executable {executable:?} path has no parent"))?; + let subprocess_stdout_file = + fs::File::create(executable_parent.join("zed_dev.log")) + .with_context(|| format!("Log file creation in {executable_parent:?}"))?; + let subprocess_stdin_file = + subprocess_stdout_file.try_clone().with_context(|| { + format!("Cloning descriptor for file {subprocess_stdout_file:?}") + })?; + let mut command = std::process::Command::new(executable); + let command = command + .env(FORCE_CLI_MODE_ENV_VAR_NAME, "") + .stderr(subprocess_stdout_file) + .stdout(subprocess_stdin_file) + .arg(url); + + command + .spawn() + .with_context(|| format!("Spawning {command:?}"))?; + } + } + + let (_, handshake) = server.accept().context("Handshake after Zed spawn")?; + Ok((handshake.requests, handshake.responses)) + } + + fn zed_version_string(&self) -> String { + let is_dev = matches!(self, Self::LocalPath { .. }); + format!( + "Zed {}{} – {}", + self.plist().bundle_short_version_string, + if is_dev { " (dev)" } else { "" }, + self.path().display(), + ) + } +} + fn touch(path: &Path) -> io::Result<()> { match OpenOptions::new().create(true).write(true).open(path) { Ok(_) => Ok(()), @@ -106,38 +238,3 @@ fn locate_bundle() -> Result { } Ok(app_path) } - -fn launch_app(app_path: PathBuf) -> Result<(IpcSender, IpcReceiver)> { - let (server, server_name) = IpcOneShotServer::::new()?; - let url = format!("zed-cli://{server_name}"); - - let status = unsafe { - let app_url = - CFURL::from_path(&app_path, true).ok_or_else(|| anyhow!("invalid app path"))?; - let url_to_open = CFURL::wrap_under_create_rule(CFURLCreateWithBytes( - ptr::null(), - url.as_ptr(), - url.len() as CFIndex, - kCFStringEncodingUTF8, - ptr::null(), - )); - let urls_to_open = CFArray::from_copyable(&[url_to_open.as_concrete_TypeRef()]); - LSOpenFromURLSpec( - &LSLaunchURLSpec { - appURL: app_url.as_concrete_TypeRef(), - itemURLs: urls_to_open.as_concrete_TypeRef(), - passThruParams: ptr::null(), - launchFlags: kLSLaunchDefaults, - asyncRefCon: ptr::null_mut(), - }, - ptr::null_mut(), - ) - }; - - if status == 0 { - let (_, handshake) = server.accept()?; - Ok((handshake.requests, handshake.responses)) - } else { - Err(anyhow!("cannot start {:?}", app_path)) - } -} diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f498078b52..60a2fc66be 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -6,7 +6,7 @@ use assets::Assets; use backtrace::Backtrace; use cli::{ ipc::{self, IpcSender}, - CliRequest, CliResponse, IpcHandshake, + CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME, }; use client::{self, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; use db::kvp::KEY_VALUE_STORE; @@ -37,7 +37,10 @@ use std::{ os::unix::prelude::OsStrExt, panic, path::PathBuf, - sync::{Arc, Weak}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Weak, + }, thread, time::Duration, }; @@ -89,29 +92,17 @@ fn main() { }; let (cli_connections_tx, mut cli_connections_rx) = mpsc::unbounded(); + let cli_connections_tx = Arc::new(cli_connections_tx); let (open_paths_tx, mut open_paths_rx) = mpsc::unbounded(); + let open_paths_tx = Arc::new(open_paths_tx); + let urls_callback_triggered = Arc::new(AtomicBool::new(false)); + + let callback_cli_connections_tx = Arc::clone(&cli_connections_tx); + let callback_open_paths_tx = Arc::clone(&open_paths_tx); + let callback_urls_callback_triggered = Arc::clone(&urls_callback_triggered); app.on_open_urls(move |urls, _| { - if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { - if let Some(cli_connection) = connect_to_cli(server_name).log_err() { - cli_connections_tx - .unbounded_send(cli_connection) - .map_err(|_| anyhow!("no listener for cli connections")) - .log_err(); - }; - } else { - let paths: Vec<_> = urls - .iter() - .flat_map(|url| url.strip_prefix("file://")) - .map(|url| { - let decoded = urlencoding::decode_binary(url.as_bytes()); - PathBuf::from(OsStr::from_bytes(decoded.as_ref())) - }) - .collect(); - open_paths_tx - .unbounded_send(paths) - .map_err(|_| anyhow!("no listener for open urls requests")) - .log_err(); - } + callback_urls_callback_triggered.store(true, Ordering::Release); + open_urls(urls, &callback_cli_connections_tx, &callback_open_paths_tx); }) .on_reopen(move |cx| { if cx.has_global::>() { @@ -234,6 +225,14 @@ fn main() { workspace::open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx); } } else { + // TODO Development mode that forces the CLI mode usually runs Zed binary as is instead + // of an *app, hence gets no specific callbacks run. Emulate them here, if needed. + if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some() + && !urls_callback_triggered.load(Ordering::Acquire) + { + open_urls(collect_url_args(), &cli_connections_tx, &open_paths_tx) + } + if let Ok(Some(connection)) = cli_connections_rx.try_next() { cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx)) .detach(); @@ -284,6 +283,37 @@ fn main() { }); } +fn open_urls( + urls: Vec, + cli_connections_tx: &mpsc::UnboundedSender<( + mpsc::Receiver, + IpcSender, + )>, + open_paths_tx: &mpsc::UnboundedSender>, +) { + if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { + if let Some(cli_connection) = connect_to_cli(server_name).log_err() { + cli_connections_tx + .unbounded_send(cli_connection) + .map_err(|_| anyhow!("no listener for cli connections")) + .log_err(); + }; + } else { + let paths: Vec<_> = urls + .iter() + .flat_map(|url| url.strip_prefix("file://")) + .map(|url| { + let decoded = urlencoding::decode_binary(url.as_bytes()); + PathBuf::from(OsStr::from_bytes(decoded.as_ref())) + }) + .collect(); + open_paths_tx + .unbounded_send(paths) + .map_err(|_| anyhow!("no listener for open urls requests")) + .log_err(); + } +} + async fn restore_or_create_workspace(app_state: &Arc, mut cx: AsyncAppContext) { if let Some(location) = workspace::last_opened_workspace_paths().await { cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx)) @@ -514,7 +544,8 @@ async fn load_login_shell_environment() -> Result<()> { } fn stdout_is_a_pty() -> bool { - unsafe { libc::isatty(libc::STDOUT_FILENO as i32) != 0 } + std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() + && unsafe { libc::isatty(libc::STDOUT_FILENO as i32) != 0 } } fn collect_path_args() -> Vec { @@ -527,7 +558,11 @@ fn collect_path_args() -> Vec { None } }) - .collect::>() + .collect() +} + +fn collect_url_args() -> Vec { + env::args().skip(1).collect() } fn load_embedded_fonts(app: &App) { From 2d4b2e0844ee6415375b3f768d8e7df73e16f333 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 16 May 2023 11:51:20 -0400 Subject: [PATCH 089/168] Fix compile error --- crates/copilot/src/copilot.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index f1b547a182..c7671cee7a 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -49,7 +49,7 @@ pub fn init(client: &Client, node_runtime: Arc, cx: &mut AppContext cx.set_global(copilot.clone()); let telemetry_settings = cx.global::().telemetry(); - let telemetry = client.telemetry(); + let telemetry = client.telemetry().clone(); cx.subscribe(&copilot, move |_, event, _| match event { Event::CompletionAccepted { uuid, file_type } => { From f50240181a669f2359c0f35722b3e6737c40881a Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 16 May 2023 13:00:39 -0400 Subject: [PATCH 090/168] Avoid removing fake fs entry when rename fails later in the process Co-Authored-By: Antonio Scandurra --- crates/fs/src/fs.rs | 21 +++++++++++++++++---- crates/project/src/worktree.rs | 11 +++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 3285eb328a..99562405b5 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -572,15 +572,15 @@ impl FakeFs { Ok(()) } - pub async fn pause_events(&self) { + pub fn pause_events(&self) { self.state.lock().events_paused = true; } - pub async fn buffered_event_count(&self) -> usize { + pub fn buffered_event_count(&self) -> usize { self.state.lock().buffered_events.len() } - pub async fn flush_events(&self, count: usize) { + pub fn flush_events(&self, count: usize) { self.state.lock().flush_events(count); } @@ -832,14 +832,16 @@ impl Fs for FakeFs { let old_path = normalize_path(old_path); let new_path = normalize_path(new_path); + let mut state = self.state.lock(); let moved_entry = state.write_path(&old_path, |e| { if let btree_map::Entry::Occupied(e) = e { - Ok(e.remove()) + Ok(e.get().clone()) } else { Err(anyhow!("path does not exist: {}", &old_path.display())) } })?; + state.write_path(&new_path, |e| { match e { btree_map::Entry::Occupied(mut e) => { @@ -855,6 +857,17 @@ impl Fs for FakeFs { } Ok(()) })?; + + state + .write_path(&old_path, |e| { + if let btree_map::Entry::Occupied(e) = e { + Ok(e.remove()) + } else { + unreachable!() + } + }) + .unwrap(); + state.emit_event(&[old_path, new_path]); Ok(()) } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index cc16ed91b8..958e72fa18 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -4632,6 +4632,7 @@ mod tests { .detach(); }); + fs.as_fake().pause_events(); let mut snapshots = Vec::new(); let mut mutations_len = operations; while mutations_len > 1 { @@ -4641,16 +4642,16 @@ mod tests { randomly_mutate_worktree(worktree, &mut rng, cx) }) .await - .unwrap(); + .log_err(); } else { randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await; } - let buffered_event_count = fs.as_fake().buffered_event_count().await; + let buffered_event_count = fs.as_fake().buffered_event_count(); if buffered_event_count > 0 && rng.gen_bool(0.3) { let len = rng.gen_range(0..=buffered_event_count); log::info!("flushing {} events", len); - fs.as_fake().flush_events(len).await; + fs.as_fake().flush_events(len); } else { randomly_mutate_fs(&fs, root_dir, 0.6, &mut rng).await; mutations_len -= 1; @@ -4666,7 +4667,7 @@ mod tests { } log::info!("quiescing"); - fs.as_fake().flush_events(usize::MAX).await; + fs.as_fake().flush_events(usize::MAX); cx.foreground().run_until_parked(); let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot()); snapshot.check_invariants(); @@ -4726,6 +4727,7 @@ mod tests { rng: &mut impl Rng, cx: &mut ModelContext, ) -> Task> { + log::info!("mutating worktree"); let worktree = worktree.as_local_mut().unwrap(); let snapshot = worktree.snapshot(); let entry = snapshot.entries(false).choose(rng).unwrap(); @@ -4787,6 +4789,7 @@ mod tests { insertion_probability: f64, rng: &mut impl Rng, ) { + log::info!("mutating fs"); let mut files = Vec::new(); let mut dirs = Vec::new(); for path in fs.as_fake().paths() { From 8b63caa0bd6bc57d837bf9a52f5c0d70c0c92513 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 16 May 2023 13:01:29 -0400 Subject: [PATCH 091/168] Fix worktree refresh request causing gitignore to not update Co-Authored-By: Antonio Scandurra --- crates/project/src/worktree.rs | 40 ++++++++++++++++------------------ 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 958e72fa18..403d893425 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -338,7 +338,7 @@ impl<'a> From for WorkDirectoryEntry { #[derive(Debug, Clone)] pub struct LocalSnapshot { - ignores_by_parent_abs_path: HashMap, (Arc, usize)>, + ignores_by_parent_abs_path: HashMap, (Arc, bool)>, // (gitignore, needs_update) // The ProjectEntryId corresponds to the entry for the .git dir // work_directory_id git_repositories: TreeMap, @@ -1882,10 +1882,8 @@ impl LocalSnapshot { let abs_path = self.abs_path.join(&entry.path); match smol::block_on(build_gitignore(&abs_path, fs)) { Ok(ignore) => { - self.ignores_by_parent_abs_path.insert( - abs_path.parent().unwrap().into(), - (Arc::new(ignore), self.scan_id), - ); + self.ignores_by_parent_abs_path + .insert(abs_path.parent().unwrap().into(), (Arc::new(ignore), true)); } Err(error) => { log::error!( @@ -1955,10 +1953,8 @@ impl LocalSnapshot { } if let Some(ignore) = ignore { - self.ignores_by_parent_abs_path.insert( - self.abs_path.join(&parent_path).into(), - (ignore, self.scan_id), - ); + self.ignores_by_parent_abs_path + .insert(self.abs_path.join(&parent_path).into(), (ignore, false)); } if parent_path.file_name() == Some(&DOT_GIT) { @@ -2062,11 +2058,11 @@ impl LocalSnapshot { if path.file_name() == Some(&GITIGNORE) { let abs_parent_path = self.abs_path.join(path.parent().unwrap()); - if let Some((_, scan_id)) = self + if let Some((_, needs_update)) = self .ignores_by_parent_abs_path .get_mut(abs_parent_path.as_path()) { - *scan_id = self.snapshot.scan_id; + *needs_update = true; } } } @@ -2609,7 +2605,7 @@ impl BackgroundScanner { self.snapshot .lock() .ignores_by_parent_abs_path - .insert(ancestor.into(), (ignore.into(), 0)); + .insert(ancestor.into(), (ignore.into(), false)); } } { @@ -2662,7 +2658,7 @@ impl BackgroundScanner { // these before handling changes reported by the filesystem. request = self.refresh_requests_rx.recv().fuse() => { let Ok((paths, barrier)) = request else { break }; - if !self.process_refresh_request(paths, barrier).await { + if !self.process_refresh_request(paths.clone(), barrier).await { return; } } @@ -2673,7 +2669,7 @@ impl BackgroundScanner { while let Poll::Ready(Some(more_events)) = futures::poll!(events_rx.next()) { paths.extend(more_events.into_iter().map(|e| e.path)); } - self.process_events(paths).await; + self.process_events(paths.clone()).await; } } } @@ -3181,16 +3177,18 @@ impl BackgroundScanner { let mut snapshot = self.snapshot.lock().clone(); let mut ignores_to_update = Vec::new(); let mut ignores_to_delete = Vec::new(); - for (parent_abs_path, (_, scan_id)) in &snapshot.ignores_by_parent_abs_path { - if let Ok(parent_path) = parent_abs_path.strip_prefix(&snapshot.abs_path) { - if *scan_id > snapshot.completed_scan_id - && snapshot.entry_for_path(parent_path).is_some() - { - ignores_to_update.push(parent_abs_path.clone()); + let abs_path = snapshot.abs_path.clone(); + for (parent_abs_path, (_, needs_update)) in &mut snapshot.ignores_by_parent_abs_path { + if let Ok(parent_path) = parent_abs_path.strip_prefix(&abs_path) { + if *needs_update { + *needs_update = false; + if snapshot.snapshot.entry_for_path(parent_path).is_some() { + ignores_to_update.push(parent_abs_path.clone()); + } } let ignore_path = parent_path.join(&*GITIGNORE); - if snapshot.entry_for_path(ignore_path).is_none() { + if snapshot.snapshot.entry_for_path(ignore_path).is_none() { ignores_to_delete.push(parent_abs_path.clone()); } } From 6976d60bfed7af33a5147c910e45c756711a2204 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 16 May 2023 13:24:25 -0400 Subject: [PATCH 092/168] Rework code to contain submitting of copilot events within editor --- Cargo.lock | 1 - crates/copilot/Cargo.toml | 1 - crates/copilot/src/copilot.rs | 64 +++-------------------------------- crates/editor/src/editor.rs | 50 +++++++++++++++++++-------- crates/zed/src/main.rs | 3 +- 5 files changed, 40 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2af1a4aa36..e009cfd342 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1338,7 +1338,6 @@ dependencies = [ "anyhow", "async-compression", "async-tar", - "client", "clock", "collections", "context_menu", diff --git a/crates/copilot/Cargo.toml b/crates/copilot/Cargo.toml index 1a6ec7968d..bac335f7b7 100644 --- a/crates/copilot/Cargo.toml +++ b/crates/copilot/Cargo.toml @@ -22,7 +22,6 @@ test-support = [ collections = { path = "../collections" } context_menu = { path = "../context_menu" } gpui = { path = "../gpui" } -client = { path = "../client" } language = { path = "../language" } settings = { path = "../settings" } theme = { path = "../theme" } diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index c7671cee7a..65d0a19bed 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -4,7 +4,6 @@ mod sign_in; use anyhow::{anyhow, Context, Result}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; -use client::{ClickhouseEvent, Client}; use collections::HashMap; use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt}; use gpui::{ @@ -41,38 +40,13 @@ actions!( [Suggest, NextSuggestion, PreviousSuggestion, Reinstall] ); -pub fn init(client: &Client, node_runtime: Arc, cx: &mut AppContext) { +pub fn init(http: Arc, node_runtime: Arc, cx: &mut AppContext) { let copilot = cx.add_model({ let node_runtime = node_runtime.clone(); - move |cx| Copilot::start(client.http_client(), node_runtime, cx) + move |cx| Copilot::start(http, node_runtime, cx) }); cx.set_global(copilot.clone()); - let telemetry_settings = cx.global::().telemetry(); - let telemetry = client.telemetry().clone(); - - cx.subscribe(&copilot, move |_, event, _| match event { - Event::CompletionAccepted { uuid, file_type } => { - let event = ClickhouseEvent::Copilot { - suggestion_id: uuid.clone(), - suggestion_accepted: true, - file_extension: file_type.clone().map(|a| a.to_string()), - }; - telemetry.report_clickhouse_event(event, telemetry_settings); - } - Event::CompletionsDiscarded { uuids, file_type } => { - for uuid in uuids { - let event = ClickhouseEvent::Copilot { - suggestion_id: uuid.clone(), - suggestion_accepted: false, - file_extension: file_type.clone().map(|a| a.to_string()), - }; - telemetry.report_clickhouse_event(event, telemetry_settings); - } - } - }) - .detach(); - cx.observe(&copilot, |handle, cx| { let status = handle.read(cx).status(); cx.update_default_global::(move |filter, _cx| { @@ -284,7 +258,7 @@ impl RegisteredBuffer { #[derive(Debug)] pub struct Completion { - uuid: String, + pub uuid: String, pub range: Range, pub text: String, } @@ -296,19 +270,8 @@ pub struct Copilot { buffers: HashMap>, } -pub enum Event { - CompletionAccepted { - uuid: String, - file_type: Option>, - }, - CompletionsDiscarded { - uuids: Vec, - file_type: Option>, - }, -} - impl Entity for Copilot { - type Event = Event; + type Event = (); fn app_will_quit( &mut self, @@ -774,26 +737,18 @@ impl Copilot { pub fn accept_completion( &mut self, completion: &Completion, - file_type: Option>, cx: &mut ModelContext, ) -> Task> { let server = match self.server.as_authenticated() { Ok(server) => server, Err(error) => return Task::ready(Err(error)), }; - - cx.emit(Event::CompletionAccepted { - uuid: completion.uuid.clone(), - file_type, - }); - let request = server .lsp .request::(request::NotifyAcceptedParams { uuid: completion.uuid.clone(), }); - cx.background().spawn(async move { request.await?; Ok(()) @@ -803,22 +758,12 @@ impl Copilot { pub fn discard_completions( &mut self, completions: &[Completion], - file_type: Option>, cx: &mut ModelContext, ) -> Task> { let server = match self.server.as_authenticated() { Ok(server) => server, Err(error) => return Task::ready(Err(error)), }; - - cx.emit(Event::CompletionsDiscarded { - uuids: completions - .iter() - .map(|completion| completion.uuid.clone()) - .collect(), - file_type: file_type.clone(), - }); - let request = server .lsp @@ -828,7 +773,6 @@ impl Copilot { .map(|completion| completion.uuid.clone()) .collect(), }); - cx.background().spawn(async move { request.await?; Ok(()) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9c5fe7e940..b4af9abb85 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3094,15 +3094,11 @@ impl Editor { if let Some((copilot, completion)) = Copilot::global(cx).zip(self.copilot_state.active_completion()) { - let language = self - .language_at(completion.range.start.offset, cx) - .map(|language| language.name()); - copilot - .update(cx, |copilot, cx| { - copilot.accept_completion(completion, language, cx) - }) + .update(cx, |copilot, cx| copilot.accept_completion(completion, cx)) .detach_and_log_err(cx); + + self.report_copilot_event(completion.uuid.clone(), true, cx) } self.insert_with_autoindent_mode(&suggestion.text.to_string(), None, cx); cx.notify(); @@ -3115,18 +3111,15 @@ impl Editor { fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext) -> bool { if self.has_active_copilot_suggestion(cx) { if let Some(copilot) = Copilot::global(cx) { - let file_type = self - .copilot_state - .completions - .get(0) - .and_then(|completion| self.language_at(completion.range.start.offset, cx)) - .map(|language| language.name()); - copilot .update(cx, |copilot, cx| { - copilot.discard_completions(&self.copilot_state.completions, file_type, cx) + copilot.discard_completions(&self.copilot_state.completions, cx) }) .detach_and_log_err(cx); + + for completion in &self.copilot_state.completions { + self.report_copilot_event(completion.uuid.clone(), false, cx) + } } self.display_map @@ -6889,6 +6882,33 @@ impl Editor { .collect() } + fn report_copilot_event( + &self, + suggestion_id: String, + suggestion_accepted: bool, + cx: &AppContext, + ) { + if let Some((project, file)) = self.project.as_ref().zip( + self.buffer + .read(cx) + .as_singleton() + .and_then(|b| b.read(cx).file()), + ) { + let telemetry_settings = cx.global::().telemetry(); + let extension = Path::new(file.file_name(cx)) + .extension() + .and_then(|e| e.to_str()); + let telemetry = project.read(cx).client().telemetry().clone(); + + let event = ClickhouseEvent::Copilot { + suggestion_id, + suggestion_accepted, + file_extension: extension.map(ToString::to_string), + }; + telemetry.report_clickhouse_event(event, telemetry_settings); + } + } + fn report_editor_event(&self, name: &'static str, cx: &AppContext) { if let Some((project, file)) = self.project.as_ref().zip( self.buffer diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 434234f7f6..f498078b52 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -178,6 +178,7 @@ fn main() { vim::init(cx); terminal_view::init(cx); theme_testbench::init(cx); + copilot::init(http.clone(), node_runtime, cx); cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx)) .detach(); @@ -196,8 +197,6 @@ fn main() { cx.global::().telemetry(), ); - copilot::init(&client, node_runtime, cx); - let app_state = Arc::new(AppState { languages, themes, From afe75e8cbd750a7b18dbf6d5dfacefdaa9be7adf Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 16 May 2023 14:02:36 -0400 Subject: [PATCH 093/168] Send copilot events even if file_extension is not known at the time --- crates/editor/src/editor.rs | 39 ++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b4af9abb85..c51ed1a14a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6888,25 +6888,28 @@ impl Editor { suggestion_accepted: bool, cx: &AppContext, ) { - if let Some((project, file)) = self.project.as_ref().zip( - self.buffer - .read(cx) - .as_singleton() - .and_then(|b| b.read(cx).file()), - ) { - let telemetry_settings = cx.global::().telemetry(); - let extension = Path::new(file.file_name(cx)) - .extension() - .and_then(|e| e.to_str()); - let telemetry = project.read(cx).client().telemetry().clone(); + let Some(project) = &self.project else { + return + }; - let event = ClickhouseEvent::Copilot { - suggestion_id, - suggestion_accepted, - file_extension: extension.map(ToString::to_string), - }; - telemetry.report_clickhouse_event(event, telemetry_settings); - } + // If None, we are either getting suggestions in a new, unsaved file, or in a file without an extension + let file_extension = self + .buffer + .read(cx) + .as_singleton() + .and_then(|b| b.read(cx).file()) + .and_then(|file| Path::new(file.file_name(cx)).extension()) + .and_then(|e| e.to_str()); + + let telemetry = project.read(cx).client().telemetry().clone(); + let telemetry_settings = cx.global::().telemetry(); + + let event = ClickhouseEvent::Copilot { + suggestion_id, + suggestion_accepted, + file_extension: file_extension.map(ToString::to_string), + }; + telemetry.report_clickhouse_event(event, telemetry_settings); } fn report_editor_event(&self, name: &'static str, cx: &AppContext) { From 3eea2fb5f8605c0a86c3b5559890ce38b87d96ad Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 10 May 2023 13:35:47 +0300 Subject: [PATCH 094/168] Parse file find queries with extra data --- crates/file_finder/src/file_finder.rs | 112 ++++++++++++++++++++++---- 1 file changed, 95 insertions(+), 17 deletions(-) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index f00430feb7..62eae72cc5 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -23,7 +23,7 @@ pub struct FileFinderDelegate { search_count: usize, latest_search_id: usize, latest_search_did_cancel: bool, - latest_search_query: String, + latest_search_query: Option, relative_to: Option>, matches: Vec, selected: Option<(usize, Arc)>, @@ -60,6 +60,62 @@ pub enum Event { Dismissed, } +#[derive(Debug, Clone, PartialEq, Eq)] +struct FileSearchQuery { + raw_query: String, + file_path_end: Option, + file_row: Option, + file_column: Option, +} + +impl FileSearchQuery { + fn new(raw_query: String) -> Self { + let fallback_query = Self { + raw_query: raw_query.clone(), + file_path_end: None, + file_row: None, + file_column: None, + }; + + let mut possible_path_and_coordinates = raw_query.as_str().rsplitn(3, ':').fuse(); + match ( + possible_path_and_coordinates.next(), + possible_path_and_coordinates.next(), + possible_path_and_coordinates.next(), + ) { + (Some(column_number_str), Some(row_number_str), Some(file_path_part)) => Self { + file_path_end: Some(file_path_part.len()), + file_row: match row_number_str.parse().ok() { + None => return fallback_query, + row => row, + }, + file_column: match column_number_str.parse().ok() { + None => return fallback_query, + column => column, + }, + raw_query, + }, + (Some(row_number_str), Some(file_path_part), None) => Self { + file_path_end: Some(file_path_part.len()), + file_row: match row_number_str.parse().ok() { + None => return fallback_query, + row => row, + }, + file_column: None, + raw_query, + }, + _no_colons_query => fallback_query, + } + } + + fn path_query(&self) -> &str { + match self.file_path_end { + Some(file_path_end) => &self.raw_query[..file_path_end], + None => &self.raw_query, + } + } +} + impl FileFinderDelegate { fn labels_for_match(&self, path_match: &PathMatch) -> (String, Vec, String, Vec) { let path = &path_match.path; @@ -103,7 +159,7 @@ impl FileFinderDelegate { search_count: 0, latest_search_id: 0, latest_search_did_cancel: false, - latest_search_query: String::new(), + latest_search_query: None, relative_to, matches: Vec::new(), selected: None, @@ -111,7 +167,11 @@ impl FileFinderDelegate { } } - fn spawn_search(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { + fn spawn_search( + &mut self, + query: FileSearchQuery, + cx: &mut ViewContext, + ) -> Task<()> { let relative_to = self.relative_to.clone(); let worktrees = self .project @@ -140,7 +200,7 @@ impl FileFinderDelegate { cx.spawn(|picker, mut cx| async move { let matches = fuzzy::match_path_sets( candidate_sets.as_slice(), - &query, + query.path_query(), relative_to, false, 100, @@ -163,18 +223,18 @@ impl FileFinderDelegate { &mut self, search_id: usize, did_cancel: bool, - query: String, + query: FileSearchQuery, matches: Vec, cx: &mut ViewContext, ) { if search_id >= self.latest_search_id { self.latest_search_id = search_id; - if self.latest_search_did_cancel && query == self.latest_search_query { + if self.latest_search_did_cancel && Some(&query) == self.latest_search_query.as_ref() { util::extend_sorted(&mut self.matches, matches.into_iter(), 100, |a, b| b.cmp(a)); } else { self.matches = matches; } - self.latest_search_query = query; + self.latest_search_query = Some(query); self.latest_search_did_cancel = did_cancel; cx.notify(); } @@ -209,14 +269,14 @@ impl PickerDelegate for FileFinderDelegate { cx.notify(); } - fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { - if query.is_empty() { + fn update_matches(&mut self, raw_query: String, cx: &mut ViewContext) -> Task<()> { + if raw_query.is_empty() { self.latest_search_id = post_inc(&mut self.search_count); self.matches.clear(); cx.notify(); Task::ready(()) } else { - self.spawn_search(query, cx) + self.spawn_search(FileSearchQuery::new(raw_query), cx) } } @@ -230,6 +290,8 @@ impl PickerDelegate for FileFinderDelegate { workspace.update(cx, |workspace, cx| { workspace + // TODO kb need to pass row and column here + // use self.latest_search_query .open_path(project_path.clone(), None, true, cx) .detach_and_log_err(cx); workspace.dismiss_modal(cx); @@ -371,7 +433,7 @@ mod tests { ) }); - let query = "hi".to_string(); + let query = FileSearchQuery::new("hi".to_string()); finder .update(cx, |f, cx| f.delegate_mut().spawn_search(query.clone(), cx)) .await; @@ -455,7 +517,10 @@ mod tests { ) }); finder - .update(cx, |f, cx| f.delegate_mut().spawn_search("hi".into(), cx)) + .update(cx, |f, cx| { + f.delegate_mut() + .spawn_search(FileSearchQuery::new("hi".to_string()), cx) + }) .await; finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 7)); } @@ -491,7 +556,10 @@ mod tests { // Even though there is only one worktree, that worktree's filename // is included in the matching, because the worktree is a single file. finder - .update(cx, |f, cx| f.delegate_mut().spawn_search("thf".into(), cx)) + .update(cx, |f, cx| { + f.delegate_mut() + .spawn_search(FileSearchQuery::new("thf".to_string()), cx) + }) .await; cx.read(|cx| { let finder = finder.read(cx); @@ -509,7 +577,10 @@ mod tests { // Since the worktree root is a file, searching for its name followed by a slash does // not match anything. finder - .update(cx, |f, cx| f.delegate_mut().spawn_search("thf/".into(), cx)) + .update(cx, |f, cx| { + f.delegate_mut() + .spawn_search(FileSearchQuery::new("thf/".to_string()), cx) + }) .await; finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 0)); } @@ -553,7 +624,10 @@ mod tests { // Run a search that matches two files with the same relative path. finder - .update(cx, |f, cx| f.delegate_mut().spawn_search("a.t".into(), cx)) + .update(cx, |f, cx| { + f.delegate_mut() + .spawn_search(FileSearchQuery::new("a.t".to_string()), cx) + }) .await; // Can switch between different matches with the same relative path. @@ -609,7 +683,8 @@ mod tests { finder .update(cx, |f, cx| { - f.delegate_mut().spawn_search("a.txt".into(), cx) + f.delegate_mut() + .spawn_search(FileSearchQuery::new("a.txt".to_string()), cx) }) .await; @@ -651,7 +726,10 @@ mod tests { ) }); finder - .update(cx, |f, cx| f.delegate_mut().spawn_search("dir".into(), cx)) + .update(cx, |f, cx| { + f.delegate_mut() + .spawn_search(FileSearchQuery::new("dir".to_string()), cx) + }) .await; cx.read(|cx| { let finder = finder.read(cx); From 54c1e77aff03eeb7f203256dd760269404faae4f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 10 May 2023 15:03:51 +0300 Subject: [PATCH 095/168] Move the caret to the opened file --- crates/file_finder/src/file_finder.rs | 83 +++++++++++++++++++++------ 1 file changed, 65 insertions(+), 18 deletions(-) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 62eae72cc5..8181d6a1f0 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -1,3 +1,4 @@ +use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor}; use fuzzy::PathMatch; use gpui::{ actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle, @@ -60,12 +61,12 @@ pub enum Event { Dismissed, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] struct FileSearchQuery { raw_query: String, file_path_end: Option, - file_row: Option, - file_column: Option, + file_row: Option, + file_column: Option, } impl FileSearchQuery { @@ -77,30 +78,43 @@ impl FileSearchQuery { file_column: None, }; - let mut possible_path_and_coordinates = raw_query.as_str().rsplitn(3, ':').fuse(); + let mut possible_path_and_coordinates = + // TODO kb go_to_line.rs uses ',' as a separator?? + raw_query.as_str().splitn(3, ':').map(str::trim).fuse(); match ( possible_path_and_coordinates.next(), possible_path_and_coordinates.next(), possible_path_and_coordinates.next(), ) { - (Some(column_number_str), Some(row_number_str), Some(file_path_part)) => Self { + (Some(file_path_part), Some(row_number_str), Some(column_number_str)) + if !row_number_str.is_empty() && !column_number_str.is_empty() => + { + Self { + file_path_end: Some(file_path_part.len()), + file_row: match row_number_str.parse().ok() { + None => return fallback_query, + row => row, + }, + file_column: match column_number_str.parse().ok() { + None => return fallback_query, + column => column, + }, + raw_query, + } + } + (Some(file_path_part), Some(row_number_str), _) if !row_number_str.is_empty() => Self { file_path_end: Some(file_path_part.len()), file_row: match row_number_str.parse().ok() { None => return fallback_query, row => row, }, - file_column: match column_number_str.parse().ok() { - None => return fallback_query, - column => column, - }, + file_column: None, raw_query, }, - (Some(row_number_str), Some(file_path_part), None) => Self { + // Covers inputs like `foo.rs:` trimming all extra colons + (Some(file_path_part), _, _) => Self { file_path_end: Some(file_path_part.len()), - file_row: match row_number_str.parse().ok() { - None => return fallback_query, - row => row, - }, + file_row: None, file_column: None, raw_query, }, @@ -229,7 +243,13 @@ impl FileFinderDelegate { ) { if search_id >= self.latest_search_id { self.latest_search_id = search_id; - if self.latest_search_did_cancel && Some(&query) == self.latest_search_query.as_ref() { + if self.latest_search_did_cancel + && Some(query.path_query()) + == self + .latest_search_query + .as_ref() + .map(|query| query.path_query()) + { util::extend_sorted(&mut self.matches, matches.into_iter(), 100, |a, b| b.cmp(a)); } else { self.matches = matches; @@ -290,12 +310,39 @@ impl PickerDelegate for FileFinderDelegate { workspace.update(cx, |workspace, cx| { workspace - // TODO kb need to pass row and column here - // use self.latest_search_query .open_path(project_path.clone(), None, true, cx) .detach_and_log_err(cx); + }); + + workspace.update(cx, |workspace, cx| { + if let Some(query) = &self.latest_search_query { + let row = query.file_row; + let column = query.file_column; + if let Some(row) = row { + if let Some(active_editor) = workspace + .active_item(cx) + .and_then(|active_item| active_item.downcast::()) + { + // TODO kb does not open proper lines for the first time + active_editor.update(cx, |active_editor, cx| { + let snapshot = active_editor.snapshot(cx).display_snapshot; + let point = DisplayPoint::new( + row.saturating_sub(1), + column.map(|column| column.saturating_sub(1)).unwrap_or(0), + ) + .to_point(&snapshot); + active_editor.change_selections( + Some(Autoscroll::center()), + cx, + |s| s.select_ranges([point..point]), + ); + }) + } + } + } + workspace.dismiss_modal(cx); - }) + }); } } } From 0db7f4202ab242584e147c4e8720df6d2dca5d74 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 10 May 2023 21:37:59 +0300 Subject: [PATCH 096/168] Properly place the caret into the window of the file opened co-authored-by: Mikayla Maki --- crates/file_finder/src/file_finder.rs | 65 ++++++++++++++++----------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 8181d6a1f0..3ad777b7fa 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -1,4 +1,4 @@ -use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor}; +use editor::{scroll::autoscroll::Autoscroll, Bias, DisplayPoint, Editor}; use fuzzy::PathMatch; use gpui::{ actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle, @@ -308,41 +308,54 @@ impl PickerDelegate for FileFinderDelegate { path: m.path.clone(), }; - workspace.update(cx, |workspace, cx| { - workspace - .open_path(project_path.clone(), None, true, cx) - .detach_and_log_err(cx); + let open_task = workspace.update(cx, |workspace, cx| { + workspace.open_path(project_path.clone(), None, true, cx) }); - workspace.update(cx, |workspace, cx| { - if let Some(query) = &self.latest_search_query { - let row = query.file_row; - let column = query.file_column; - if let Some(row) = row { - if let Some(active_editor) = workspace - .active_item(cx) - .and_then(|active_item| active_item.downcast::()) - { - // TODO kb does not open proper lines for the first time - active_editor.update(cx, |active_editor, cx| { - let snapshot = active_editor.snapshot(cx).display_snapshot; + let workspace = workspace.downgrade(); + + cx.spawn(|file_finder, mut cx| async move { + let item = open_task.await.log_err()?; + + let (row, col) = file_finder + .read_with(&cx, |file_finder, _| { + file_finder + .delegate() + .latest_search_query + .as_ref() + .map(|query| (query.file_row, query.file_column)) + }) + .log_err() + .flatten()?; + + if let Some(row) = row { + if let Some(active_editor) = item.downcast::() { + active_editor + .downgrade() + .update(&mut cx, |editor, cx| { + let snapshot = editor.snapshot(cx).display_snapshot; let point = DisplayPoint::new( row.saturating_sub(1), - column.map(|column| column.saturating_sub(1)).unwrap_or(0), + col.map(|column| column.saturating_sub(1)).unwrap_or(0), ) .to_point(&snapshot); - active_editor.change_selections( - Some(Autoscroll::center()), - cx, - |s| s.select_ranges([point..point]), - ); + let point = + snapshot.buffer_snapshot.clip_point(point, Bias::Left); + editor.change_selections(Some(Autoscroll::center()), cx, |s| { + s.select_ranges([point..point]) + }); }) - } + .log_err(); } } - workspace.dismiss_modal(cx); - }); + workspace + .update(&mut cx, |workspace, cx| workspace.dismiss_modal(cx)) + .log_err(); + + Some(()) + }) + .detach(); } } } From e9606982e6ed2ecec14b085d2a017cbae8a37cba Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 May 2023 11:32:41 +0300 Subject: [PATCH 097/168] Use ':' instead of ',' to separate files, rows and columns --- crates/editor/src/editor.rs | 1 + crates/editor/src/items.rs | 7 ++++++- crates/go_to_line/src/go_to_line.rs | 17 ++++++++++------- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b6d44397a9..915bd14786 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -98,6 +98,7 @@ const MAX_SELECTION_HISTORY_LEN: usize = 1024; const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75); pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); +pub const FILE_ROW_COLUMN_DELIMITER: char = ':'; #[derive(Clone, Deserialize, PartialEq, Default)] pub struct SelectNext { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index d2b9c20803..a99f9c3d08 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -2,6 +2,7 @@ use crate::{ display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition, movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, + FILE_ROW_COLUMN_DELIMITER, }; use anyhow::{Context, Result}; use collections::HashSet; @@ -1112,7 +1113,11 @@ impl View for CursorPosition { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(position) = self.position { let theme = &cx.global::().theme.workspace.status_bar; - let mut text = format!("{},{}", position.row + 1, position.column + 1); + let mut text = format!( + "{}{FILE_ROW_COLUMN_DELIMITER}{}", + position.row + 1, + position.column + 1 + ); if self.selected_count > 0 { write!(text, " ({} selected)", self.selected_count).unwrap(); } diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 90287e9270..d6db685906 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -1,6 +1,9 @@ use std::sync::Arc; -use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, DisplayPoint, Editor}; +use editor::{ + display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, DisplayPoint, Editor, + FILE_ROW_COLUMN_DELIMITER, +}; use gpui::{ actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Axis, Entity, View, ViewContext, ViewHandle, @@ -97,14 +100,14 @@ impl GoToLine { editor::Event::Blurred => cx.emit(Event::Dismissed), editor::Event::BufferEdited { .. } => { let line_editor = self.line_editor.read(cx).text(cx); - let mut components = line_editor.trim().split(&[',', ':'][..]); + let mut components = line_editor + .splitn(2, FILE_ROW_COLUMN_DELIMITER) + .map(str::trim) + .fuse(); let row = components.next().and_then(|row| row.parse::().ok()); let column = components.next().and_then(|row| row.parse::().ok()); if let Some(point) = row.map(|row| { - Point::new( - row.saturating_sub(1), - column.map(|column| column.saturating_sub(1)).unwrap_or(0), - ) + Point::new(row.saturating_sub(1), column.unwrap_or(0).saturating_sub(1)) }) { self.active_editor.update(cx, |active_editor, cx| { let snapshot = active_editor.snapshot(cx).display_snapshot; @@ -147,7 +150,7 @@ impl View for GoToLine { let theme = &cx.global::().theme.picker; let label = format!( - "{},{} of {} lines", + "{}{FILE_ROW_COLUMN_DELIMITER}{} of {} lines", self.cursor_point.row + 1, self.cursor_point.column + 1, self.max_point.row + 1 From e5bca9c8710ecf70377bc1ca61277fadb0005907 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 May 2023 12:17:47 +0300 Subject: [PATCH 098/168] Simplify file-row-column parsing --- crates/file_finder/src/file_finder.rs | 89 +++++++++------------------ 1 file changed, 28 insertions(+), 61 deletions(-) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 3ad777b7fa..3d1a9a0c99 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -1,4 +1,4 @@ -use editor::{scroll::autoscroll::Autoscroll, Bias, DisplayPoint, Editor}; +use editor::{scroll::autoscroll::Autoscroll, Bias, DisplayPoint, Editor, FILE_ROW_COLUMN_DELIMITER}; use fuzzy::PathMatch; use gpui::{ actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle, @@ -71,54 +71,20 @@ struct FileSearchQuery { impl FileSearchQuery { fn new(raw_query: String) -> Self { - let fallback_query = Self { - raw_query: raw_query.clone(), - file_path_end: None, - file_row: None, - file_column: None, - }; + let mut components = raw_query + .as_str() + .splitn(3, FILE_ROW_COLUMN_DELIMITER) + .map(str::trim) + .fuse(); + let file_query = components.next().filter(|str| !str.is_empty()); + let file_row = components.next().and_then(|row| row.parse::().ok()); + let file_column = components.next().and_then(|col| col.parse::().ok()); - let mut possible_path_and_coordinates = - // TODO kb go_to_line.rs uses ',' as a separator?? - raw_query.as_str().splitn(3, ':').map(str::trim).fuse(); - match ( - possible_path_and_coordinates.next(), - possible_path_and_coordinates.next(), - possible_path_and_coordinates.next(), - ) { - (Some(file_path_part), Some(row_number_str), Some(column_number_str)) - if !row_number_str.is_empty() && !column_number_str.is_empty() => - { - Self { - file_path_end: Some(file_path_part.len()), - file_row: match row_number_str.parse().ok() { - None => return fallback_query, - row => row, - }, - file_column: match column_number_str.parse().ok() { - None => return fallback_query, - column => column, - }, - raw_query, - } - } - (Some(file_path_part), Some(row_number_str), _) if !row_number_str.is_empty() => Self { - file_path_end: Some(file_path_part.len()), - file_row: match row_number_str.parse().ok() { - None => return fallback_query, - row => row, - }, - file_column: None, - raw_query, - }, - // Covers inputs like `foo.rs:` trimming all extra colons - (Some(file_path_part), _, _) => Self { - file_path_end: Some(file_path_part.len()), - file_row: None, - file_column: None, - raw_query, - }, - _no_colons_query => fallback_query, + Self { + file_path_end: file_query.map(|query| query.len()), + file_row, + file_column, + raw_query, } } @@ -314,19 +280,18 @@ impl PickerDelegate for FileFinderDelegate { let workspace = workspace.downgrade(); - cx.spawn(|file_finder, mut cx| async move { - let item = open_task.await.log_err()?; - - let (row, col) = file_finder - .read_with(&cx, |file_finder, _| { - file_finder - .delegate() - .latest_search_query - .as_ref() - .map(|query| (query.file_row, query.file_column)) - }) - .log_err() - .flatten()?; + if let Some(row) = self + .latest_search_query + .as_ref() + .and_then(|query| query.file_row) + .map(|row| row.saturating_sub(1)) + { + let col = self + .latest_search_query + .as_ref() + .and_then(|query| query.file_column) + .unwrap_or(0) + .saturating_sub(1); if let Some(row) = row { if let Some(active_editor) = item.downcast::() { @@ -339,6 +304,8 @@ impl PickerDelegate for FileFinderDelegate { col.map(|column| column.saturating_sub(1)).unwrap_or(0), ) .to_point(&snapshot); + let point = + snapshot.buffer_snapshot.clip_point(point, Bias::Left); let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left); editor.change_selections(Some(Autoscroll::center()), cx, |s| { From 477bc8da05c5771bce4189c6393cfb3f01d155c3 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 May 2023 12:23:05 +0300 Subject: [PATCH 099/168] Make Go To Line to respect column numbers --- crates/go_to_line/src/go_to_line.rs | 41 ++++++++++++++++------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index d6db685906..072ba2f199 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -1,8 +1,7 @@ use std::sync::Arc; use editor::{ - display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, DisplayPoint, Editor, - FILE_ROW_COLUMN_DELIMITER, + display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor, FILE_ROW_COLUMN_DELIMITER, }; use gpui::{ actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Axis, Entity, @@ -78,15 +77,16 @@ impl GoToLine { fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { self.prev_scroll_position.take(); - self.active_editor.update(cx, |active_editor, cx| { - if let Some(rows) = active_editor.highlighted_rows() { + if let Some(point) = self.point_from_query(cx) { + self.active_editor.update(cx, |active_editor, cx| { let snapshot = active_editor.snapshot(cx).display_snapshot; - let position = DisplayPoint::new(rows.start, 0).to_point(&snapshot); + let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left); active_editor.change_selections(Some(Autoscroll::center()), cx, |s| { - s.select_ranges([position..position]) + s.select_ranges([point..point]) }); - } - }); + }); + } + cx.emit(Event::Dismissed); } @@ -99,16 +99,7 @@ impl GoToLine { match event { editor::Event::Blurred => cx.emit(Event::Dismissed), editor::Event::BufferEdited { .. } => { - let line_editor = self.line_editor.read(cx).text(cx); - let mut components = line_editor - .splitn(2, FILE_ROW_COLUMN_DELIMITER) - .map(str::trim) - .fuse(); - let row = components.next().and_then(|row| row.parse::().ok()); - let column = components.next().and_then(|row| row.parse::().ok()); - if let Some(point) = row.map(|row| { - Point::new(row.saturating_sub(1), column.unwrap_or(0).saturating_sub(1)) - }) { + if let Some(point) = self.point_from_query(cx) { self.active_editor.update(cx, |active_editor, cx| { let snapshot = active_editor.snapshot(cx).display_snapshot; let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left); @@ -123,6 +114,20 @@ impl GoToLine { _ => {} } } + + fn point_from_query(&self, cx: &ViewContext) -> Option { + let line_editor = self.line_editor.read(cx).text(cx); + let mut components = line_editor + .splitn(2, FILE_ROW_COLUMN_DELIMITER) + .map(str::trim) + .fuse(); + let row = components.next().and_then(|row| row.parse::().ok())?; + let column = components.next().and_then(|col| col.parse::().ok()); + Some(Point::new( + row.saturating_sub(1), + column.unwrap_or(0).saturating_sub(1), + )) + } } impl Entity for GoToLine { From 89fe5c6b0990f484032c0ff22f3b6059664326d8 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 May 2023 11:45:29 +0300 Subject: [PATCH 100/168] Test caret selection in file finder co-authored-by: Max --- Cargo.lock | 1 + crates/editor/src/hover_popover.rs | 3 +- crates/file_finder/Cargo.toml | 1 + crates/file_finder/src/file_finder.rs | 216 +++++++++++++++++++++++--- 4 files changed, 198 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e009cfd342..871a95c190 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2185,6 +2185,7 @@ dependencies = [ "project", "serde_json", "settings", + "text", "theme", "util", "workspace", diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 438c662ed1..85edb97da4 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1006,8 +1006,7 @@ mod tests { .zip(expected_styles.iter().cloned()) .collect::>(); assert_eq!( - rendered.text, - dbg!(expected_text), + rendered.text, expected_text, "wrong text for input {blocks:?}" ); assert_eq!( diff --git a/crates/file_finder/Cargo.toml b/crates/file_finder/Cargo.toml index 30a4650ad7..8b99cc5856 100644 --- a/crates/file_finder/Cargo.toml +++ b/crates/file_finder/Cargo.toml @@ -16,6 +16,7 @@ menu = { path = "../menu" } picker = { path = "../picker" } project = { path = "../project" } settings = { path = "../settings" } +text = { path = "../text" } util = { path = "../util" } theme = { path = "../theme" } workspace = { path = "../workspace" } diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 3d1a9a0c99..6447706358 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -1,4 +1,4 @@ -use editor::{scroll::autoscroll::Autoscroll, Bias, DisplayPoint, Editor, FILE_ROW_COLUMN_DELIMITER}; +use editor::{scroll::autoscroll::Autoscroll, Bias, Editor, FILE_ROW_COLUMN_DELIMITER}; use fuzzy::PathMatch; use gpui::{ actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle, @@ -13,6 +13,7 @@ use std::{ Arc, }, }; +use text::Point; use util::{post_inc, ResultExt}; use workspace::Workspace; @@ -64,7 +65,7 @@ pub enum Event { #[derive(Debug, Clone)] struct FileSearchQuery { raw_query: String, - file_path_end: Option, + file_query_end: Option, file_row: Option, file_column: Option, } @@ -81,7 +82,9 @@ impl FileSearchQuery { let file_column = components.next().and_then(|col| col.parse::().ok()); Self { - file_path_end: file_query.map(|query| query.len()), + file_query_end: file_query + .filter(|_| file_row.is_some()) + .map(|query| query.len()), file_row, file_column, raw_query, @@ -89,7 +92,7 @@ impl FileSearchQuery { } fn path_query(&self) -> &str { - match self.file_path_end { + match self.file_query_end { Some(file_path_end) => &self.raw_query[..file_path_end], None => &self.raw_query, } @@ -280,32 +283,28 @@ impl PickerDelegate for FileFinderDelegate { let workspace = workspace.downgrade(); - if let Some(row) = self + let row = self .latest_search_query .as_ref() .and_then(|query| query.file_row) - .map(|row| row.saturating_sub(1)) - { - let col = self - .latest_search_query - .as_ref() - .and_then(|query| query.file_column) - .unwrap_or(0) - .saturating_sub(1); - + .map(|row| row.saturating_sub(1)); + let col = self + .latest_search_query + .as_ref() + .and_then(|query| query.file_column) + .unwrap_or(0) + .saturating_sub(1); + cx.spawn(|_, mut cx| async move { + let item = open_task.await.log_err()?; if let Some(row) = row { if let Some(active_editor) = item.downcast::() { active_editor .downgrade() .update(&mut cx, |editor, cx| { let snapshot = editor.snapshot(cx).display_snapshot; - let point = DisplayPoint::new( - row.saturating_sub(1), - col.map(|column| column.saturating_sub(1)).unwrap_or(0), - ) - .to_point(&snapshot); - let point = - snapshot.buffer_snapshot.clip_point(point, Bias::Left); + let point = snapshot + .buffer_snapshot + .clip_point(Point::new(row, col), Bias::Left); let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left); editor.change_selections(Some(Autoscroll::center()), cx, |s| { @@ -359,6 +358,7 @@ impl PickerDelegate for FileFinderDelegate { mod tests { use super::*; use editor::Editor; + use gpui::executor::Deterministic; use menu::{Confirm, SelectNext}; use serde_json::json; use workspace::{AppState, Workspace}; @@ -426,6 +426,180 @@ mod tests { }); } + #[gpui::test] + async fn test_row_column_numbers_query_inside_file( + deterministic: Arc, + cx: &mut gpui::TestAppContext, + ) { + let app_state = cx.update(|cx| { + super::init(cx); + editor::init(cx); + AppState::test(cx) + }); + + let first_file_name = "first.rs"; + let first_file_contents = "// First Rust file"; + app_state + .fs + .as_fake() + .insert_tree( + "/src", + json!({ + "test": { + first_file_name: first_file_contents, + "second.rs": "// Second Rust file", + } + }), + ) + .await; + + let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; + let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + cx.dispatch_action(window_id, Toggle); + let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); + + let file_query = &first_file_name[..3]; + let file_row = 1; + let file_column = 3; + assert!(file_column <= first_file_contents.len()); + let query_inside_file = format!("{file_query}:{file_row}:{file_column}"); + finder + .update(cx, |finder, cx| { + finder + .delegate_mut() + .update_matches(query_inside_file.to_string(), cx) + }) + .await; + finder.read_with(cx, |finder, _| { + let finder = finder.delegate(); + assert_eq!(finder.matches.len(), 1); + let latest_search_query = finder + .latest_search_query + .as_ref() + .expect("Finder should have a query after the update_matches call"); + assert_eq!(latest_search_query.raw_query, query_inside_file); + assert_eq!(latest_search_query.file_row, Some(file_row)); + assert_eq!(latest_search_query.file_column, Some(file_column as u32)); + assert_eq!(latest_search_query.file_query_end, Some(file_query.len())); + }); + + let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); + cx.dispatch_action(window_id, SelectNext); + cx.dispatch_action(window_id, Confirm); + active_pane + .condition(cx, |pane, _| pane.active_item().is_some()) + .await; + let editor = cx.update(|cx| { + let active_item = active_pane.read(cx).active_item().unwrap(); + active_item.downcast::().unwrap() + }); + deterministic.advance_clock(std::time::Duration::from_secs(2)); + deterministic.start_waiting(); + deterministic.finish_waiting(); + editor.update(cx, |editor, cx| { + let all_selections = editor.selections.all_adjusted(cx); + assert_eq!( + all_selections.len(), + 1, + "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}" + ); + let caret_selection = all_selections.into_iter().next().unwrap(); + assert_eq!(caret_selection.start, caret_selection.end, + "Caret selection should have its start and end at the same position"); + assert_eq!(file_row, caret_selection.start.row + 1, + "Query inside file should get caret with the same focus row"); + assert_eq!(file_column, caret_selection.start.column as usize + 1, + "Query inside file should get caret with the same focus column"); + }); + } + + #[gpui::test] + async fn test_row_column_numbers_query_outside_file( + deterministic: Arc, + cx: &mut gpui::TestAppContext, + ) { + let app_state = cx.update(|cx| { + super::init(cx); + editor::init(cx); + AppState::test(cx) + }); + + let first_file_name = "first.rs"; + let first_file_contents = "// First Rust file"; + app_state + .fs + .as_fake() + .insert_tree( + "/src", + json!({ + "test": { + first_file_name: first_file_contents, + "second.rs": "// Second Rust file", + } + }), + ) + .await; + + let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; + let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + cx.dispatch_action(window_id, Toggle); + let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); + + let file_query = &first_file_name[..3]; + let file_row = 200; + let file_column = 300; + assert!(file_column > first_file_contents.len()); + let query_outside_file = format!("{file_query}:{file_row}:{file_column}"); + finder + .update(cx, |finder, cx| { + finder + .delegate_mut() + .update_matches(query_outside_file.to_string(), cx) + }) + .await; + finder.read_with(cx, |finder, _| { + let finder = finder.delegate(); + assert_eq!(finder.matches.len(), 1); + let latest_search_query = finder + .latest_search_query + .as_ref() + .expect("Finder should have a query after the update_matches call"); + assert_eq!(latest_search_query.raw_query, query_outside_file); + assert_eq!(latest_search_query.file_row, Some(file_row)); + assert_eq!(latest_search_query.file_column, Some(file_column as u32)); + assert_eq!(latest_search_query.file_query_end, Some(file_query.len())); + }); + + let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); + cx.dispatch_action(window_id, SelectNext); + cx.dispatch_action(window_id, Confirm); + active_pane + .condition(cx, |pane, _| pane.active_item().is_some()) + .await; + let editor = cx.update(|cx| { + let active_item = active_pane.read(cx).active_item().unwrap(); + active_item.downcast::().unwrap() + }); + deterministic.advance_clock(std::time::Duration::from_secs(2)); + deterministic.start_waiting(); + deterministic.finish_waiting(); + editor.update(cx, |editor, cx| { + let all_selections = editor.selections.all_adjusted(cx); + assert_eq!( + all_selections.len(), + 1, + "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}" + ); + let caret_selection = all_selections.into_iter().next().unwrap(); + assert_eq!(caret_selection.start, caret_selection.end, + "Caret selection should have its start and end at the same position"); + assert_eq!(0, caret_selection.start.row, + "Excessive rows (as in query outside file borders) should get trimmed to last file row"); + assert_eq!(first_file_contents.len(), caret_selection.start.column as usize, + "Excessive columns (as in query outside file borders) should get trimmed to selected row's last column"); + }); + } + #[gpui::test] async fn test_matching_cancellation(cx: &mut gpui::TestAppContext) { let app_state = cx.update(AppState::test); From d7193521520d08b3f166434d952fc67d0b990c91 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 12 May 2023 16:52:07 +0300 Subject: [PATCH 101/168] Unify path:row:column parsing, use it in CLI --- Cargo.lock | 2 + crates/cli/Cargo.toml | 1 + crates/cli/src/main.rs | 30 +++++-- crates/editor/src/editor.rs | 1 - crates/editor/src/items.rs | 3 +- crates/file_finder/src/file_finder.rs | 112 +++++++++++++------------- crates/go_to_line/Cargo.toml | 1 + crates/go_to_line/src/go_to_line.rs | 5 +- crates/util/src/paths.rs | 37 +++++++++ 9 files changed, 127 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 871a95c190..41c3eab821 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1097,6 +1097,7 @@ dependencies = [ "plist", "serde", "serde_derive", + "util", ] [[package]] @@ -2675,6 +2676,7 @@ dependencies = [ "postage", "settings", "text", + "util", "workspace", ] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 9b8009dd69..2b4a375a5b 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -19,6 +19,7 @@ dirs = "3.0" ipc-channel = "0.16" serde.workspace = true serde_derive.workspace = true +util = { path = "../util" } [target.'cfg(target_os = "macos")'.dependencies] core-foundation = "0.9" diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 0ae4d2477e..80ec2bbf99 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -16,6 +16,7 @@ use std::{ path::{Path, PathBuf}, ptr, }; +use util::paths::PathLikeWithPosition; #[derive(Parser)] #[clap(name = "zed", global_setting(clap::AppSettings::NoAutoVersion))] @@ -24,8 +25,11 @@ struct Args { #[clap(short, long)] wait: bool, /// A sequence of space-separated paths that you want to open. - #[clap()] - paths: Vec, + /// + /// Use `path:line:row` syntax to open a file at a specific location. + /// Non-existing paths and directories will ignore `:line:row` suffix. + #[clap(value_parser = parse_path_with_position)] + paths_with_position: Vec>, /// Print Zed's version and the app path. #[clap(short, long)] version: bool, @@ -34,6 +38,14 @@ struct Args { bundle_path: Option, } +fn parse_path_with_position( + argument_str: &str, +) -> Result, std::convert::Infallible> { + PathLikeWithPosition::parse_str(argument_str, |path_str| { + Ok(Path::new(path_str).to_path_buf()) + }) +} + #[derive(Debug, Deserialize)] struct InfoPlist { #[serde(rename = "CFBundleShortVersionString")] @@ -50,7 +62,11 @@ fn main() -> Result<()> { return Ok(()); } - for path in args.paths.iter() { + for path in args + .paths_with_position + .iter() + .map(|path_with_position| &path_with_position.path_like) + { if !path.exists() { touch(path.as_path())?; } @@ -60,9 +76,13 @@ fn main() -> Result<()> { tx.send(CliRequest::Open { paths: args - .paths + .paths_with_position .into_iter() - .map(|path| fs::canonicalize(path).map_err(|error| anyhow!(error))) + // TODO kb continue sendint path with the position further + .map(|path_with_position| path_with_position.path_like) + .map(|path| { + fs::canonicalize(&path).with_context(|| format!("path {path:?} canonicalization")) + }) .collect::>>()?, wait: args.wait, })?; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 915bd14786..b6d44397a9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -98,7 +98,6 @@ const MAX_SELECTION_HISTORY_LEN: usize = 1024; const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75); pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); -pub const FILE_ROW_COLUMN_DELIMITER: char = ':'; #[derive(Clone, Deserialize, PartialEq, Default)] pub struct SelectNext { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index a99f9c3d08..1a9fcc963e 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -2,7 +2,6 @@ use crate::{ display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition, movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, - FILE_ROW_COLUMN_DELIMITER, }; use anyhow::{Context, Result}; use collections::HashSet; @@ -28,7 +27,7 @@ use std::{ path::{Path, PathBuf}, }; use text::Selection; -use util::{ResultExt, TryFutureExt}; +use util::{paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt}; use workspace::item::{BreadcrumbText, FollowableItemHandle}; use workspace::{ item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem}, diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 6447706358..fae9bd565c 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -1,4 +1,4 @@ -use editor::{scroll::autoscroll::Autoscroll, Bias, Editor, FILE_ROW_COLUMN_DELIMITER}; +use editor::{scroll::autoscroll::Autoscroll, Bias, Editor}; use fuzzy::PathMatch; use gpui::{ actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle, @@ -14,7 +14,7 @@ use std::{ }, }; use text::Point; -use util::{post_inc, ResultExt}; +use util::{paths::PathLikeWithPosition, post_inc, ResultExt}; use workspace::Workspace; pub type FileFinder = Picker; @@ -25,7 +25,7 @@ pub struct FileFinderDelegate { search_count: usize, latest_search_id: usize, latest_search_did_cancel: bool, - latest_search_query: Option, + latest_search_query: Option>, relative_to: Option>, matches: Vec, selected: Option<(usize, Arc)>, @@ -66,31 +66,9 @@ pub enum Event { struct FileSearchQuery { raw_query: String, file_query_end: Option, - file_row: Option, - file_column: Option, } impl FileSearchQuery { - fn new(raw_query: String) -> Self { - let mut components = raw_query - .as_str() - .splitn(3, FILE_ROW_COLUMN_DELIMITER) - .map(str::trim) - .fuse(); - let file_query = components.next().filter(|str| !str.is_empty()); - let file_row = components.next().and_then(|row| row.parse::().ok()); - let file_column = components.next().and_then(|col| col.parse::().ok()); - - Self { - file_query_end: file_query - .filter(|_| file_row.is_some()) - .map(|query| query.len()), - file_row, - file_column, - raw_query, - } - } - fn path_query(&self) -> &str { match self.file_query_end { Some(file_path_end) => &self.raw_query[..file_path_end], @@ -152,7 +130,7 @@ impl FileFinderDelegate { fn spawn_search( &mut self, - query: FileSearchQuery, + query: PathLikeWithPosition, cx: &mut ViewContext, ) -> Task<()> { let relative_to = self.relative_to.clone(); @@ -183,7 +161,7 @@ impl FileFinderDelegate { cx.spawn(|picker, mut cx| async move { let matches = fuzzy::match_path_sets( candidate_sets.as_slice(), - query.path_query(), + query.path_like.path_query(), relative_to, false, 100, @@ -206,18 +184,18 @@ impl FileFinderDelegate { &mut self, search_id: usize, did_cancel: bool, - query: FileSearchQuery, + query: PathLikeWithPosition, matches: Vec, cx: &mut ViewContext, ) { if search_id >= self.latest_search_id { self.latest_search_id = search_id; if self.latest_search_did_cancel - && Some(query.path_query()) + && Some(query.path_like.path_query()) == self .latest_search_query .as_ref() - .map(|query| query.path_query()) + .map(|query| query.path_like.path_query()) { util::extend_sorted(&mut self.matches, matches.into_iter(), 100, |a, b| b.cmp(a)); } else { @@ -265,7 +243,19 @@ impl PickerDelegate for FileFinderDelegate { cx.notify(); Task::ready(()) } else { - self.spawn_search(FileSearchQuery::new(raw_query), cx) + let raw_query = &raw_query; + let query = PathLikeWithPosition::parse_str(raw_query, |path_like_str| { + Ok::<_, std::convert::Infallible>(FileSearchQuery { + raw_query: raw_query.to_owned(), + file_query_end: if path_like_str == raw_query { + None + } else { + Some(path_like_str.len()) + }, + }) + }) + .expect("infallible"); + self.spawn_search(query, cx) } } @@ -286,12 +276,12 @@ impl PickerDelegate for FileFinderDelegate { let row = self .latest_search_query .as_ref() - .and_then(|query| query.file_row) + .and_then(|query| query.row) .map(|row| row.saturating_sub(1)); let col = self .latest_search_query .as_ref() - .and_then(|query| query.file_column) + .and_then(|query| query.column) .unwrap_or(0) .saturating_sub(1); cx.spawn(|_, mut cx| async move { @@ -477,10 +467,13 @@ mod tests { .latest_search_query .as_ref() .expect("Finder should have a query after the update_matches call"); - assert_eq!(latest_search_query.raw_query, query_inside_file); - assert_eq!(latest_search_query.file_row, Some(file_row)); - assert_eq!(latest_search_query.file_column, Some(file_column as u32)); - assert_eq!(latest_search_query.file_query_end, Some(file_query.len())); + assert_eq!(latest_search_query.path_like.raw_query, query_inside_file); + assert_eq!( + latest_search_query.path_like.file_query_end, + Some(file_query.len()) + ); + assert_eq!(latest_search_query.row, Some(file_row)); + assert_eq!(latest_search_query.column, Some(file_column as u32)); }); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); @@ -564,10 +557,13 @@ mod tests { .latest_search_query .as_ref() .expect("Finder should have a query after the update_matches call"); - assert_eq!(latest_search_query.raw_query, query_outside_file); - assert_eq!(latest_search_query.file_row, Some(file_row)); - assert_eq!(latest_search_query.file_column, Some(file_column as u32)); - assert_eq!(latest_search_query.file_query_end, Some(file_query.len())); + assert_eq!(latest_search_query.path_like.raw_query, query_outside_file); + assert_eq!( + latest_search_query.path_like.file_query_end, + Some(file_query.len()) + ); + assert_eq!(latest_search_query.row, Some(file_row)); + assert_eq!(latest_search_query.column, Some(file_column as u32)); }); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); @@ -634,7 +630,7 @@ mod tests { ) }); - let query = FileSearchQuery::new("hi".to_string()); + let query = test_path_like("hi"); finder .update(cx, |f, cx| f.delegate_mut().spawn_search(query.clone(), cx)) .await; @@ -719,8 +715,7 @@ mod tests { }); finder .update(cx, |f, cx| { - f.delegate_mut() - .spawn_search(FileSearchQuery::new("hi".to_string()), cx) + f.delegate_mut().spawn_search(test_path_like("hi"), cx) }) .await; finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 7)); @@ -758,8 +753,7 @@ mod tests { // is included in the matching, because the worktree is a single file. finder .update(cx, |f, cx| { - f.delegate_mut() - .spawn_search(FileSearchQuery::new("thf".to_string()), cx) + f.delegate_mut().spawn_search(test_path_like("thf"), cx) }) .await; cx.read(|cx| { @@ -779,8 +773,7 @@ mod tests { // not match anything. finder .update(cx, |f, cx| { - f.delegate_mut() - .spawn_search(FileSearchQuery::new("thf/".to_string()), cx) + f.delegate_mut().spawn_search(test_path_like("thf/"), cx) }) .await; finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 0)); @@ -826,8 +819,7 @@ mod tests { // Run a search that matches two files with the same relative path. finder .update(cx, |f, cx| { - f.delegate_mut() - .spawn_search(FileSearchQuery::new("a.t".to_string()), cx) + f.delegate_mut().spawn_search(test_path_like("a.t"), cx) }) .await; @@ -884,8 +876,7 @@ mod tests { finder .update(cx, |f, cx| { - f.delegate_mut() - .spawn_search(FileSearchQuery::new("a.txt".to_string()), cx) + f.delegate_mut().spawn_search(test_path_like("a.txt"), cx) }) .await; @@ -928,8 +919,7 @@ mod tests { }); finder .update(cx, |f, cx| { - f.delegate_mut() - .spawn_search(FileSearchQuery::new("dir".to_string()), cx) + f.delegate_mut().spawn_search(test_path_like("dir"), cx) }) .await; cx.read(|cx| { @@ -937,4 +927,18 @@ mod tests { assert_eq!(finder.delegate().matches.len(), 0); }); } + + fn test_path_like(test_str: &str) -> PathLikeWithPosition { + PathLikeWithPosition::parse_str(test_str, |path_like_str| { + Ok::<_, std::convert::Infallible>(FileSearchQuery { + raw_query: test_str.to_owned(), + file_query_end: if path_like_str == test_str { + None + } else { + Some(path_like_str.len()) + }, + }) + }) + .unwrap() + } } diff --git a/crates/go_to_line/Cargo.toml b/crates/go_to_line/Cargo.toml index f279aca569..8f99aa366c 100644 --- a/crates/go_to_line/Cargo.toml +++ b/crates/go_to_line/Cargo.toml @@ -16,3 +16,4 @@ settings = { path = "../settings" } text = { path = "../text" } workspace = { path = "../workspace" } postage.workspace = true +util = { path = "../util" } diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 072ba2f199..967f17b794 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -1,8 +1,6 @@ use std::sync::Arc; -use editor::{ - display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor, FILE_ROW_COLUMN_DELIMITER, -}; +use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor}; use gpui::{ actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Axis, Entity, View, ViewContext, ViewHandle, @@ -10,6 +8,7 @@ use gpui::{ use menu::{Cancel, Confirm}; use settings::Settings; use text::{Bias, Point}; +use util::paths::FILE_ROW_COLUMN_DELIMITER; use workspace::{Modal, Workspace}; actions!(go_to_line, [Toggle]); diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index a324b21a31..280d3cad02 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -70,3 +70,40 @@ pub fn compact(path: &Path) -> PathBuf { path.to_path_buf() } } + +pub const FILE_ROW_COLUMN_DELIMITER: char = ':'; + +#[derive(Debug, Clone)] +pub struct PathLikeWithPosition

{ + pub path_like: P, + pub row: Option, + pub column: Option, +} + +impl

PathLikeWithPosition

{ + pub fn parse_str(s: &str, parse_path_like_str: F) -> Result + where + F: Fn(&str) -> Result, + { + let mut components = s.splitn(3, FILE_ROW_COLUMN_DELIMITER).map(str::trim).fuse(); + let path_like_str = components.next().filter(|str| !str.is_empty()); + let row = components.next().and_then(|row| row.parse::().ok()); + let column = components + .next() + .filter(|_| row.is_some()) + .and_then(|col| col.parse::().ok()); + + Ok(match path_like_str { + Some(path_like_str) => Self { + path_like: parse_path_like_str(path_like_str)?, + row, + column, + }, + None => Self { + path_like: parse_path_like_str(s)?, + row: None, + column: None, + }, + }) + } +} From 628558aa3901caf1c44bd62fb85ec889c4507028 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 12 May 2023 18:13:28 +0300 Subject: [PATCH 102/168] Attempt to open rows and columns from CLI input --- crates/cli/src/cli.rs | 7 ++- crates/cli/src/main.rs | 13 ++-- crates/file_finder/src/file_finder.rs | 2 - crates/util/src/paths.rs | 15 ++++- crates/zed/src/main.rs | 86 ++++++++++++++++++++------- 5 files changed, 91 insertions(+), 32 deletions(-) diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index de7b14e142..8281bcb651 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -1,6 +1,7 @@ pub use ipc_channel::ipc; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use util::paths::PathLikeWithPosition; #[derive(Serialize, Deserialize)] pub struct IpcHandshake { @@ -10,7 +11,11 @@ pub struct IpcHandshake { #[derive(Debug, Serialize, Deserialize)] pub enum CliRequest { - Open { paths: Vec, wait: bool }, + Open { + // TODO kb old cli won't be able to communicate to new Zed with this change + paths: Vec>, + wait: bool, + }, } #[derive(Debug, Serialize, Deserialize)] diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 80ec2bbf99..ff7a65c2fc 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -21,7 +21,7 @@ use util::paths::PathLikeWithPosition; #[derive(Parser)] #[clap(name = "zed", global_setting(clap::AppSettings::NoAutoVersion))] struct Args { - /// Wait for all of the given paths to be closed before exiting. + /// Wait for all of the given paths to be opened/closed before exiting. #[clap(short, long)] wait: bool, /// A sequence of space-separated paths that you want to open. @@ -78,12 +78,13 @@ fn main() -> Result<()> { paths: args .paths_with_position .into_iter() - // TODO kb continue sendint path with the position further - .map(|path_with_position| path_with_position.path_like) - .map(|path| { - fs::canonicalize(&path).with_context(|| format!("path {path:?} canonicalization")) + .map(|path_with_position| { + path_with_position.convert_path(|path| { + fs::canonicalize(&path) + .with_context(|| format!("path {path:?} canonicalization")) + }) }) - .collect::>>()?, + .collect::>()?, wait: args.wait, })?; diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index fae9bd565c..063067891a 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -295,8 +295,6 @@ impl PickerDelegate for FileFinderDelegate { let point = snapshot .buffer_snapshot .clip_point(Point::new(row, col), Bias::Left); - let point = - snapshot.buffer_snapshot.clip_point(point, Bias::Left); editor.change_selections(Some(Autoscroll::center()), cx, |s| { s.select_ranges([point..point]) }); diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 280d3cad02..8a0c91aba6 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -1,5 +1,7 @@ use std::path::{Path, PathBuf}; +use serde::{Deserialize, Serialize}; + lazy_static::lazy_static! { pub static ref HOME: PathBuf = dirs::home_dir().expect("failed to determine home directory"); pub static ref CONFIG_DIR: PathBuf = HOME.join(".config").join("zed"); @@ -73,7 +75,7 @@ pub fn compact(path: &Path) -> PathBuf { pub const FILE_ROW_COLUMN_DELIMITER: char = ':'; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct PathLikeWithPosition

{ pub path_like: P, pub row: Option, @@ -106,4 +108,15 @@ impl

PathLikeWithPosition

{ }, }) } + + pub fn convert_path( + self, + mapping: impl FnOnce(P) -> Result, + ) -> Result, E> { + Ok(PathLikeWithPosition { + path_like: mapping(self.path_like)?, + row: self.row, + column: self.column, + }) + } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 60a2fc66be..369a657527 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -10,7 +10,7 @@ use cli::{ }; use client::{self, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN}; use db::kvp::KEY_VALUE_STORE; -use editor::Editor; +use editor::{scroll::autoscroll::Autoscroll, Editor}; use futures::{ channel::{mpsc, oneshot}, FutureExt, SinkExt, StreamExt, @@ -30,6 +30,7 @@ use settings::{ use simplelog::ConfigBuilder; use smol::process::Command; use std::{ + collections::HashMap, env, ffi::OsStr, fs::OpenOptions, @@ -44,7 +45,9 @@ use std::{ thread, time::Duration, }; +use sum_tree::Bias; use terminal_view::{get_working_directory, TerminalView}; +use text::Point; use util::http::{self, HttpClient}; use welcome::{show_welcome_experience, FIRST_OPEN}; @@ -678,13 +681,29 @@ async fn handle_cli_connection( if let Some(request) = requests.next().await { match request { CliRequest::Open { paths, wait } => { + let mut caret_positions = HashMap::new(); + let paths = if paths.is_empty() { workspace::last_opened_workspace_paths() .await .map(|location| location.paths().to_vec()) - .unwrap_or(paths) + .unwrap_or_default() } else { paths + .into_iter() + .map(|path_with_position| { + let path = path_with_position.path_like; + if let Some(row) = path_with_position.row { + if path.is_file() { + let row = row.saturating_sub(1); + let col = + path_with_position.column.unwrap_or(0).saturating_sub(1); + caret_positions.insert(path.clone(), Point::new(row, col)); + } + } + path + }) + .collect() }; let mut errored = false; @@ -694,11 +713,37 @@ async fn handle_cli_connection( { Ok((workspace, items)) => { let mut item_release_futures = Vec::new(); - cx.update(|cx| { - for (item, path) in items.into_iter().zip(&paths) { - match item { - Some(Ok(item)) => { - let released = oneshot::channel(); + + for (item, path) in items.into_iter().zip(&paths) { + match item { + Some(Ok(item)) => { + if let Some(point) = caret_positions.remove(path) { + // TODO kb does not work + log::info!("@@@@@@@@ {path:?}@{point:?}"); + if let Some(active_editor) = item.downcast::() { + log::info!("@@@@@@@@ editor"); + active_editor + .downgrade() + .update(&mut cx, |editor, cx| { + log::info!("@@@@@@@@ update"); + let snapshot = + editor.snapshot(cx).display_snapshot; + let point = snapshot + .buffer_snapshot + .clip_point(point, Bias::Left); + editor.change_selections( + Some(Autoscroll::center()), + cx, + |s| s.select_ranges([point..point]), + ); + log::info!("@@@@@@@@ finished"); + }) + .log_err(); + } + } + + let released = oneshot::channel(); + cx.update(|cx| { item.on_release( cx, Box::new(move |_| { @@ -706,23 +751,20 @@ async fn handle_cli_connection( }), ) .detach(); - item_release_futures.push(released.1); - } - Some(Err(err)) => { - responses - .send(CliResponse::Stderr { - message: format!( - "error opening {:?}: {}", - path, err - ), - }) - .log_err(); - errored = true; - } - None => {} + }); + item_release_futures.push(released.1); } + Some(Err(err)) => { + responses + .send(CliResponse::Stderr { + message: format!("error opening {:?}: {}", path, err), + }) + .log_err(); + errored = true; + } + None => {} } - }); + } if wait { let background = cx.background(); From 106064c73402c035b92028aecb4b0abf17e1e27c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 13 May 2023 11:39:35 +0300 Subject: [PATCH 103/168] Do not break Zed & Zed CLI compatibility --- crates/cli/src/cli.rs | 13 ++++++------- crates/cli/src/main.rs | 5 +++-- crates/util/src/paths.rs | 16 ++++++++++++++++ crates/zed/src/main.rs | 20 ++++++++++++++++---- 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index 8281bcb651..3a0abbaec7 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -1,7 +1,5 @@ pub use ipc_channel::ipc; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use util::paths::PathLikeWithPosition; #[derive(Serialize, Deserialize)] pub struct IpcHandshake { @@ -11,11 +9,12 @@ pub struct IpcHandshake { #[derive(Debug, Serialize, Deserialize)] pub enum CliRequest { - Open { - // TODO kb old cli won't be able to communicate to new Zed with this change - paths: Vec>, - wait: bool, - }, + // The filed is named `path` for compatibility, but now CLI can request + // opening a path at a certain row and/or column: `some/path:123` and `some/path:123:456`. + // + // Since Zed CLI has to be installed separately, there can be situations when old CLI is + // querying new Zed editors, support both formats by using `String` here and parsing it on Zed side later. + Open { paths: Vec, wait: bool }, } #[derive(Debug, Serialize, Deserialize)] diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index ff7a65c2fc..d4b75f9533 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -79,10 +79,11 @@ fn main() -> Result<()> { .paths_with_position .into_iter() .map(|path_with_position| { - path_with_position.convert_path(|path| { + let path_with_position = path_with_position.convert_path(|path| { fs::canonicalize(&path) .with_context(|| format!("path {path:?} canonicalization")) - }) + })?; + Ok(path_with_position.to_string(|path| path.display().to_string())) }) .collect::>()?, wait: args.wait, diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 8a0c91aba6..96311fabf8 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -119,4 +119,20 @@ impl

PathLikeWithPosition

{ column: self.column, }) } + + pub fn to_string(&self, path_like_to_string: F) -> String + where + F: Fn(&P) -> String, + { + let path_like_string = path_like_to_string(&self.path_like); + if let Some(row) = self.row { + if let Some(column) = self.column { + format!("{path_like_string}:{row}:{column}") + } else { + format!("{path_like_string}:{row}") + } + } else { + path_like_string + } + } } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 369a657527..03fdbf7067 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -37,7 +37,7 @@ use std::{ io::Write as _, os::unix::prelude::OsStrExt, panic, - path::PathBuf, + path::{Path, PathBuf}, sync::{ atomic::{AtomicBool, Ordering}, Arc, Weak, @@ -48,7 +48,10 @@ use std::{ use sum_tree::Bias; use terminal_view::{get_working_directory, TerminalView}; use text::Point; -use util::http::{self, HttpClient}; +use util::{ + http::{self, HttpClient}, + paths::PathLikeWithPosition, +}; use welcome::{show_welcome_experience, FIRST_OPEN}; use fs::RealFs; @@ -691,7 +694,16 @@ async fn handle_cli_connection( } else { paths .into_iter() - .map(|path_with_position| { + .filter_map(|path_with_position_string| { + let path_with_position = PathLikeWithPosition::parse_str( + &path_with_position_string, + |path_str| { + Ok::<_, std::convert::Infallible>( + Path::new(path_str).to_path_buf(), + ) + }, + ) + .expect("Infallible"); let path = path_with_position.path_like; if let Some(row) = path_with_position.row { if path.is_file() { @@ -701,7 +713,7 @@ async fn handle_cli_connection( caret_positions.insert(path.clone(), Point::new(row, col)); } } - path + Some(path) }) .collect() }; From 0c6f1038996a42d217c2a5e5b911eb06a669744a Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 16 May 2023 00:18:31 +0300 Subject: [PATCH 104/168] Return proper items on workspace restoration. co-authored-by: Mikayla --- crates/workspace/src/dock.rs | 7 +- crates/workspace/src/persistence/model.rs | 29 +- crates/workspace/src/workspace.rs | 338 ++++++++++++++-------- crates/zed/src/main.rs | 4 + 4 files changed, 243 insertions(+), 135 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 7efcb7f9d3..a1fce13df4 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -462,7 +462,6 @@ mod tests { let (_, _workspace) = cx.add_window(|cx| { Workspace::new( - Some(serialized_workspace), 0, project.clone(), Arc::new(AppState { @@ -480,6 +479,11 @@ mod tests { ) }); + cx.update(|cx| { + Workspace::load_workspace(_workspace.downgrade(), serialized_workspace, Vec::new(), cx) + }) + .await; + cx.foreground().run_until_parked(); //Should terminate } @@ -605,7 +609,6 @@ mod tests { let project = Project::test(fs, [], cx).await; let (window_id, workspace) = cx.add_window(|cx| { Workspace::new( - None, 0, project.clone(), Arc::new(AppState { diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index a92c369e7a..46a8ab49b2 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -1,5 +1,6 @@ use crate::{ - dock::DockPosition, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, + dock::DockPosition, item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, + WorkspaceId, }; use anyhow::{anyhow, Context, Result}; use async_recursion::async_recursion; @@ -97,17 +98,23 @@ impl SerializedPaneGroup { workspace_id: WorkspaceId, workspace: &WeakViewHandle, cx: &mut AsyncAppContext, - ) -> Option<(Member, Option>)> { + ) -> Option<( + Member, + Option>, + Vec>>, + )> { match self { SerializedPaneGroup::Group { axis, children } => { let mut current_active_pane = None; let mut members = Vec::new(); + let mut items = Vec::new(); for child in children { - if let Some((new_member, active_pane)) = child + if let Some((new_member, active_pane, new_items)) = child .deserialize(project, workspace_id, workspace, cx) .await { members.push(new_member); + items.extend(new_items); current_active_pane = current_active_pane.or(active_pane); } } @@ -117,7 +124,7 @@ impl SerializedPaneGroup { } if members.len() == 1 { - return Some((members.remove(0), current_active_pane)); + return Some((members.remove(0), current_active_pane, items)); } Some(( @@ -126,6 +133,7 @@ impl SerializedPaneGroup { members, }), current_active_pane, + items, )) } SerializedPaneGroup::Pane(serialized_pane) => { @@ -133,7 +141,7 @@ impl SerializedPaneGroup { .update(cx, |workspace, cx| workspace.add_pane(cx).downgrade()) .log_err()?; let active = serialized_pane.active; - serialized_pane + let new_items = serialized_pane .deserialize_to(project, &pane, workspace_id, workspace, cx) .await .log_err()?; @@ -143,7 +151,7 @@ impl SerializedPaneGroup { .log_err()? { let pane = pane.upgrade(cx)?; - Some((Member::Pane(pane.clone()), active.then(|| pane))) + Some((Member::Pane(pane.clone()), active.then(|| pane), new_items)) } else { let pane = pane.upgrade(cx)?; workspace @@ -174,7 +182,8 @@ impl SerializedPane { workspace_id: WorkspaceId, workspace: &WeakViewHandle, cx: &mut AsyncAppContext, - ) -> Result<()> { + ) -> Result>>> { + let mut items = Vec::new(); let mut active_item_index = None; for (index, item) in self.children.iter().enumerate() { let project = project.clone(); @@ -192,6 +201,10 @@ impl SerializedPane { .await .log_err(); + items.push(item_handle.clone()); + + log::info!("ACTUALLY SHOWN ITEMS: {:?}", &item_handle); + if let Some(item_handle) = item_handle { workspace.update(cx, |workspace, cx| { let pane_handle = pane_handle @@ -213,7 +226,7 @@ impl SerializedPane { })?; } - anyhow::Ok(()) + anyhow::Ok(items) } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6350b43415..fe8ea65697 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -82,7 +82,7 @@ use status_bar::StatusBar; pub use status_bar::StatusItemView; use theme::{Theme, ThemeRegistry}; pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; -use util::{paths, ResultExt}; +use util::{async_iife, paths, ResultExt}; lazy_static! { static ref ZED_WINDOW_SIZE: Option = env::var("ZED_WINDOW_SIZE") @@ -493,7 +493,6 @@ struct FollowerState { impl Workspace { pub fn new( - serialized_workspace: Option, workspace_id: WorkspaceId, project: ModelHandle, app_state: Arc, @@ -659,16 +658,6 @@ impl Workspace { this.project_remote_id_changed(project.read(cx).remote_id(), cx); cx.defer(|this, cx| this.update_window_title(cx)); - if let Some(serialized_workspace) = serialized_workspace { - cx.defer(move |_, cx| { - Self::load_from_serialized_workspace(weak_handle, serialized_workspace, cx) - }); - } else if project.read(cx).is_local() { - if cx.global::().default_dock_anchor != DockAnchor::Expanded { - Dock::show(&mut this, false, cx); - } - } - this } @@ -690,8 +679,7 @@ impl Workspace { ); cx.spawn(|mut cx| async move { - let mut serialized_workspace = - persistence::DB.workspace_for_roots(&abs_paths.as_slice()); + let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice()); let paths_to_open = serialized_workspace .as_ref() @@ -700,8 +688,9 @@ impl Workspace { // Get project paths for all of the abs_paths let mut worktree_roots: HashSet> = Default::default(); - let mut project_paths = Vec::new(); - for path in paths_to_open.iter() { + let mut project_paths: Vec<(PathBuf, Option)> = + Vec::with_capacity(paths_to_open.len()); + for path in paths_to_open.iter().cloned() { if let Some((worktree, project_entry)) = cx .update(|cx| { Workspace::project_path_for_path(project_handle.clone(), &path, true, cx) @@ -710,9 +699,9 @@ impl Workspace { .log_err() { worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path())); - project_paths.push(Some(project_entry)); + project_paths.push((path, Some(project_entry))); } else { - project_paths.push(None); + project_paths.push((path, None)); } } @@ -732,27 +721,17 @@ impl Workspace { )) }); - let build_workspace = - |cx: &mut ViewContext, - serialized_workspace: Option| { - let mut workspace = Workspace::new( - serialized_workspace, - workspace_id, - project_handle.clone(), - app_state.clone(), - cx, - ); - (app_state.initialize_workspace)(&mut workspace, &app_state, cx); - workspace - }; + let build_workspace = |cx: &mut ViewContext| { + let mut workspace = + Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx); + (app_state.initialize_workspace)(&mut workspace, &app_state, cx); + + workspace + }; let workspace = requesting_window_id .and_then(|window_id| { - cx.update(|cx| { - cx.replace_root_view(window_id, |cx| { - build_workspace(cx, serialized_workspace.take()) - }) - }) + cx.update(|cx| cx.replace_root_view(window_id, |cx| build_workspace(cx))) }) .unwrap_or_else(|| { let (bounds, display) = if let Some(bounds) = window_bounds_override { @@ -790,44 +769,21 @@ impl Workspace { // Use the serialized workspace to construct the new window cx.add_window( (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), - |cx| build_workspace(cx, serialized_workspace), + |cx| build_workspace(cx), ) .1 }); let workspace = workspace.downgrade(); notify_if_database_failed(&workspace, &mut cx); - - // Call open path for each of the project paths - // (this will bring them to the front if they were in the serialized workspace) - debug_assert!(paths_to_open.len() == project_paths.len()); - let tasks = paths_to_open - .iter() - .cloned() - .zip(project_paths.into_iter()) - .map(|(abs_path, project_path)| { - let workspace = workspace.clone(); - cx.spawn(|mut cx| { - let fs = app_state.fs.clone(); - async move { - let project_path = project_path?; - if fs.is_file(&abs_path).await { - Some( - workspace - .update(&mut cx, |workspace, cx| { - workspace.open_path(project_path, None, true, cx) - }) - .log_err()? - .await, - ) - } else { - None - } - } - }) - }); - - let opened_items = futures::future::join_all(tasks.into_iter()).await; + let opened_items = open_items( + serialized_workspace, + &workspace, + project_paths, + app_state, + cx, + ) + .await; (workspace, opened_items) }) @@ -2536,13 +2492,15 @@ impl Workspace { } } - fn load_from_serialized_workspace( + pub(crate) fn load_workspace( workspace: WeakViewHandle, serialized_workspace: SerializedWorkspace, + paths_to_open: Vec>, cx: &mut AppContext, - ) { + ) -> Task, anyhow::Error>>>> { cx.spawn(|mut cx| async move { - let (project, dock_pane_handle, old_center_pane) = + let result = async_iife! {{ + let (project, dock_pane_handle, old_center_pane) = workspace.read_with(&cx, |workspace, _| { ( workspace.project().clone(), @@ -2551,74 +2509,109 @@ impl Workspace { ) })?; - serialized_workspace - .dock_pane - .deserialize_to( + let dock_items = serialized_workspace + .dock_pane + .deserialize_to( &project, &dock_pane_handle, serialized_workspace.id, &workspace, &mut cx, - ) - .await?; + ) + .await?; - // Traverse the splits tree and add to things - let center_group = serialized_workspace + // Traverse the splits tree and add to things + let something = serialized_workspace .center_group .deserialize(&project, serialized_workspace.id, &workspace, &mut cx) .await; - // Remove old panes from workspace panes list - workspace.update(&mut cx, |workspace, cx| { - if let Some((center_group, active_pane)) = center_group { - workspace.remove_panes(workspace.center.root.clone(), cx); - - // Swap workspace center group - workspace.center = PaneGroup::with_root(center_group); - - // Change the focus to the workspace first so that we retrigger focus in on the pane. - cx.focus_self(); - - if let Some(active_pane) = active_pane { - cx.focus(&active_pane); - } else { - cx.focus(workspace.panes.last().unwrap()); - } - } else { - let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx)); - if let Some(old_center_handle) = old_center_handle { - cx.focus(&old_center_handle) - } else { - cx.focus_self() - } + let mut center_items = None; + let mut center_group = None; + if let Some((group, active_pane, items)) = something { + center_items = Some(items); + center_group = Some((group, active_pane)) } - if workspace.left_sidebar().read(cx).is_open() - != serialized_workspace.left_sidebar_open - { - workspace.toggle_sidebar(SidebarSide::Left, cx); - } + let resulting_list = cx.read(|cx| { + let mut opened_items = center_items + .unwrap_or_default() + .into_iter() + .chain(dock_items.into_iter()) + .filter_map(|item| { + let item = item?; + let project_path = item.project_path(cx)?; + Some((project_path, item)) + }) + .collect::>(); - // Note that without after_window, the focus_self() and - // the focus the dock generates start generating alternating - // focus due to the deferred execution each triggering each other - cx.after_window_update(move |workspace, cx| { - Dock::set_dock_position( - workspace, - serialized_workspace.dock_position, - false, - cx, - ); + paths_to_open + .into_iter() + .map(|path_to_open| { + path_to_open.map(|path_to_open| { + Ok(opened_items.remove(&path_to_open)) + }) + .transpose() + .map(|item| item.flatten()) + .transpose() + }) + .collect::>() }); - cx.notify(); - })?; + // Remove old panes from workspace panes list + workspace.update(&mut cx, |workspace, cx| { + if let Some((center_group, active_pane)) = center_group { + workspace.remove_panes(workspace.center.root.clone(), cx); - // Serialize ourself to make sure our timestamps and any pane / item changes are replicated - workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?; - anyhow::Ok(()) + // Swap workspace center group + workspace.center = PaneGroup::with_root(center_group); + + // Change the focus to the workspace first so that we retrigger focus in on the pane. + cx.focus_self(); + + if let Some(active_pane) = active_pane { + cx.focus(&active_pane); + } else { + cx.focus(workspace.panes.last().unwrap()); + } + } else { + let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx)); + if let Some(old_center_handle) = old_center_handle { + cx.focus(&old_center_handle) + } else { + cx.focus_self() + } + } + + if workspace.left_sidebar().read(cx).is_open() + != serialized_workspace.left_sidebar_open + { + workspace.toggle_sidebar(SidebarSide::Left, cx); + } + + // Note that without after_window, the focus_self() and + // the focus the dock generates start generating alternating + // focus due to the deferred execution each triggering each other + cx.after_window_update(move |workspace, cx| { + Dock::set_dock_position( + workspace, + serialized_workspace.dock_position, + false, + cx, + ); + }); + + cx.notify(); + })?; + + // Serialize ourself to make sure our timestamps and any pane / item changes are replicated + workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?; + + Ok::<_, anyhow::Error>(resulting_list) + }}; + + result.await.unwrap_or_default() }) - .detach_and_log_err(cx); } #[cfg(any(test, feature = "test-support"))] @@ -2634,10 +2627,106 @@ impl Workspace { dock_default_item_factory: |_, _| None, background_actions: || &[], }); - Self::new(None, 0, project, app_state, cx) + Self::new(0, project, app_state, cx) } } +async fn open_items( + serialized_workspace: Option, + workspace: &WeakViewHandle, + mut project_paths_to_open: Vec<(PathBuf, Option)>, + app_state: Arc, + mut cx: AsyncAppContext, +) -> Vec>>> { + let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); + + if let Some(serialized_workspace) = serialized_workspace { + // TODO kb + // If the user is opening a serialized workspace, force open the requested paths + // Requested items: (CLI args or whatever) + // Restored items: What came from the database + // Remaining items = Requested - restored + // For each remaining item, call workspace.open_path() (as below) + + let workspace = workspace.clone(); + let restored_items = cx + .update(|cx| { + Workspace::load_workspace( + workspace, + serialized_workspace, + project_paths_to_open + .iter() + .map(|(_, project_path)| project_path) + .cloned() + .collect(), + cx, + ) + }) + .await; + + let restored_project_paths = cx.read(|cx| { + restored_items + .iter() + .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx)) + .collect::>() + }); + + opened_items = restored_items; + project_paths_to_open + .iter_mut() + .for_each(|(_, project_path)| { + if let Some(project_path_to_open) = project_path { + if restored_project_paths.contains(project_path_to_open) { + *project_path = None; + } + } + }); + } else { + for _ in 0..project_paths_to_open.len() { + opened_items.push(None); + } + } + assert!(opened_items.len() == project_paths_to_open.len()); + + let tasks = + project_paths_to_open + .into_iter() + .enumerate() + .map(|(i, (abs_path, project_path))| { + let workspace = workspace.clone(); + cx.spawn(|mut cx| { + let fs = app_state.fs.clone(); + async move { + let file_project_path = project_path?; + if fs.is_file(&abs_path).await { + Some(( + i, + workspace + .update(&mut cx, |workspace, cx| { + workspace.open_path(file_project_path, None, true, cx) + }) + .log_err()? + .await, + )) + } else { + None + } + } + }) + }); + + for maybe_opened_path in futures::future::join_all(tasks.into_iter()) + .await + .into_iter() + { + if let Some((i, path_open_result)) = maybe_opened_path { + opened_items[i] = Some(path_open_result); + } + } + + opened_items +} + fn notify_if_database_failed(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml"; @@ -3008,8 +3097,7 @@ pub fn join_remote_project( let (_, workspace) = cx.add_window( (app_state.build_window_options)(None, None, cx.platform().as_ref()), |cx| { - let mut workspace = - Workspace::new(Default::default(), 0, project, app_state.clone(), cx); + let mut workspace = Workspace::new(0, project, app_state.clone(), cx); (app_state.initialize_workspace)(&mut workspace, &app_state, cx); workspace }, diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 03fdbf7067..8a659fd0cc 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -729,6 +729,10 @@ async fn handle_cli_connection( for (item, path) in items.into_iter().zip(&paths) { match item { Some(Ok(item)) => { + log::info!("UPDATED ITEMS: {:?}", item); + log::info!( + "caret_positions: {caret_positions:?}, path: {path:?}", + ); if let Some(point) = caret_positions.remove(path) { // TODO kb does not work log::info!("@@@@@@@@ {path:?}@{point:?}"); From be7a58b5082e78794c9f08197a37c4356d29184c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 16 May 2023 17:37:23 +0300 Subject: [PATCH 105/168] Finalize the CLI opening part --- crates/workspace/src/persistence/model.rs | 2 - crates/workspace/src/workspace.rs | 46 +++++++++-------------- crates/zed/src/main.rs | 9 ----- 3 files changed, 17 insertions(+), 40 deletions(-) diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 46a8ab49b2..dd81109d8c 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -203,8 +203,6 @@ impl SerializedPane { items.push(item_handle.clone()); - log::info!("ACTUALLY SHOWN ITEMS: {:?}", &item_handle); - if let Some(item_handle) = item_handle { workspace.update(cx, |workspace, cx| { let pane_handle = pane_handle diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index fe8ea65697..00093639e3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -681,10 +681,7 @@ impl Workspace { cx.spawn(|mut cx| async move { let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice()); - let paths_to_open = serialized_workspace - .as_ref() - .map(|workspace| workspace.location.paths()) - .unwrap_or(Arc::new(abs_paths)); + let paths_to_open = Arc::new(abs_paths); // Get project paths for all of the abs_paths let mut worktree_roots: HashSet> = Default::default(); @@ -1074,6 +1071,8 @@ impl Workspace { visible: bool, cx: &mut ViewContext, ) -> Task, anyhow::Error>>>> { + log::info!("open paths {:?}", abs_paths); + let fs = self.app_state.fs.clone(); // Sort the paths to ensure we add worktrees for parents before their children. @@ -2512,25 +2511,23 @@ impl Workspace { let dock_items = serialized_workspace .dock_pane .deserialize_to( - &project, - &dock_pane_handle, - serialized_workspace.id, - &workspace, - &mut cx, + &project, + &dock_pane_handle, + serialized_workspace.id, + &workspace, + &mut cx, ) .await?; - // Traverse the splits tree and add to things - let something = serialized_workspace - .center_group - .deserialize(&project, serialized_workspace.id, &workspace, &mut cx) - .await; - let mut center_items = None; let mut center_group = None; - if let Some((group, active_pane, items)) = something { - center_items = Some(items); - center_group = Some((group, active_pane)) + // Traverse the splits tree and add to things + if let Some((group, active_pane, items)) = serialized_workspace + .center_group + .deserialize(&project, serialized_workspace.id, &workspace, &mut cx) + .await { + center_items = Some(items); + center_group = Some((group, active_pane)) } let resulting_list = cx.read(|cx| { @@ -2584,7 +2581,7 @@ impl Workspace { } if workspace.left_sidebar().read(cx).is_open() - != serialized_workspace.left_sidebar_open + != serialized_workspace.left_sidebar_open { workspace.toggle_sidebar(SidebarSide::Left, cx); } @@ -2641,13 +2638,6 @@ async fn open_items( let mut opened_items = Vec::with_capacity(project_paths_to_open.len()); if let Some(serialized_workspace) = serialized_workspace { - // TODO kb - // If the user is opening a serialized workspace, force open the requested paths - // Requested items: (CLI args or whatever) - // Restored items: What came from the database - // Remaining items = Requested - restored - // For each remaining item, call workspace.open_path() (as below) - let workspace = workspace.clone(); let restored_items = cx .update(|cx| { @@ -2656,7 +2646,7 @@ async fn open_items( serialized_workspace, project_paths_to_open .iter() - .map(|(_, project_path)| project_path) + .map(|(_, project_path)| dbg!(project_path)) .cloned() .collect(), cx, @@ -2966,8 +2956,6 @@ pub fn open_paths( Vec, anyhow::Error>>>, )>, > { - log::info!("open paths {:?}", abs_paths); - let app_state = app_state.clone(); let abs_paths = abs_paths.to_vec(); cx.spawn(|mut cx| async move { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 8a659fd0cc..1947095bf5 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -729,19 +729,11 @@ async fn handle_cli_connection( for (item, path) in items.into_iter().zip(&paths) { match item { Some(Ok(item)) => { - log::info!("UPDATED ITEMS: {:?}", item); - log::info!( - "caret_positions: {caret_positions:?}, path: {path:?}", - ); if let Some(point) = caret_positions.remove(path) { - // TODO kb does not work - log::info!("@@@@@@@@ {path:?}@{point:?}"); if let Some(active_editor) = item.downcast::() { - log::info!("@@@@@@@@ editor"); active_editor .downgrade() .update(&mut cx, |editor, cx| { - log::info!("@@@@@@@@ update"); let snapshot = editor.snapshot(cx).display_snapshot; let point = snapshot @@ -752,7 +744,6 @@ async fn handle_cli_connection( cx, |s| s.select_ranges([point..point]), ); - log::info!("@@@@@@@@ finished"); }) .log_err(); } From 5d4fc9975038a5be6bae3ed9be537ab54c2a6110 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 16 May 2023 20:48:19 +0300 Subject: [PATCH 106/168] Unit test file:row:column parsing --- crates/cli/src/main.rs | 2 +- crates/util/src/paths.rs | 197 +++++++++++++++++++++++++++++++++------ 2 files changed, 170 insertions(+), 29 deletions(-) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index d4b75f9533..feebbff61b 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -79,7 +79,7 @@ fn main() -> Result<()> { .paths_with_position .into_iter() .map(|path_with_position| { - let path_with_position = path_with_position.convert_path(|path| { + let path_with_position = path_with_position.map_path_like(|path| { fs::canonicalize(&path) .with_context(|| format!("path {path:?} canonicalization")) })?; diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 96311fabf8..f998fc319f 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -73,43 +73,80 @@ pub fn compact(path: &Path) -> PathBuf { } } +/// A delimiter to use in `path_query:row_number:column_number` strings parsing. pub const FILE_ROW_COLUMN_DELIMITER: char = ':'; -#[derive(Debug, Clone, Serialize, Deserialize)] +/// A representation of a path-like string with optional row and column numbers. +/// Matching values example: `te`, `test.rs:22`, `te:22:5`, etc. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PathLikeWithPosition

{ pub path_like: P, pub row: Option, + // Absent if row is absent. pub column: Option, } impl

PathLikeWithPosition

{ - pub fn parse_str(s: &str, parse_path_like_str: F) -> Result - where - F: Fn(&str) -> Result, - { - let mut components = s.splitn(3, FILE_ROW_COLUMN_DELIMITER).map(str::trim).fuse(); - let path_like_str = components.next().filter(|str| !str.is_empty()); - let row = components.next().and_then(|row| row.parse::().ok()); - let column = components - .next() - .filter(|_| row.is_some()) - .and_then(|col| col.parse::().ok()); - - Ok(match path_like_str { - Some(path_like_str) => Self { - path_like: parse_path_like_str(path_like_str)?, - row, - column, - }, - None => Self { - path_like: parse_path_like_str(s)?, + /// Parses a string that possibly has `:row:column` suffix. + /// Ignores trailing `:`s, so `test.rs:22:` is parsed as `test.rs:22`. + /// If any of the row/column component parsing fails, the whole string is then parsed as a path like. + pub fn parse_str( + s: &str, + parse_path_like_str: impl Fn(&str) -> Result, + ) -> Result { + let fallback = |fallback_str| { + Ok(Self { + path_like: parse_path_like_str(fallback_str)?, row: None, column: None, - }, - }) + }) + }; + + match s.trim().split_once(FILE_ROW_COLUMN_DELIMITER) { + Some((path_like_str, maybe_row_and_col_str)) => { + let path_like_str = path_like_str.trim(); + let maybe_row_and_col_str = maybe_row_and_col_str.trim(); + if path_like_str.is_empty() { + fallback(s) + } else if maybe_row_and_col_str.is_empty() { + fallback(path_like_str) + } else { + let (row_parse_result, maybe_col_str) = + match maybe_row_and_col_str.split_once(FILE_ROW_COLUMN_DELIMITER) { + Some((maybe_row_str, maybe_col_str)) => { + (maybe_row_str.parse::(), maybe_col_str.trim()) + } + None => (maybe_row_and_col_str.parse::(), ""), + }; + + match row_parse_result { + Ok(row) => { + if maybe_col_str.is_empty() { + Ok(Self { + path_like: parse_path_like_str(path_like_str)?, + row: Some(row), + column: None, + }) + } else { + match maybe_col_str.parse::() { + Ok(col) => Ok(Self { + path_like: parse_path_like_str(path_like_str)?, + row: Some(row), + column: Some(col), + }), + Err(_) => fallback(s), + } + } + } + Err(_) => fallback(s), + } + } + } + None => fallback(s), + } } - pub fn convert_path( + pub fn map_path_like( self, mapping: impl FnOnce(P) -> Result, ) -> Result, E> { @@ -120,10 +157,7 @@ impl

PathLikeWithPosition

{ }) } - pub fn to_string(&self, path_like_to_string: F) -> String - where - F: Fn(&P) -> String, - { + pub fn to_string(&self, path_like_to_string: impl Fn(&P) -> String) -> String { let path_like_string = path_like_to_string(&self.path_like); if let Some(row) = self.row { if let Some(column) = self.column { @@ -136,3 +170,110 @@ impl

PathLikeWithPosition

{ } } } + +#[cfg(test)] +mod tests { + use super::*; + + type TestPath = PathLikeWithPosition; + + fn parse_str(s: &str) -> TestPath { + TestPath::parse_str(s, |s| Ok::<_, std::convert::Infallible>(s.to_string())) + .expect("infallible") + } + + #[test] + fn path_with_position_parsing_positive() { + let input_and_expected = [ + ( + "test_file.rs", + PathLikeWithPosition { + path_like: "test_file.rs".to_string(), + row: None, + column: None, + }, + ), + ( + "test_file.rs:1", + PathLikeWithPosition { + path_like: "test_file.rs".to_string(), + row: Some(1), + column: None, + }, + ), + ( + "test_file.rs:1:2", + PathLikeWithPosition { + path_like: "test_file.rs".to_string(), + row: Some(1), + column: Some(2), + }, + ), + ]; + + for (input, expected) in input_and_expected { + let actual = parse_str(input); + assert_eq!( + actual, expected, + "For positive case input str '{input}', got a parse mismatch" + ); + } + } + + #[test] + fn path_with_position_parsing_negative() { + for input in [ + "test_file.rs:a", + "test_file.rs:a:b", + "test_file.rs::", + "test_file.rs::1", + "test_file.rs:1::", + "test_file.rs::1:2", + "test_file.rs:1::2", + "test_file.rs:1:2:", + "test_file.rs:1:2:3", + ] { + let actual = parse_str(input); + assert_eq!( + actual, + PathLikeWithPosition { + path_like: input.to_string(), + row: None, + column: None, + }, + "For negative case input str '{input}', got a parse mismatch" + ); + } + } + + // Trim off trailing `:`s for otherwise valid input. + #[test] + fn path_with_position_parsing_special() { + let input_and_expected = [ + ( + "test_file.rs:", + PathLikeWithPosition { + path_like: "test_file.rs".to_string(), + row: None, + column: None, + }, + ), + ( + "test_file.rs:1:", + PathLikeWithPosition { + path_like: "test_file.rs".to_string(), + row: Some(1), + column: None, + }, + ), + ]; + + for (input, expected) in input_and_expected { + let actual = parse_str(input); + assert_eq!( + actual, expected, + "For special case input str '{input}', got a parse mismatch" + ); + } + } +} From 55950e52c20459af3481ac544d4fc2c3e803a384 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 16 May 2023 22:15:56 +0300 Subject: [PATCH 107/168] Remove extra dbg! --- crates/workspace/src/workspace.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 00093639e3..4bdaaedc64 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2646,7 +2646,7 @@ async fn open_items( serialized_workspace, project_paths_to_open .iter() - .map(|(_, project_path)| dbg!(project_path)) + .map(|(_, project_path)| project_path) .cloned() .collect(), cx, From ffd503951ba4a3cc87403cf66177325aae3876ac Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 16 May 2023 17:19:05 -0400 Subject: [PATCH 108/168] Don't make events for every rejected suggestion --- crates/client/src/telemetry.rs | 2 +- crates/editor/src/editor.rs | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index d11a78bb62..5e3c78420d 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -87,7 +87,7 @@ pub enum ClickhouseEvent { copilot_enabled_for_language: bool, }, Copilot { - suggestion_id: String, + suggestion_id: Option, suggestion_accepted: bool, file_extension: Option, }, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c51ed1a14a..ff4c0846cb 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3098,7 +3098,7 @@ impl Editor { .update(cx, |copilot, cx| copilot.accept_completion(completion, cx)) .detach_and_log_err(cx); - self.report_copilot_event(completion.uuid.clone(), true, cx) + self.report_copilot_event(Some(completion.uuid.clone()), true, cx) } self.insert_with_autoindent_mode(&suggestion.text.to_string(), None, cx); cx.notify(); @@ -3117,9 +3117,7 @@ impl Editor { }) .detach_and_log_err(cx); - for completion in &self.copilot_state.completions { - self.report_copilot_event(completion.uuid.clone(), false, cx) - } + self.report_copilot_event(None, false, cx) } self.display_map @@ -6884,7 +6882,7 @@ impl Editor { fn report_copilot_event( &self, - suggestion_id: String, + suggestion_id: Option, suggestion_accepted: bool, cx: &AppContext, ) { From c27859871fe7a74e2d29ab2a6d4f128cfbbae04a Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 16 May 2023 18:16:09 -0400 Subject: [PATCH 109/168] Send editor event when saving a new file --- crates/editor/src/editor.rs | 76 +++++++++++++++++++++---------------- crates/editor/src/items.rs | 7 +++- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ff4c0846cb..21d495d6ee 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1330,7 +1330,7 @@ impl Editor { cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars)); } - this.report_editor_event("open", cx); + this.report_editor_event("open", None, cx); this } @@ -6897,7 +6897,8 @@ impl Editor { .as_singleton() .and_then(|b| b.read(cx).file()) .and_then(|file| Path::new(file.file_name(cx)).extension()) - .and_then(|e| e.to_str()); + .and_then(|e| e.to_str()) + .map(|a| a.to_string()); let telemetry = project.read(cx).client().telemetry().clone(); let telemetry_settings = cx.global::().telemetry(); @@ -6905,49 +6906,58 @@ impl Editor { let event = ClickhouseEvent::Copilot { suggestion_id, suggestion_accepted, - file_extension: file_extension.map(ToString::to_string), + file_extension, }; telemetry.report_clickhouse_event(event, telemetry_settings); } - fn report_editor_event(&self, name: &'static str, cx: &AppContext) { - if let Some((project, file)) = self.project.as_ref().zip( - self.buffer - .read(cx) - .as_singleton() - .and_then(|b| b.read(cx).file()), - ) { - let settings = cx.global::(); + fn report_editor_event( + &self, + name: &'static str, + file_extension: Option, + cx: &AppContext, + ) { + let Some(project) = &self.project else { + return + }; - let extension = Path::new(file.file_name(cx)) - .extension() - .and_then(|e| e.to_str()); - let telemetry = project.read(cx).client().telemetry().clone(); - telemetry.report_mixpanel_event( + // If None, we are in a file without an extension + let file_extension = file_extension.or(self + .buffer + .read(cx) + .as_singleton() + .and_then(|b| b.read(cx).file()) + .and_then(|file| Path::new(file.file_name(cx)).extension()) + .and_then(|e| e.to_str()) + .map(|a| a.to_string())); + + let settings = cx.global::(); + + let telemetry = project.read(cx).client().telemetry().clone(); + telemetry.report_mixpanel_event( match name { "open" => "open editor", "save" => "save editor", _ => name, }, - json!({ "File Extension": extension, "Vim Mode": settings.vim_mode, "In Clickhouse": true }), + json!({ "File Extension": file_extension, "Vim Mode": settings.vim_mode, "In Clickhouse": true }), settings.telemetry(), ); - let event = ClickhouseEvent::Editor { - file_extension: extension.map(ToString::to_string), - vim_mode: settings.vim_mode, - operation: name, - copilot_enabled: settings.features.copilot, - copilot_enabled_for_language: settings.show_copilot_suggestions( - self.language_at(0, cx) - .map(|language| language.name()) - .as_deref(), - self.file_at(0, cx) - .map(|file| file.path().clone()) - .as_deref(), - ), - }; - telemetry.report_clickhouse_event(event, settings.telemetry()) - } + let event = ClickhouseEvent::Editor { + file_extension, + vim_mode: settings.vim_mode, + operation: name, + copilot_enabled: settings.features.copilot, + copilot_enabled_for_language: settings.show_copilot_suggestions( + self.language_at(0, cx) + .map(|language| language.name()) + .as_deref(), + self.file_at(0, cx) + .map(|file| file.path().clone()) + .as_deref(), + ), + }; + telemetry.report_clickhouse_event(event, settings.telemetry()) } /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines, diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 1a9fcc963e..9e122cc63d 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -637,7 +637,7 @@ impl Item for Editor { project: ModelHandle, cx: &mut ViewContext, ) -> Task> { - self.report_editor_event("save", cx); + self.report_editor_event("save", None, cx); let format = self.perform_format(project.clone(), FormatTrigger::Save, cx); let buffers = self.buffer().clone().read(cx).all_buffers(); cx.spawn(|_, mut cx| async move { @@ -686,6 +686,11 @@ impl Item for Editor { .as_singleton() .expect("cannot call save_as on an excerpt list"); + let file_extension = abs_path + .extension() + .map(|a| a.to_string_lossy().to_string()); + self.report_editor_event("save", file_extension, cx); + project.update(cx, |project, cx| { project.save_buffer_as(buffer, abs_path, cx) }) From d61b12a05b089515bfaf44526b14bbdc81e5fdf7 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 16 May 2023 18:44:16 -0400 Subject: [PATCH 110/168] Disable usvg's text feature flags to include less dependency code --- crates/gpui/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 35c5010cdd..04862d1814 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -48,7 +48,7 @@ smallvec.workspace = true smol.workspace = true time.workspace = true tiny-skia = "0.5" -usvg = "0.14" +usvg = { version = "0.14", features = [] } uuid = { version = "1.1.2", features = ["v4"] } waker-fn = "1.1.0" From 39618ae32da38e415f46902bdeeb5c966d8b9a1b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 May 2023 14:40:35 -0700 Subject: [PATCH 111/168] Define language settings in the language crate --- Cargo.lock | 8 + crates/auto_update/src/auto_update.rs | 14 +- crates/client/src/client.rs | 17 +- crates/collab/src/tests.rs | 5 +- crates/collab/src/tests/integration_tests.rs | 13 +- crates/command_palette/Cargo.toml | 1 + crates/command_palette/src/command_palette.rs | 20 +- crates/copilot/src/copilot.rs | 88 ++--- crates/copilot_button/Cargo.toml | 1 + crates/copilot_button/src/copilot_button.rs | 103 +++-- crates/diagnostics/src/diagnostics.rs | 15 +- crates/editor/Cargo.toml | 2 + crates/editor/src/display_map.rs | 65 ++-- crates/editor/src/editor.rs | 91 ++--- crates/editor/src/editor_tests.rs | 352 ++++++++++------- crates/editor/src/element.rs | 90 ++--- .../editor/src/highlight_matching_bracket.rs | 4 +- crates/editor/src/hover_popover.rs | 11 +- crates/editor/src/link_go_to_definition.rs | 13 +- crates/editor/src/mouse_context_menu.rs | 5 +- crates/editor/src/movement.rs | 34 +- crates/editor/src/multi_buffer.rs | 23 +- .../src/test/editor_lsp_test_context.rs | 1 + crates/editor/src/test/editor_test_context.rs | 30 +- crates/file_finder/Cargo.toml | 4 +- crates/file_finder/src/file_finder.rs | 35 +- crates/gpui/src/executor.rs | 16 + crates/journal/src/journal.rs | 18 +- crates/language/Cargo.toml | 3 + crates/language/src/buffer.rs | 17 +- crates/language/src/buffer_tests.rs | 73 ++-- crates/language/src/language.rs | 5 + crates/language/src/language_settings.rs | 285 ++++++++++++++ crates/project/src/project.rs | 50 ++- crates/project/src/project_tests.rs | 143 ++++--- crates/project_panel/Cargo.toml | 1 + crates/project_panel/src/project_panel.rs | 27 +- crates/project_symbols/Cargo.toml | 1 + crates/project_symbols/src/project_symbols.rs | 13 +- crates/search/src/buffer_search.rs | 10 +- crates/search/src/project_search.rs | 29 +- crates/settings/src/settings.rs | 300 +-------------- crates/settings/src/settings_file.rs | 4 +- crates/settings/src/settings_store.rs | 360 ++++++++---------- crates/terminal/src/terminal.rs | 2 +- crates/vim/Cargo.toml | 1 + crates/vim/src/test/vim_test_context.rs | 4 +- crates/vim/src/vim.rs | 16 +- crates/workspace/src/workspace.rs | 8 +- crates/zed/src/languages/c.rs | 15 +- crates/zed/src/languages/python.rs | 15 +- crates/zed/src/languages/rust.rs | 15 +- crates/zed/src/languages/yaml.rs | 8 +- crates/zed/src/zed.rs | 25 +- 54 files changed, 1348 insertions(+), 1161 deletions(-) create mode 100644 crates/language/src/language_settings.rs diff --git a/Cargo.lock b/Cargo.lock index 748ec5ef49..f01022e294 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1341,6 +1341,7 @@ dependencies = [ "env_logger 0.9.3", "fuzzy", "gpui", + "language", "picker", "project", "serde_json", @@ -1408,6 +1409,7 @@ dependencies = [ "fs", "futures 0.3.28", "gpui", + "language", "settings", "smol", "theme", @@ -2034,6 +2036,7 @@ dependencies = [ "pulldown-cmark", "rand 0.8.5", "rpc", + "schemars", "serde", "serde_derive", "settings", @@ -2243,6 +2246,7 @@ dependencies = [ "env_logger 0.9.3", "fuzzy", "gpui", + "language", "menu", "picker", "postage", @@ -3427,6 +3431,7 @@ dependencies = [ "futures 0.3.28", "fuzzy", "git", + "glob", "gpui", "indoc", "lazy_static", @@ -3437,6 +3442,7 @@ dependencies = [ "rand 0.8.5", "regex", "rpc", + "schemars", "serde", "serde_derive", "serde_json", @@ -4872,6 +4878,7 @@ dependencies = [ "editor", "futures 0.3.28", "gpui", + "language", "menu", "postage", "project", @@ -7818,6 +7825,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" name = "vim" version = "0.1.0" dependencies = [ + "anyhow", "assets", "async-compat", "async-trait", diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 54f3f93b00..ebfa50aaf8 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -65,12 +65,14 @@ impl Setting for AutoUpdateSetting { type FileContent = Option; - fn load(default_value: &Option, user_values: &[&Option], _: &AppContext) -> Self { - Self( - Self::json_merge(default_value, user_values) - .unwrap() - .unwrap(), - ) + fn load( + default_value: &Option, + user_values: &[&Option], + _: &AppContext, + ) -> Result { + Ok(Self( + Self::json_merge(default_value, user_values)?.ok_or_else(Self::missing_default)?, + )) } } diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index bcf9152051..f1aee9540e 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -350,17 +350,18 @@ impl settings::Setting for TelemetrySettings { default_value: &Self::FileContent, user_values: &[&Self::FileContent], _: &AppContext, - ) -> Self { - Self { - diagnostics: user_values - .first() - .and_then(|v| v.diagnostics) - .unwrap_or(default_value.diagnostics.unwrap()), + ) -> Result { + Ok(Self { + diagnostics: user_values.first().and_then(|v| v.diagnostics).unwrap_or( + default_value + .diagnostics + .ok_or_else(Self::missing_default)?, + ), metrics: user_values .first() .and_then(|v| v.metrics) - .unwrap_or(default_value.metrics.unwrap()), - } + .unwrap_or(default_value.metrics.ok_or_else(Self::missing_default)?), + }) } } diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 50fb9658fe..02e5fa56e4 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -186,7 +186,10 @@ impl TestServer { }) }); - cx.update(|cx| client::init(&client, cx)); + cx.update(|cx| { + client::init(&client, cx); + language::init(cx); + }); let fs = FakeFs::new(cx.background()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx)); diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index e3b5b0be7e..2896c47808 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -18,6 +18,7 @@ use gpui::{ }; use indoc::indoc; use language::{ + language_settings::{AllLanguageSettings, Formatter}, tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig, OffsetRangeExt, Point, Rope, }; @@ -26,7 +27,7 @@ use lsp::LanguageServerId; use project::{search::SearchQuery, DiagnosticSummary, HoverBlockKind, Project, ProjectPath}; use rand::prelude::*; use serde_json::json; -use settings::{Formatter, Settings}; +use settings::{SettingsStore}; use std::{ cell::{Cell, RefCell}, env, future, mem, @@ -4219,10 +4220,12 @@ async fn test_formatting_buffer( // Ensure buffer can be formatted using an external command. Notice how the // host's configuration is honored as opposed to using the guest's settings. cx_a.update(|cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.editor_defaults.formatter = Some(Formatter::External { - command: "awk".to_string(), - arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()], + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |file| { + file.defaults.formatter = Some(Formatter::External { + command: "awk".into(), + arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(), + }); }); }); }); diff --git a/crates/command_palette/Cargo.toml b/crates/command_palette/Cargo.toml index 8ad1843cb6..95ba452c14 100644 --- a/crates/command_palette/Cargo.toml +++ b/crates/command_palette/Cargo.toml @@ -23,6 +23,7 @@ workspace = { path = "../workspace" } [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } editor = { path = "../editor", features = ["test-support"] } +language = { path = "../language", features = ["test-support"] } project = { path = "../project", features = ["test-support"] } serde_json.workspace = true workspace = { path = "../workspace", features = ["test-support"] } diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 4e0e776000..12134f6d6e 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -294,14 +294,7 @@ mod tests { #[gpui::test] async fn test_command_palette(deterministic: Arc, cx: &mut TestAppContext) { - deterministic.forbid_parking(); - let app_state = cx.update(AppState::test); - - cx.update(|cx| { - editor::init(cx); - workspace::init(app_state.clone(), cx); - init(cx); - }); + let app_state = init_test(cx); let project = Project::test(app_state.fs.clone(), [], cx).await; let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); @@ -369,4 +362,15 @@ mod tests { assert!(palette.delegate().matches.is_empty()) }); } + + fn init_test(cx: &mut TestAppContext) -> Arc { + cx.update(|cx| { + let app_state = AppState::test(cx); + language::init(cx); + editor::init(cx); + workspace::init(app_state.clone(), cx); + init(cx); + app_state + }) + } } diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 9ccd9c445d..b966348cd6 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -10,6 +10,7 @@ use gpui::{ actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle, }; use language::{ + language_settings::{all_language_settings, language_settings}, point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16, ToPointUtf16, }; @@ -17,7 +18,7 @@ use log::{debug, error}; use lsp::{LanguageServer, LanguageServerId}; use node_runtime::NodeRuntime; use request::{LogMessage, StatusNotification}; -use settings::Settings; +use settings::SettingsStore; use smol::{fs, io::BufReader, stream::StreamExt}; use std::{ ffi::OsString, @@ -302,56 +303,34 @@ impl Copilot { node_runtime: Arc, cx: &mut ModelContext, ) -> Self { - cx.observe_global::({ - let http = http.clone(); - let node_runtime = node_runtime.clone(); - move |this, cx| { - if cx.global::().features.copilot { - if matches!(this.server, CopilotServer::Disabled) { - let start_task = cx - .spawn({ - let http = http.clone(); - let node_runtime = node_runtime.clone(); - move |this, cx| { - Self::start_language_server(http, node_runtime, this, cx) - } - }) - .shared(); - this.server = CopilotServer::Starting { task: start_task }; - cx.notify(); - } - } else { - this.server = CopilotServer::Disabled; - cx.notify(); - } - } - }) - .detach(); + let mut this = Self { + http, + node_runtime, + server: CopilotServer::Disabled, + buffers: Default::default(), + }; + this.enable_or_disable_copilot(cx); + cx.observe_global::(move |this, cx| this.enable_or_disable_copilot(cx)) + .detach(); + this + } - if cx.global::().features.copilot { - let start_task = cx - .spawn({ - let http = http.clone(); - let node_runtime = node_runtime.clone(); - move |this, cx| async { - Self::start_language_server(http, node_runtime, this, cx).await - } - }) - .shared(); - - Self { - http, - node_runtime, - server: CopilotServer::Starting { task: start_task }, - buffers: Default::default(), + fn enable_or_disable_copilot(&mut self, cx: &mut ModelContext) { + let http = self.http.clone(); + let node_runtime = self.node_runtime.clone(); + if all_language_settings(None, cx).copilot_enabled(None, None) { + if matches!(self.server, CopilotServer::Disabled) { + let start_task = cx + .spawn({ + move |this, cx| Self::start_language_server(http, node_runtime, this, cx) + }) + .shared(); + self.server = CopilotServer::Starting { task: start_task }; + cx.notify(); } } else { - Self { - http, - node_runtime, - server: CopilotServer::Disabled, - buffers: Default::default(), - } + self.server = CopilotServer::Disabled; + cx.notify(); } } @@ -805,13 +784,14 @@ impl Copilot { let snapshot = registered_buffer.report_changes(buffer, cx); let buffer = buffer.read(cx); let uri = registered_buffer.uri.clone(); - let settings = cx.global::(); let position = position.to_point_utf16(buffer); - let language = buffer.language_at(position); - let language_name = language.map(|language| language.name()); - let language_name = language_name.as_deref(); - let tab_size = settings.tab_size(language_name); - let hard_tabs = settings.hard_tabs(language_name); + let settings = language_settings( + None, + buffer.language_at(position).map(|l| l.name()).as_deref(), + cx, + ); + let tab_size = settings.tab_size; + let hard_tabs = settings.hard_tabs; let relative_path = buffer .file() .map(|file| file.path().to_path_buf()) diff --git a/crates/copilot_button/Cargo.toml b/crates/copilot_button/Cargo.toml index 77937e8bd0..ad3febd68c 100644 --- a/crates/copilot_button/Cargo.toml +++ b/crates/copilot_button/Cargo.toml @@ -15,6 +15,7 @@ editor = { path = "../editor" } fs = { path = "../fs" } context_menu = { path = "../context_menu" } gpui = { path = "../gpui" } +language = { path = "../language" } settings = { path = "../settings" } theme = { path = "../theme" } util = { path = "../util" } diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 61c84bc517..d04f1e0e75 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -9,6 +9,7 @@ use gpui::{ AnyElement, AppContext, AsyncAppContext, Element, Entity, MouseState, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; +use language::language_settings::{self, all_language_settings, AllLanguageSettings}; use settings::{update_settings_file, Settings, SettingsStore}; use std::{path::Path, sync::Arc}; use util::{paths, ResultExt}; @@ -40,12 +41,12 @@ impl View for CopilotButton { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let settings = cx.global::(); - - if !settings.features.copilot { + let all_language_settings = &all_language_settings(None, cx); + if !all_language_settings.copilot.feature_enabled { return Empty::new().into_any(); } + let settings = cx.global::(); let theme = settings.theme.clone(); let active = self.popup_menu.read(cx).visible(); let Some(copilot) = Copilot::global(cx) else { @@ -55,7 +56,7 @@ impl View for CopilotButton { let enabled = self .editor_enabled - .unwrap_or(settings.show_copilot_suggestions(None, None)); + .unwrap_or_else(|| all_language_settings.copilot_enabled(None, None)); Stack::new() .with_child( @@ -192,14 +193,14 @@ impl CopilotButton { } pub fn deploy_copilot_menu(&mut self, cx: &mut ViewContext) { - let settings = cx.global::(); let fs = self.fs.clone(); - let mut menu_options = Vec::with_capacity(8); if let Some(language) = self.language.clone() { let fs = fs.clone(); - let language_enabled = settings.copilot_enabled_for_language(Some(language.as_ref())); + let language_enabled = + language_settings::language_settings(None, Some(language.as_ref()), cx) + .show_copilot_suggestions; menu_options.push(ContextMenuItem::handler( format!( "{} Suggestions for {}", @@ -210,6 +211,8 @@ impl CopilotButton { )); } + let settings = settings::get_setting::(None, cx); + if let Some(path) = self.path.as_ref() { let path_enabled = settings.copilot_enabled_for_path(path); let path = path.clone(); @@ -234,7 +237,7 @@ impl CopilotButton { )); } - let globally_enabled = cx.global::().features.copilot; + let globally_enabled = settings.copilot_enabled(None, None); menu_options.push(ContextMenuItem::handler( if globally_enabled { "Hide Suggestions for All Files" @@ -246,7 +249,7 @@ impl CopilotButton { menu_options.push(ContextMenuItem::Separator); - let icon_style = settings.theme.copilot.out_link_icon.clone(); + let icon_style = cx.global::().theme.copilot.out_link_icon.clone(); menu_options.push(ContextMenuItem::action( move |state: &mut MouseState, style: &theme::ContextMenuItem| { Flex::row() @@ -272,22 +275,19 @@ impl CopilotButton { pub fn update_enabled(&mut self, editor: ViewHandle, cx: &mut ViewContext) { let editor = editor.read(cx); - let snapshot = editor.buffer().read(cx).snapshot(cx); - let settings = cx.global::(); let suggestion_anchor = editor.selections.newest_anchor().start; - let language_name = snapshot .language_at(suggestion_anchor) .map(|language| language.name()); - let path = snapshot - .file_at(suggestion_anchor) - .map(|file| file.path().clone()); + let path = snapshot.file_at(suggestion_anchor).map(|file| file.path()); - self.editor_enabled = - Some(settings.show_copilot_suggestions(language_name.as_deref(), path.as_deref())); + self.editor_enabled = Some( + all_language_settings(None, cx) + .copilot_enabled(language_name.as_deref(), path.map(|p| p.as_ref())), + ); self.language = language_name; - self.path = path; + self.path = path.cloned(); cx.notify() } @@ -328,27 +328,27 @@ async fn configure_disabled_globs( settings_editor.downgrade().update(&mut cx, |item, cx| { let text = item.buffer().read(cx).snapshot(cx).text(); - let edits = cx - .global::() - .update::(&text, |file| { - let copilot = file.copilot.get_or_insert_with(Default::default); - let globs = copilot.disabled_globs.get_or_insert_with(|| { - cx.global::() - .copilot - .disabled_globs - .clone() - .iter() - .map(|glob| glob.as_str().to_string()) - .collect::>() - }); - - if let Some(path_to_disable) = &path_to_disable { - globs.push(path_to_disable.to_string_lossy().into_owned()); - } else { - globs.clear(); - } + let settings = cx.global::(); + let edits = settings.edits_for_update::(&text, |file| { + let copilot = file.copilot.get_or_insert_with(Default::default); + let globs = copilot.disabled_globs.get_or_insert_with(|| { + settings + .get::(None) + .copilot + .disabled_globs + .clone() + .iter() + .map(|glob| glob.as_str().to_string()) + .collect::>() }); + if let Some(path_to_disable) = &path_to_disable { + globs.push(path_to_disable.to_string_lossy().into_owned()); + } else { + globs.clear(); + } + }); + if !edits.is_empty() { item.change_selections(Some(Autoscroll::newest()), cx, |selections| { selections.select_ranges(edits.iter().map(|e| e.0.clone())); @@ -365,31 +365,26 @@ async fn configure_disabled_globs( } fn toggle_copilot_globally(fs: Arc, cx: &mut AppContext) { - let show_copilot_suggestions = cx.global::().show_copilot_suggestions(None, None); - update_settings_file::(fs, cx, move |file_contents| { - file_contents.editor.show_copilot_suggestions = Some((!show_copilot_suggestions).into()) + let show_copilot_suggestions = all_language_settings(None, cx).copilot_enabled(None, None); + update_settings_file::(fs, cx, move |file| { + file.defaults.show_copilot_suggestions = Some((!show_copilot_suggestions).into()) }); } fn toggle_copilot_for_language(language: Arc, fs: Arc, cx: &mut AppContext) { - let show_copilot_suggestions = cx - .global::() - .show_copilot_suggestions(Some(&language), None); - - update_settings_file::(fs, cx, move |file_contents| { - file_contents.languages.insert( - language, - settings::EditorSettings { - show_copilot_suggestions: Some((!show_copilot_suggestions).into()), - ..Default::default() - }, - ); + let show_copilot_suggestions = + all_language_settings(None, cx).copilot_enabled(Some(&language), None); + update_settings_file::(fs, cx, move |file| { + file.languages + .entry(language) + .or_default() + .show_copilot_suggestions = Some(!show_copilot_suggestions); }); } fn hide_copilot(fs: Arc, cx: &mut AppContext) { - update_settings_file::(fs, cx, move |file_contents| { - file_contents.features.copilot = Some(false) + update_settings_file::(fs, cx, move |file| { + file.features.get_or_insert(Default::default()).copilot = Some(false); }); } diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index b9db439e49..62e03ca54f 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -820,11 +820,13 @@ mod tests { use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped}; use project::FakeFs; use serde_json::json; + use settings::SettingsStore; use unindent::Unindent as _; #[gpui::test] async fn test_diagnostics(cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/test", @@ -1227,7 +1229,8 @@ mod tests { #[gpui::test] async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/test", @@ -1491,6 +1494,14 @@ mod tests { }); } + fn init_test(cx: &mut TestAppContext) { + cx.update(|cx| { + cx.set_global(Settings::test(cx)); + cx.set_global(SettingsStore::test(cx)); + language::init(cx); + }); + } + fn editor_blocks(editor: &ViewHandle, cx: &mut WindowContext) -> Vec<(u32, String)> { editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index e4767a12e2..fc7bf4b8ab 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -49,6 +49,7 @@ workspace = { path = "../workspace" } aho-corasick = "0.7" anyhow.workspace = true futures.workspace = true +glob.workspace = true indoc = "1.0.4" itertools = "0.10" lazy_static.workspace = true @@ -58,6 +59,7 @@ parking_lot.workspace = true postage.workspace = true pulldown-cmark = { version = "0.9.2", default-features = false } rand = { workspace = true, optional = true } +schemars.workspace = true serde.workspace = true serde_derive.workspace = true smallvec.workspace = true diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index e190ec7717..4c36be682b 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -13,8 +13,9 @@ use gpui::{ fonts::{FontId, HighlightStyle}, Entity, ModelContext, ModelHandle, }; -use language::{OffsetUtf16, Point, Subscription as BufferSubscription}; -use settings::Settings; +use language::{ + language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription, +}; use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc}; pub use suggestion_map::Suggestion; use suggestion_map::SuggestionMap; @@ -276,8 +277,7 @@ impl DisplayMap { .as_singleton() .and_then(|buffer| buffer.read(cx).language()) .map(|language| language.name()); - - cx.global::().tab_size(language_name.as_deref()) + language_settings(None, language_name.as_deref(), cx).tab_size } #[cfg(test)] @@ -844,8 +844,12 @@ pub mod tests { use super::*; use crate::{movement, test::marked_display_snapshot}; use gpui::{color::Color, elements::*, test::observe, AppContext}; - use language::{Buffer, Language, LanguageConfig, SelectionGoal}; + use language::{ + language_settings::{AllLanguageSettings, AllLanguageSettingsContent}, + Buffer, Language, LanguageConfig, SelectionGoal, + }; use rand::{prelude::*, Rng}; + use settings::SettingsStore; use smol::stream::StreamExt; use std::{env, sync::Arc}; use theme::SyntaxTheme; @@ -882,9 +886,7 @@ pub mod tests { log::info!("wrap width: {:?}", wrap_width); cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.editor_overrides.tab_size = NonZeroU32::new(tab_size); - cx.set_global(settings) + init_test(cx, |s| s.defaults.tab_size = NonZeroU32::new(tab_size)); }); let buffer = cx.update(|cx| { @@ -939,9 +941,11 @@ pub mod tests { tab_size = *tab_sizes.choose(&mut rng).unwrap(); log::info!("setting tab size to {:?}", tab_size); cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.editor_overrides.tab_size = NonZeroU32::new(tab_size); - cx.set_global(settings) + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, |s| { + s.defaults.tab_size = NonZeroU32::new(tab_size); + }); + }); }); } 30..=44 => { @@ -1119,7 +1123,7 @@ pub mod tests { #[gpui::test(retries = 5)] fn test_soft_wraps(cx: &mut AppContext) { cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); - cx.foreground().forbid_parking(); + init_test(cx, |_| {}); let font_cache = cx.font_cache(); @@ -1131,7 +1135,6 @@ pub mod tests { .unwrap(); let font_size = 12.0; let wrap_width = Some(64.); - cx.set_global(Settings::test(cx)); let text = "one two three four five\nsix seven eight"; let buffer = MultiBuffer::build_simple(text, cx); @@ -1211,7 +1214,8 @@ pub mod tests { #[gpui::test] fn test_text_chunks(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx, |_| {}); + let text = sample_text(6, 6, 'a'); let buffer = MultiBuffer::build_simple(&text, cx); let family_id = cx @@ -1225,6 +1229,7 @@ pub mod tests { let font_size = 14.0; let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx)); + buffer.update(cx, |buffer, cx| { buffer.edit( vec![ @@ -1289,11 +1294,8 @@ pub mod tests { .unwrap(), ); language.set_theme(&theme); - cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.editor_defaults.tab_size = Some(2.try_into().unwrap()); - cx.set_global(settings); - }); + + cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap()))); let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); buffer.condition(cx, |buf, _| !buf.is_parsing()).await; @@ -1382,7 +1384,7 @@ pub mod tests { ); language.set_theme(&theme); - cx.update(|cx| cx.set_global(Settings::test(cx))); + cx.update(|cx| init_test(cx, |_| {})); let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx)); buffer.condition(cx, |buf, _| !buf.is_parsing()).await; @@ -1429,9 +1431,8 @@ pub mod tests { #[gpui::test] async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) { - cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); + cx.update(|cx| init_test(cx, |_| {})); - cx.update(|cx| cx.set_global(Settings::test(cx))); let theme = SyntaxTheme::new(vec![ ("operator".to_string(), Color::red().into()), ("string".to_string(), Color::green().into()), @@ -1510,7 +1511,8 @@ pub mod tests { #[gpui::test] fn test_clip_point(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx, |_| {}); + fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::AppContext) { let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx); @@ -1559,7 +1561,7 @@ pub mod tests { #[gpui::test] fn test_clip_at_line_ends(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx, |_| {}); fn assert(text: &str, cx: &mut gpui::AppContext) { let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx); @@ -1578,7 +1580,8 @@ pub mod tests { #[gpui::test] fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx, |_| {}); + let text = "✅\t\tα\nβ\t\n🏀β\t\tγ"; let buffer = MultiBuffer::build_simple(text, cx); let font_cache = cx.font_cache(); @@ -1639,7 +1642,8 @@ pub mod tests { #[gpui::test] fn test_max_point(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx, |_| {}); + let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx); let font_cache = cx.font_cache(); let family_id = font_cache @@ -1718,4 +1722,13 @@ pub mod tests { } chunks } + + fn init_test(cx: &mut AppContext, f: impl Fn(&mut AllLanguageSettingsContent)) { + cx.foreground().forbid_parking(); + cx.set_global(SettingsStore::test(cx)); + language::init(cx); + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, f); + }); + } } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index abd8885d30..41087c6eaa 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -51,6 +51,7 @@ pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ + language_settings::{self, all_language_settings}, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, TransactionId, @@ -436,7 +437,7 @@ pub enum EditorMode { Full, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum SoftWrap { None, EditorWidth, @@ -471,7 +472,7 @@ pub struct Editor { select_larger_syntax_node_stack: Vec]>>, ime_transaction: Option, active_diagnostics: Option, - soft_wrap_mode_override: Option, + soft_wrap_mode_override: Option, get_field_editor_theme: Option>, override_text_style: Option>, project: Option>, @@ -1247,7 +1248,7 @@ impl Editor { let blink_manager = cx.add_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx)); let soft_wrap_mode_override = - (mode == EditorMode::SingleLine).then(|| settings::SoftWrap::None); + (mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::None); let mut this = Self { handle: cx.weak_handle(), buffer: buffer.clone(), @@ -3116,17 +3117,12 @@ impl Editor { snapshot: &MultiBufferSnapshot, cx: &mut ViewContext, ) -> bool { - let settings = cx.global::(); - - let path = snapshot.file_at(location).map(|file| file.path()); + let path = snapshot.file_at(location).map(|file| file.path().as_ref()); let language_name = snapshot .language_at(location) .map(|language| language.name()); - if !settings.show_copilot_suggestions(language_name.as_deref(), path.map(|p| p.as_ref())) { - return false; - } - - true + let settings = all_language_settings(None, cx); + settings.copilot_enabled(language_name.as_deref(), path) } fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool { @@ -3427,12 +3423,9 @@ impl Editor { { let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row); - let language_name = buffer - .language_at(line_buffer_range.start) - .map(|language| language.name()); let indent_len = match indent_size.kind { IndentKind::Space => { - cx.global::().tab_size(language_name.as_deref()) + buffer.settings_at(line_buffer_range.start, cx).tab_size } IndentKind::Tab => NonZeroU32::new(1).unwrap(), }; @@ -3544,12 +3537,11 @@ impl Editor { } // Otherwise, insert a hard or soft tab. - let settings = cx.global::(); - let language_name = buffer.language_at(cursor, cx).map(|l| l.name()); - let tab_size = if settings.hard_tabs(language_name.as_deref()) { + let settings = buffer.settings_at(cursor, cx); + let tab_size = if settings.hard_tabs { IndentSize::tab() } else { - let tab_size = settings.tab_size(language_name.as_deref()).get(); + let tab_size = settings.tab_size.get(); let char_column = snapshot .text_for_range(Point::new(cursor.row, 0)..cursor) .flat_map(str::chars) @@ -3602,10 +3594,9 @@ impl Editor { delta_for_start_row: u32, cx: &AppContext, ) -> u32 { - let language_name = buffer.language_at(selection.start, cx).map(|l| l.name()); - let settings = cx.global::(); - let tab_size = settings.tab_size(language_name.as_deref()).get(); - let indent_kind = if settings.hard_tabs(language_name.as_deref()) { + let settings = buffer.settings_at(selection.start, cx); + let tab_size = settings.tab_size.get(); + let indent_kind = if settings.hard_tabs { IndentKind::Tab } else { IndentKind::Space @@ -3674,11 +3665,8 @@ impl Editor { let buffer = self.buffer.read(cx); let snapshot = buffer.snapshot(cx); for selection in &selections { - let language_name = buffer.language_at(selection.start, cx).map(|l| l.name()); - let tab_size = cx - .global::() - .tab_size(language_name.as_deref()) - .get(); + let settings = buffer.settings_at(selection.start, cx); + let tab_size = settings.tab_size.get(); let mut rows = selection.spanned_rows(false, &display_map); // Avoid re-outdenting a row that has already been outdented by a @@ -6439,27 +6427,24 @@ impl Editor { } pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap { - let language_name = self - .buffer - .read(cx) - .as_singleton() - .and_then(|singleton_buffer| singleton_buffer.read(cx).language()) - .map(|l| l.name()); - - let settings = cx.global::(); + let settings = self.buffer.read(cx).settings_at(0, cx); let mode = self .soft_wrap_mode_override - .unwrap_or_else(|| settings.soft_wrap(language_name.as_deref())); + .unwrap_or_else(|| settings.soft_wrap); match mode { - settings::SoftWrap::None => SoftWrap::None, - settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth, - settings::SoftWrap::PreferredLineLength => { - SoftWrap::Column(settings.preferred_line_length(language_name.as_deref())) + language_settings::SoftWrap::None => SoftWrap::None, + language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth, + language_settings::SoftWrap::PreferredLineLength => { + SoftWrap::Column(settings.preferred_line_length) } } } - pub fn set_soft_wrap_mode(&mut self, mode: settings::SoftWrap, cx: &mut ViewContext) { + pub fn set_soft_wrap_mode( + &mut self, + mode: language_settings::SoftWrap, + cx: &mut ViewContext, + ) { self.soft_wrap_mode_override = Some(mode); cx.notify(); } @@ -6474,8 +6459,8 @@ impl Editor { self.soft_wrap_mode_override.take(); } else { let soft_wrap = match self.soft_wrap_mode(cx) { - SoftWrap::None => settings::SoftWrap::EditorWidth, - SoftWrap::EditorWidth | SoftWrap::Column(_) => settings::SoftWrap::None, + SoftWrap::None => language_settings::SoftWrap::EditorWidth, + SoftWrap::EditorWidth | SoftWrap::Column(_) => language_settings::SoftWrap::None, }; self.soft_wrap_mode_override = Some(soft_wrap); } @@ -6874,7 +6859,12 @@ impl Editor { .get("vim_mode") == Some(&serde_json::Value::Bool(true)); let telemetry_settings = *settings::get_setting::(None, cx); - let settings = cx.global::(); + let copilot_enabled = all_language_settings(None, cx).copilot_enabled(None, None); + let copilot_enabled_for_language = self + .buffer + .read(cx) + .settings_at(0, cx) + .show_copilot_suggestions; let extension = Path::new(file.file_name(cx)) .extension() @@ -6893,15 +6883,8 @@ impl Editor { file_extension: extension.map(ToString::to_string), vim_mode, operation: name, - copilot_enabled: settings.features.copilot, - copilot_enabled_for_language: settings.show_copilot_suggestions( - self.language_at(0, cx) - .map(|language| language.name()) - .as_deref(), - self.file_at(0, cx) - .map(|file| file.path().clone()) - .as_deref(), - ), + copilot_enabled, + copilot_enabled_for_language, }; telemetry.report_clickhouse_event(event, telemetry_settings) } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 0f749bde48..7ee6a52955 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -12,10 +12,12 @@ use gpui::{ serde_json, TestAppContext, }; use indoc::indoc; -use language::{BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point}; +use language::{ + language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent}, + BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point, +}; use parking_lot::Mutex; use project::FakeFs; -use settings::EditorSettings; use std::{cell::RefCell, future::Future, rc::Rc, time::Instant}; use unindent::Unindent; use util::{ @@ -29,7 +31,8 @@ use workspace::{ #[gpui::test] fn test_edit_events(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let buffer = cx.add_model(|cx| { let mut buffer = language::Buffer::new(0, "123456", cx); buffer.set_group_interval(Duration::from_secs(1)); @@ -156,7 +159,8 @@ fn test_edit_events(cx: &mut TestAppContext) { #[gpui::test] fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let mut now = Instant::now(); let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx)); let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval()); @@ -226,7 +230,8 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) { #[gpui::test] fn test_ime_composition(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let buffer = cx.add_model(|cx| { let mut buffer = language::Buffer::new(0, "abcde", cx); // Ensure automatic grouping doesn't occur. @@ -328,7 +333,7 @@ fn test_ime_composition(cx: &mut TestAppContext) { #[gpui::test] fn test_selection_with_mouse(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx); @@ -395,7 +400,8 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) { #[gpui::test] fn test_canceling_pending_selection(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx); build_editor(buffer, cx) @@ -429,6 +435,8 @@ fn test_canceling_pending_selection(cx: &mut TestAppContext) { #[gpui::test] fn test_clone(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + let (text, selection_ranges) = marked_text_ranges( indoc! {" one @@ -439,7 +447,6 @@ fn test_clone(cx: &mut TestAppContext) { "}, true, ); - cx.update(|cx| cx.set_global(Settings::test(cx))); let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple(&text, cx); @@ -487,7 +494,8 @@ fn test_clone(cx: &mut TestAppContext) { #[gpui::test] async fn test_navigation_history(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + cx.set_global(DragAndDrop::::default()); use workspace::item::Item; @@ -600,7 +608,8 @@ async fn test_navigation_history(cx: &mut TestAppContext) { #[gpui::test] fn test_cancel(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx); build_editor(buffer, cx) @@ -642,7 +651,8 @@ fn test_cancel(cx: &mut TestAppContext) { #[gpui::test] fn test_fold_action(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple( &" @@ -731,7 +741,8 @@ fn test_fold_action(cx: &mut TestAppContext) { #[gpui::test] fn test_move_cursor(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx)); let (_, view) = cx.add_window(|cx| build_editor(buffer.clone(), cx)); @@ -806,7 +817,8 @@ fn test_move_cursor(cx: &mut TestAppContext) { #[gpui::test] fn test_move_cursor_multibyte(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx); build_editor(buffer.clone(), cx) @@ -910,7 +922,8 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) { #[gpui::test] fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx); build_editor(buffer.clone(), cx) @@ -959,7 +972,8 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { #[gpui::test] fn test_beginning_end_of_line(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("abc\n def", cx); build_editor(buffer, cx) @@ -1121,7 +1135,8 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) { #[gpui::test] fn test_prev_next_word_boundary(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx); build_editor(buffer, cx) @@ -1172,7 +1187,8 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) { #[gpui::test] fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx); build_editor(buffer, cx) @@ -1229,6 +1245,7 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { #[gpui::test] async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx); let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache())); @@ -1343,6 +1360,7 @@ async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); let mut cx = EditorTestContext::new(cx); cx.set_state("one «two threeˇ» four"); cx.update_editor(|editor, cx| { @@ -1353,7 +1371,8 @@ async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_delete_to_word_boundary(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("one two three four", cx); build_editor(buffer.clone(), cx) @@ -1388,7 +1407,8 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) { #[gpui::test] fn test_newline(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx); build_editor(buffer.clone(), cx) @@ -1410,7 +1430,8 @@ fn test_newline(cx: &mut TestAppContext) { #[gpui::test] fn test_newline_with_old_selections(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple( " @@ -1491,11 +1512,8 @@ fn test_newline_with_old_selections(cx: &mut TestAppContext) { #[gpui::test] async fn test_newline_above(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx); - cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.editor_overrides.tab_size = Some(NonZeroU32::new(4).unwrap()); - }); + init_test(cx, |settings| { + settings.defaults.tab_size = NonZeroU32::new(4) }); let language = Arc::new( @@ -1506,8 +1524,9 @@ async fn test_newline_above(cx: &mut gpui::TestAppContext) { .with_indents_query(r#"(_ "(" ")" @end) @indent"#) .unwrap(), ); - cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); + let mut cx = EditorTestContext::new(cx); + cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); cx.set_state(indoc! {" const a: ˇA = ( (ˇ @@ -1516,6 +1535,7 @@ async fn test_newline_above(cx: &mut gpui::TestAppContext) { )ˇ ˇ);ˇ "}); + cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx)); cx.assert_editor_state(indoc! {" ˇ @@ -1540,11 +1560,8 @@ async fn test_newline_above(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_newline_below(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx); - cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.editor_overrides.tab_size = Some(NonZeroU32::new(4).unwrap()); - }); + init_test(cx, |settings| { + settings.defaults.tab_size = NonZeroU32::new(4) }); let language = Arc::new( @@ -1555,8 +1572,9 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) { .with_indents_query(r#"(_ "(" ")" @end) @indent"#) .unwrap(), ); - cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); + let mut cx = EditorTestContext::new(cx); + cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); cx.set_state(indoc! {" const a: ˇA = ( (ˇ @@ -1565,6 +1583,7 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) { )ˇ ˇ);ˇ "}); + cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx)); cx.assert_editor_state(indoc! {" const a: A = ( @@ -1589,7 +1608,8 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_insert_with_old_selections(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx); let mut editor = build_editor(buffer.clone(), cx); @@ -1615,12 +1635,11 @@ fn test_insert_with_old_selections(cx: &mut TestAppContext) { #[gpui::test] async fn test_tab(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx); - cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.editor_overrides.tab_size = Some(NonZeroU32::new(3).unwrap()); - }); + init_test(cx, |settings| { + settings.defaults.tab_size = NonZeroU32::new(3) }); + + let mut cx = EditorTestContext::new(cx); cx.set_state(indoc! {" ˇabˇc ˇ🏀ˇ🏀ˇefg @@ -1646,6 +1665,8 @@ async fn test_tab(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); let language = Arc::new( Language::new( @@ -1704,7 +1725,10 @@ async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAp #[gpui::test] async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx); + init_test(cx, |settings| { + settings.defaults.tab_size = NonZeroU32::new(4) + }); + let language = Arc::new( Language::new( LanguageConfig::default(), @@ -1713,14 +1737,9 @@ async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) { .with_indents_query(r#"(_ "{" "}" @end) @indent"#) .unwrap(), ); + + let mut cx = EditorTestContext::new(cx); cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); - - cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.editor_overrides.tab_size = Some(4.try_into().unwrap()); - }); - }); - cx.set_state(indoc! {" fn a() { if b { @@ -1741,6 +1760,10 @@ async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_indent_outdent(cx: &mut gpui::TestAppContext) { + init_test(cx, |settings| { + settings.defaults.tab_size = NonZeroU32::new(4); + }); + let mut cx = EditorTestContext::new(cx); cx.set_state(indoc! {" @@ -1810,13 +1833,12 @@ async fn test_indent_outdent(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx); - cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.editor_overrides.hard_tabs = Some(true); - }); + init_test(cx, |settings| { + settings.defaults.hard_tabs = Some(true); }); + let mut cx = EditorTestContext::new(cx); + // select two ranges on one line cx.set_state(indoc! {" «oneˇ» «twoˇ» @@ -1907,25 +1929,25 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) { - cx.update(|cx| { - cx.set_global( - Settings::test(cx) - .with_language_defaults( - "TOML", - EditorSettings { - tab_size: Some(2.try_into().unwrap()), - ..Default::default() - }, - ) - .with_language_defaults( - "Rust", - EditorSettings { - tab_size: Some(4.try_into().unwrap()), - ..Default::default() - }, - ), - ); + init_test(cx, |settings| { + settings.languages.extend([ + ( + "TOML".into(), + LanguageSettingsContent { + tab_size: NonZeroU32::new(2), + ..Default::default() + }, + ), + ( + "Rust".into(), + LanguageSettingsContent { + tab_size: NonZeroU32::new(4), + ..Default::default() + }, + ), + ]); }); + let toml_language = Arc::new(Language::new( LanguageConfig { name: "TOML".into(), @@ -2020,6 +2042,8 @@ fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) { #[gpui::test] async fn test_backspace(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); // Basic backspace @@ -2067,8 +2091,9 @@ async fn test_backspace(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_delete(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx); + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); cx.set_state(indoc! {" onˇe two three fou«rˇ» five six @@ -2095,7 +2120,8 @@ async fn test_delete(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_delete_line(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); build_editor(buffer, cx) @@ -2119,7 +2145,6 @@ fn test_delete_line(cx: &mut TestAppContext) { ); }); - cx.update(|cx| cx.set_global(Settings::test(cx))); let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); build_editor(buffer, cx) @@ -2139,7 +2164,8 @@ fn test_delete_line(cx: &mut TestAppContext) { #[gpui::test] fn test_duplicate_line(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx); build_editor(buffer, cx) @@ -2191,7 +2217,8 @@ fn test_duplicate_line(cx: &mut TestAppContext) { #[gpui::test] fn test_move_line_up_down(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx); build_editor(buffer, cx) @@ -2289,7 +2316,8 @@ fn test_move_line_up_down(cx: &mut TestAppContext) { #[gpui::test] fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx); build_editor(buffer, cx) @@ -2315,7 +2343,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { #[gpui::test] fn test_transpose(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); _ = cx .add_window(|cx| { @@ -2417,6 +2445,8 @@ fn test_transpose(cx: &mut TestAppContext) { #[gpui::test] async fn test_clipboard(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six "); @@ -2497,6 +2527,8 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_paste_multiline(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); let language = Arc::new(Language::new( LanguageConfig::default(), @@ -2609,7 +2641,8 @@ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_select_all(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx); build_editor(buffer, cx) @@ -2625,7 +2658,8 @@ fn test_select_all(cx: &mut TestAppContext) { #[gpui::test] fn test_select_line(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx); build_editor(buffer, cx) @@ -2671,7 +2705,8 @@ fn test_select_line(cx: &mut TestAppContext) { #[gpui::test] fn test_split_selection_into_lines(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx); build_editor(buffer, cx) @@ -2741,7 +2776,8 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) { #[gpui::test] fn test_add_selection_above_below(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, view) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx); build_editor(buffer, cx) @@ -2935,6 +2971,8 @@ fn test_add_selection_above_below(cx: &mut TestAppContext) { #[gpui::test] async fn test_select_next(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); cx.set_state("abc\nˇabc abc\ndefabc\nabc"); @@ -2959,7 +2997,8 @@ async fn test_select_next(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let language = Arc::new(Language::new( LanguageConfig::default(), Some(tree_sitter_rust::language()), @@ -3100,7 +3139,8 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let language = Arc::new( Language::new( LanguageConfig { @@ -3160,6 +3200,8 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); let language = Arc::new(Language::new( @@ -3329,6 +3371,8 @@ async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); let html_language = Arc::new( @@ -3563,6 +3607,8 @@ async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); let rust_language = Arc::new( @@ -3660,7 +3706,8 @@ async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let language = Arc::new(Language::new( LanguageConfig { brackets: BracketPairConfig { @@ -3814,7 +3861,8 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let language = Arc::new(Language::new( LanguageConfig { brackets: BracketPairConfig { @@ -3919,7 +3967,7 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_snippets(cx: &mut gpui::TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); let (text, insertion_ranges) = marked_text_ranges( indoc! {" @@ -4027,7 +4075,7 @@ async fn test_snippets(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx, |_| {}); let mut language = Language::new( LanguageConfig { @@ -4111,16 +4159,14 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { assert!(!cx.read(|cx| editor.is_dirty(cx))); // Set rust language override and assert overriden tabsize is sent to language server - cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.language_overrides.insert( - "Rust".into(), - EditorSettings { - tab_size: Some(8.try_into().unwrap()), - ..Default::default() - }, - ); - }) + update_test_settings(cx, |settings| { + settings.languages.insert( + "Rust".into(), + LanguageSettingsContent { + tab_size: NonZeroU32::new(8), + ..Default::default() + }, + ); }); let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx)); @@ -4141,7 +4187,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx, |_| {}); let mut language = Language::new( LanguageConfig { @@ -4227,16 +4273,14 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { assert!(!cx.read(|cx| editor.is_dirty(cx))); // Set rust language override and assert overriden tabsize is sent to language server - cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.language_overrides.insert( - "Rust".into(), - EditorSettings { - tab_size: Some(8.try_into().unwrap()), - ..Default::default() - }, - ); - }) + update_test_settings(cx, |settings| { + settings.languages.insert( + "Rust".into(), + LanguageSettingsContent { + tab_size: NonZeroU32::new(8), + ..Default::default() + }, + ); }); let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx)); @@ -4257,7 +4301,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx, |_| {}); let mut language = Language::new( LanguageConfig { @@ -4342,7 +4386,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx, |_| {}); let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { @@ -4399,7 +4443,7 @@ async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx, |_| {}); let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { @@ -4514,6 +4558,8 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) #[gpui::test] async fn test_completion(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions { @@ -4681,7 +4727,8 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_toggle_comment(cx: &mut gpui::TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let language = Arc::new(Language::new( LanguageConfig { line_comment: Some("// ".into()), @@ -4764,8 +4811,7 @@ async fn test_toggle_comment(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) { - let mut cx = EditorTestContext::new(cx); - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); let language = Arc::new(Language::new( LanguageConfig { @@ -4778,6 +4824,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) let registry = Arc::new(LanguageRegistry::test()); registry.add(language.clone()); + let mut cx = EditorTestContext::new(cx); cx.update_buffer(|buffer, cx| { buffer.set_language_registry(registry); buffer.set_language(Some(language), cx); @@ -4897,6 +4944,8 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) #[gpui::test] async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); let html_language = Arc::new( @@ -5021,7 +5070,8 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx)); let multibuffer = cx.add_model(|cx| { let mut multibuffer = MultiBuffer::new(0); @@ -5067,7 +5117,8 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { #[gpui::test] fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let markers = vec![('[', ']').into(), ('(', ')').into()]; let (initial_text, mut excerpt_ranges) = marked_text_ranges_by( indoc! {" @@ -5140,7 +5191,8 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) { #[gpui::test] fn test_refresh_selections(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx)); let mut excerpt1_id = None; let multibuffer = cx.add_model(|cx| { @@ -5224,7 +5276,8 @@ fn test_refresh_selections(cx: &mut TestAppContext) { #[gpui::test] fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx)); let mut excerpt1_id = None; let multibuffer = cx.add_model(|cx| { @@ -5282,7 +5335,8 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) { #[gpui::test] async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let language = Arc::new( Language::new( LanguageConfig { @@ -5355,7 +5409,8 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) { #[gpui::test] fn test_highlighted_ranges(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx); build_editor(buffer.clone(), cx) @@ -5437,7 +5492,8 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) { #[gpui::test] async fn test_following(cx: &mut gpui::TestAppContext) { - Settings::test_async(cx); + init_test(cx, |_| {}); + let fs = FakeFs::new(cx.background()); let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; @@ -5576,7 +5632,8 @@ async fn test_following(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) { - Settings::test_async(cx); + init_test(cx, |_| {}); + let fs = FakeFs::new(cx.background()); let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); @@ -5805,6 +5862,8 @@ fn test_combine_syntax_and_fuzzy_match_highlights() { #[gpui::test] async fn go_to_hunk(deterministic: Arc, cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx); let diff_base = r#" @@ -5924,6 +5983,8 @@ fn test_split_words() { #[gpui::test] async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await; let mut assert = |before, after| { let _state_context = cx.set_state(before); @@ -5972,6 +6033,8 @@ async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) { #[gpui::test(iterations = 10)] async fn test_copilot(deterministic: Arc, cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let (copilot, copilot_lsp) = Copilot::fake(cx); cx.update(|cx| cx.set_global(copilot)); let mut cx = EditorLspTestContext::new_rust( @@ -6223,6 +6286,8 @@ async fn test_copilot_completion_invalidation( deterministic: Arc, cx: &mut gpui::TestAppContext, ) { + init_test(cx, |_| {}); + let (copilot, copilot_lsp) = Copilot::fake(cx); cx.update(|cx| cx.set_global(copilot)); let mut cx = EditorLspTestContext::new_rust( @@ -6288,11 +6353,10 @@ async fn test_copilot_multibuffer( deterministic: Arc, cx: &mut gpui::TestAppContext, ) { + init_test(cx, |_| {}); + let (copilot, copilot_lsp) = Copilot::fake(cx); - cx.update(|cx| { - cx.set_global(Settings::test(cx)); - cx.set_global(copilot) - }); + cx.update(|cx| cx.set_global(copilot)); let buffer_1 = cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx)); let buffer_2 = cx.add_model(|cx| Buffer::new(0, "c = 3\nd = 4\n", cx)); @@ -6392,14 +6456,16 @@ async fn test_copilot_disabled_globs( deterministic: Arc, cx: &mut gpui::TestAppContext, ) { - let (copilot, copilot_lsp) = Copilot::fake(cx); - cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.copilot.disabled_globs = vec![glob::Pattern::new(".env*").unwrap()]; - cx.set_global(settings); - cx.set_global(copilot) + init_test(cx, |settings| { + settings + .copilot + .get_or_insert(Default::default()) + .disabled_globs = Some(vec![".env*".to_string()]); }); + let (copilot, copilot_lsp) = Copilot::fake(cx); + cx.update(|cx| cx.set_global(copilot)); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/test", @@ -6596,3 +6662,27 @@ fn handle_copilot_completion_request( } }); } + +pub(crate) fn update_test_settings( + cx: &mut TestAppContext, + f: impl Fn(&mut AllLanguageSettingsContent), +) { + cx.update(|cx| { + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, f); + }); + }); +} + +pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) { + cx.foreground().forbid_parking(); + + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + cx.set_global(Settings::test(cx)); + language::init(cx); + crate::init(cx); + }); + + update_test_settings(cx, f); +} diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index c1710b7337..8194d51b25 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -35,9 +35,12 @@ use gpui::{ }; use itertools::Itertools; use json::json; -use language::{Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, Selection}; +use language::{ + language_settings::ShowWhitespaceSetting, Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, + Selection, +}; use project::ProjectPath; -use settings::{GitGutter, Settings, ShowWhitespaces}; +use settings::{GitGutter, Settings}; use smallvec::SmallVec; use std::{ borrow::Cow, @@ -708,6 +711,7 @@ impl EditorElement { let scroll_left = scroll_position.x() * max_glyph_width; let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.); let line_end_overshoot = 0.15 * layout.position_map.line_height; + let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces; scene.push_layer(Some(bounds)); @@ -882,9 +886,10 @@ impl EditorElement { content_origin, scroll_left, visible_text_bounds, - cx, + whitespace_setting, &invisible_display_ranges, visible_bounds, + cx, ) } } @@ -1738,9 +1743,10 @@ impl LineWithInvisibles { content_origin: Vector2F, scroll_left: f32, visible_text_bounds: RectF, - cx: &mut ViewContext, + whitespace_setting: ShowWhitespaceSetting, selection_ranges: &[Range], visible_bounds: RectF, + cx: &mut ViewContext, ) { let line_height = layout.position_map.line_height; let line_y = row as f32 * line_height - scroll_top; @@ -1754,7 +1760,6 @@ impl LineWithInvisibles { ); self.draw_invisibles( - cx, &selection_ranges, layout, content_origin, @@ -1764,12 +1769,13 @@ impl LineWithInvisibles { scene, visible_bounds, line_height, + whitespace_setting, + cx, ); } fn draw_invisibles( &self, - cx: &mut ViewContext, selection_ranges: &[Range], layout: &LayoutState, content_origin: Vector2F, @@ -1779,17 +1785,13 @@ impl LineWithInvisibles { scene: &mut SceneBuilder, visible_bounds: RectF, line_height: f32, + whitespace_setting: ShowWhitespaceSetting, + cx: &mut ViewContext, ) { - let settings = cx.global::(); - let allowed_invisibles_regions = match settings - .editor_overrides - .show_whitespaces - .or(settings.editor_defaults.show_whitespaces) - .unwrap_or_default() - { - ShowWhitespaces::None => return, - ShowWhitespaces::Selection => Some(selection_ranges), - ShowWhitespaces::All => None, + let allowed_invisibles_regions = match whitespace_setting { + ShowWhitespaceSetting::None => return, + ShowWhitespaceSetting::Selection => Some(selection_ranges), + ShowWhitespaceSetting::All => None, }; for invisible in &self.invisibles { @@ -2773,17 +2775,19 @@ mod tests { use super::*; use crate::{ display_map::{BlockDisposition, BlockProperties}, + editor_tests::{init_test, update_test_settings}, Editor, MultiBuffer, }; use gpui::TestAppContext; + use language::language_settings; use log::info; - use settings::Settings; use std::{num::NonZeroU32, sync::Arc}; use util::test::sample_text; #[gpui::test] fn test_layout_line_numbers(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx); Editor::new(EditorMode::Full, buffer, None, None, cx) @@ -2801,7 +2805,8 @@ mod tests { #[gpui::test] fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx, |_| {}); + let (_, editor) = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("", cx); Editor::new(EditorMode::Full, buffer, None, None, cx) @@ -2861,26 +2866,27 @@ mod tests { #[gpui::test] fn test_all_invisibles_drawing(cx: &mut TestAppContext) { - let tab_size = 4; + const TAB_SIZE: u32 = 4; + let input_text = "\t \t|\t| a b"; let expected_invisibles = vec![ Invisible::Tab { line_start_offset: 0, }, Invisible::Whitespace { - line_offset: tab_size as usize, + line_offset: TAB_SIZE as usize, }, Invisible::Tab { - line_start_offset: tab_size as usize + 1, + line_start_offset: TAB_SIZE as usize + 1, }, Invisible::Tab { - line_start_offset: tab_size as usize * 2 + 1, + line_start_offset: TAB_SIZE as usize * 2 + 1, }, Invisible::Whitespace { - line_offset: tab_size as usize * 3 + 1, + line_offset: TAB_SIZE as usize * 3 + 1, }, Invisible::Whitespace { - line_offset: tab_size as usize * 3 + 3, + line_offset: TAB_SIZE as usize * 3 + 3, }, ]; assert_eq!( @@ -2892,12 +2898,11 @@ mod tests { "Hardcoded expected invisibles differ from the actual ones in '{input_text}'" ); - cx.update(|cx| { - let mut test_settings = Settings::test(cx); - test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All); - test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(tab_size).unwrap()); - cx.set_global(test_settings); + init_test(cx, |s| { + s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All); + s.defaults.tab_size = NonZeroU32::new(TAB_SIZE); }); + let actual_invisibles = collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, 500.0); @@ -2906,11 +2911,9 @@ mod tests { #[gpui::test] fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) { - cx.update(|cx| { - let mut test_settings = Settings::test(cx); - test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All); - test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(4).unwrap()); - cx.set_global(test_settings); + init_test(cx, |s| { + s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All); + s.defaults.tab_size = NonZeroU32::new(4); }); for editor_mode_without_invisibles in [ @@ -2961,19 +2964,18 @@ mod tests { ); info!("Expected invisibles: {expected_invisibles:?}"); + init_test(cx, |_| {}); + // Put the same string with repeating whitespace pattern into editors of various size, // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point. let resize_step = 10.0; let mut editor_width = 200.0; while editor_width <= 1000.0 { - cx.update(|cx| { - let mut test_settings = Settings::test(cx); - test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(tab_size).unwrap()); - test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All); - test_settings.editor_defaults.preferred_line_length = Some(editor_width as u32); - test_settings.editor_defaults.soft_wrap = - Some(settings::SoftWrap::PreferredLineLength); - cx.set_global(test_settings); + update_test_settings(cx, |s| { + s.defaults.tab_size = NonZeroU32::new(tab_size); + s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All); + s.defaults.preferred_line_length = Some(editor_width as u32); + s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength); }); let actual_invisibles = @@ -3021,7 +3023,7 @@ mod tests { let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx))); let (_, layout_state) = editor.update(cx, |editor, cx| { - editor.set_soft_wrap_mode(settings::SoftWrap::EditorWidth, cx); + editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx); editor.set_wrap_width(Some(editor_width), cx); let mut new_parents = Default::default(); diff --git a/crates/editor/src/highlight_matching_bracket.rs b/crates/editor/src/highlight_matching_bracket.rs index ce3864f56a..a0baf6882f 100644 --- a/crates/editor/src/highlight_matching_bracket.rs +++ b/crates/editor/src/highlight_matching_bracket.rs @@ -33,12 +33,14 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon #[cfg(test)] mod tests { use super::*; - use crate::test::editor_lsp_test_context::EditorLspTestContext; + use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext}; use indoc::indoc; use language::{BracketPair, BracketPairConfig, Language, LanguageConfig}; #[gpui::test] async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new( Language::new( LanguageConfig { diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 438c662ed1..c40cb0fbe3 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -694,7 +694,7 @@ impl DiagnosticPopover { #[cfg(test)] mod tests { use super::*; - use crate::test::editor_lsp_test_context::EditorLspTestContext; + use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext}; use gpui::fonts::Weight; use indoc::indoc; use language::{Diagnostic, DiagnosticSet}; @@ -706,6 +706,8 @@ mod tests { #[gpui::test] async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), @@ -773,6 +775,8 @@ mod tests { #[gpui::test] async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), @@ -816,6 +820,8 @@ mod tests { #[gpui::test] async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), @@ -882,7 +888,8 @@ mod tests { #[gpui::test] fn test_render_blocks(cx: &mut gpui::TestAppContext) { - Settings::test_async(cx); + init_test(cx, |_| {}); + cx.add_window(|cx| { let editor = Editor::single_line(None, cx); let style = editor.style(cx); diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index b2105c1c81..04cf4611f1 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -1,10 +1,9 @@ -use std::ops::Range; - use crate::{Anchor, DisplayPoint, Editor, EditorSnapshot, SelectPhase}; use gpui::{Task, ViewContext}; use language::{Bias, ToOffset}; use project::LocationLink; use settings::Settings; +use std::ops::Range; use util::TryFutureExt; #[derive(Debug, Default)] @@ -297,6 +296,8 @@ fn go_to_fetched_definition_of_kind( #[cfg(test)] mod tests { + use super::*; + use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext}; use futures::StreamExt; use gpui::{ platform::{self, Modifiers, ModifiersChangedEvent}, @@ -305,12 +306,10 @@ mod tests { use indoc::indoc; use lsp::request::{GotoDefinition, GotoTypeDefinition}; - use crate::test::editor_lsp_test_context::EditorLspTestContext; - - use super::*; - #[gpui::test] async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), @@ -417,6 +416,8 @@ mod tests { #[gpui::test] async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index b892fffbca..8dfdcdff53 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -57,13 +57,14 @@ pub fn deploy_context_menu( #[cfg(test)] mod tests { - use crate::test::editor_lsp_test_context::EditorLspTestContext; - use super::*; + use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext}; use indoc::indoc; #[gpui::test] async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust( lsp::ServerCapabilities { hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 284f0c94bc..311616cfe0 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -367,13 +367,15 @@ pub fn split_display_range_by_lines( #[cfg(test)] mod tests { + use settings::{Settings, SettingsStore}; + use super::*; use crate::{test::marked_display_snapshot, Buffer, DisplayMap, ExcerptRange, MultiBuffer}; - use settings::Settings; #[gpui::test] fn test_previous_word_start(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + fn assert(marked_text: &str, cx: &mut gpui::AppContext) { let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); assert_eq!( @@ -400,7 +402,8 @@ mod tests { #[gpui::test] fn test_previous_subword_start(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + fn assert(marked_text: &str, cx: &mut gpui::AppContext) { let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); assert_eq!( @@ -434,7 +437,8 @@ mod tests { #[gpui::test] fn test_find_preceding_boundary(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + fn assert( marked_text: &str, cx: &mut gpui::AppContext, @@ -466,7 +470,8 @@ mod tests { #[gpui::test] fn test_next_word_end(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + fn assert(marked_text: &str, cx: &mut gpui::AppContext) { let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); assert_eq!( @@ -490,7 +495,8 @@ mod tests { #[gpui::test] fn test_next_subword_end(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + fn assert(marked_text: &str, cx: &mut gpui::AppContext) { let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); assert_eq!( @@ -523,7 +529,8 @@ mod tests { #[gpui::test] fn test_find_boundary(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + fn assert( marked_text: &str, cx: &mut gpui::AppContext, @@ -555,7 +562,8 @@ mod tests { #[gpui::test] fn test_surrounding_word(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + fn assert(marked_text: &str, cx: &mut gpui::AppContext) { let (snapshot, display_points) = marked_display_snapshot(marked_text, cx); assert_eq!( @@ -576,7 +584,8 @@ mod tests { #[gpui::test] fn test_move_up_and_down_with_excerpts(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); + let family_id = cx .font_cache() .load_family(&["Helvetica"], &Default::default()) @@ -691,4 +700,11 @@ mod tests { (DisplayPoint::new(7, 2), SelectionGoal::Column(2)), ); } + + fn init_test(cx: &mut gpui::AppContext) { + cx.set_global(SettingsStore::test(cx)); + cx.set_global(Settings::test(cx)); + language::init(cx); + crate::init(cx); + } } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index a2160b47e5..1e07bba065 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -9,7 +9,9 @@ use git::diff::DiffHunk; use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; pub use language::Completion; use language::{ - char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, + char_kind, + language_settings::{language_settings, LanguageSettings}, + AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, @@ -1372,6 +1374,15 @@ impl MultiBuffer { .and_then(|(buffer, offset)| buffer.read(cx).language_at(offset)) } + pub fn settings_at<'a, T: ToOffset>( + &self, + point: T, + cx: &'a AppContext, + ) -> &'a LanguageSettings { + let language = self.language_at(point, cx); + language_settings(None, language.map(|l| l.name()).as_deref(), cx) + } + pub fn for_each_buffer(&self, mut f: impl FnMut(&ModelHandle)) { self.buffers .borrow() @@ -2764,6 +2775,16 @@ impl MultiBufferSnapshot { .and_then(|(buffer, offset)| buffer.language_at(offset)) } + pub fn settings_at<'a, T: ToOffset>( + &'a self, + point: T, + cx: &'a AppContext, + ) -> &'a LanguageSettings { + self.point_to_buffer_offset(point) + .map(|(buffer, offset)| buffer.settings_at(offset, cx)) + .unwrap_or_else(|| language_settings(None, None, cx)) + } + pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option { self.point_to_buffer_offset(point) .and_then(|(buffer, offset)| buffer.language_scope_at(offset)) diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index e268e2a0ce..e9aaa3df1b 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -37,6 +37,7 @@ impl<'a> EditorLspTestContext<'a> { let app_state = cx.update(AppState::test); cx.update(|cx| { + language::init(cx); crate::init(cx); pane::init(cx); }); diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index 269a2f4f3c..ced99a3f23 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -1,19 +1,16 @@ +use crate::{ + display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer, +}; +use futures::Future; +use gpui::{ + keymap_matcher::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle, +}; +use indoc::indoc; +use language::{Buffer, BufferSnapshot}; use std::{ any::TypeId, ops::{Deref, DerefMut, Range}, }; - -use futures::Future; -use indoc::indoc; - -use crate::{ - display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer, -}; -use gpui::{ - keymap_matcher::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle, -}; -use language::{Buffer, BufferSnapshot}; -use settings::Settings; use util::{ assert_set_eq, test::{generate_marked_text, marked_text_ranges}, @@ -30,15 +27,10 @@ pub struct EditorTestContext<'a> { impl<'a> EditorTestContext<'a> { pub fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> { let (window_id, editor) = cx.update(|cx| { - cx.set_global(Settings::test(cx)); - crate::init(cx); - - let (window_id, editor) = cx.add_window(Default::default(), |cx| { + cx.add_window(Default::default(), |cx| { cx.focus_self(); build_editor(MultiBuffer::build_simple("", cx), cx) - }); - - (window_id, editor) + }) }); Self { diff --git a/crates/file_finder/Cargo.toml b/crates/file_finder/Cargo.toml index 30a4650ad7..024054c005 100644 --- a/crates/file_finder/Cargo.toml +++ b/crates/file_finder/Cargo.toml @@ -23,7 +23,9 @@ postage.workspace = true [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } -serde_json.workspace = true +language = { path = "../language", features = ["test-support"] } workspace = { path = "../workspace", features = ["test-support"] } + +serde_json.workspace = true ctor.workspace = true env_logger.workspace = true diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index f00430feb7..7d5aff79d0 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -270,6 +270,7 @@ impl PickerDelegate for FileFinderDelegate { mod tests { use super::*; use editor::Editor; + use gpui::TestAppContext; use menu::{Confirm, SelectNext}; use serde_json::json; use workspace::{AppState, Workspace}; @@ -283,12 +284,7 @@ mod tests { #[gpui::test] async fn test_matching_paths(cx: &mut gpui::TestAppContext) { - let app_state = cx.update(|cx| { - super::init(cx); - editor::init(cx); - AppState::test(cx) - }); - + let app_state = init_test(cx); app_state .fs .as_fake() @@ -339,7 +335,7 @@ mod tests { #[gpui::test] async fn test_matching_cancellation(cx: &mut gpui::TestAppContext) { - let app_state = cx.update(AppState::test); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -408,7 +404,7 @@ mod tests { #[gpui::test] async fn test_ignored_files(cx: &mut gpui::TestAppContext) { - let app_state = cx.update(AppState::test); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -462,7 +458,7 @@ mod tests { #[gpui::test] async fn test_single_file_worktrees(cx: &mut gpui::TestAppContext) { - let app_state = cx.update(AppState::test); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -516,9 +512,7 @@ mod tests { #[gpui::test] async fn test_multiple_matches_with_same_relative_path(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - - let app_state = cx.update(AppState::test); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -570,9 +564,7 @@ mod tests { #[gpui::test] async fn test_path_distance_ordering(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - - let app_state = cx.update(AppState::test); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -622,7 +614,7 @@ mod tests { #[gpui::test] async fn test_search_worktree_without_files(cx: &mut gpui::TestAppContext) { - let app_state = cx.update(AppState::test); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -658,4 +650,15 @@ mod tests { assert_eq!(finder.delegate().matches.len(), 0); }); } + + fn init_test(cx: &mut TestAppContext) -> Arc { + cx.foreground().forbid_parking(); + cx.update(|cx| { + let state = AppState::test(cx); + language::init(cx); + super::init(cx); + editor::init(cx); + state + }) + } } diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 7baffab2d9..028656a027 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -477,6 +477,14 @@ impl Deterministic { state.rng = StdRng::seed_from_u64(state.seed); } + pub fn allow_parking(&self) { + use rand::prelude::*; + + let mut state = self.state.lock(); + state.forbid_parking = false; + state.rng = StdRng::seed_from_u64(state.seed); + } + pub async fn simulate_random_delay(&self) { use rand::prelude::*; use smol::future::yield_now; @@ -698,6 +706,14 @@ impl Foreground { } } + #[cfg(any(test, feature = "test-support"))] + pub fn allow_parking(&self) { + match self { + Self::Deterministic { executor, .. } => executor.allow_parking(), + _ => panic!("this method can only be called on a deterministic executor"), + } + } + #[cfg(any(test, feature = "test-support"))] pub fn advance_clock(&self, duration: Duration) { match self { diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 938cd82922..34af4ed0e4 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use chrono::{Datelike, Local, NaiveTime, Timelike}; use editor::{scroll::autoscroll::Autoscroll, Editor}; use gpui::{actions, AppContext}; @@ -40,21 +41,8 @@ impl settings::Setting for JournalSettings { type FileContent = Self; - fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Self { - Self { - path: Some( - user_values - .first() - .and_then(|s| s.path.clone()) - .unwrap_or(default_value.path.clone().unwrap()), - ), - hour_format: Some( - user_values - .first() - .and_then(|s| s.hour_format.clone()) - .unwrap_or(default_value.hour_format.clone().unwrap()), - ), - } + fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Result { + Self::load_via_json_merge(default_value, user_values) } } diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 6e03a07bc3..5a7644d98e 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -36,16 +36,19 @@ sum_tree = { path = "../sum_tree" } text = { path = "../text" } theme = { path = "../theme" } util = { path = "../util" } + anyhow.workspace = true async-broadcast = "0.4" async-trait.workspace = true futures.workspace = true +glob.workspace = true lazy_static.workspace = true log.workspace = true parking_lot.workspace = true postage.workspace = true rand = { workspace = true, optional = true } regex.workspace = true +schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 43766192eb..fd1430b1e4 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -5,6 +5,7 @@ pub use crate::{ }; use crate::{ diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, + language_settings::{language_settings, LanguageSettings}, outline::OutlineItem, syntax_map::{ SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint, @@ -18,7 +19,6 @@ use futures::FutureExt as _; use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, Task}; use lsp::LanguageServerId; use parking_lot::Mutex; -use settings::Settings; use similar::{ChangeTag, TextDiff}; use smallvec::SmallVec; use smol::future::yield_now; @@ -1827,11 +1827,11 @@ impl BufferSnapshot { pub fn language_indent_size_at(&self, position: T, cx: &AppContext) -> IndentSize { let language_name = self.language_at(position).map(|language| language.name()); - let settings = cx.global::(); - if settings.hard_tabs(language_name.as_deref()) { + let settings = language_settings(None, language_name.as_deref(), cx); + if settings.hard_tabs { IndentSize::tab() } else { - IndentSize::spaces(settings.tab_size(language_name.as_deref()).get()) + IndentSize::spaces(settings.tab_size.get()) } } @@ -2146,6 +2146,15 @@ impl BufferSnapshot { .or(self.language.as_ref()) } + pub fn settings_at<'a, D: ToOffset>( + &self, + position: D, + cx: &'a AppContext, + ) -> &'a LanguageSettings { + let language = self.language_at(position); + language_settings(None, language.map(|l| l.name()).as_deref(), cx) + } + pub fn language_scope_at(&self, position: D) -> Option { let offset = position.to_offset(self); diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index eeac1a4818..be573aa895 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1,3 +1,7 @@ +use crate::language_settings::{ + AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, +}; + use super::*; use clock::ReplicaId; use collections::BTreeMap; @@ -7,7 +11,7 @@ use indoc::indoc; use proto::deserialize_operation; use rand::prelude::*; use regex::RegexBuilder; -use settings::Settings; +use settings::SettingsStore; use std::{ cell::RefCell, env, @@ -36,7 +40,8 @@ fn init_logger() { #[gpui::test] fn test_line_endings(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let mut buffer = Buffer::new(0, "one\r\ntwo\rthree", cx).with_language(Arc::new(rust_lang()), cx); @@ -862,8 +867,7 @@ fn test_range_for_syntax_ancestor(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_with_soft_tabs(cx: &mut AppContext) { - let settings = Settings::test(cx); - cx.set_global(settings); + init_settings(cx, |_| {}); cx.add_model(|cx| { let text = "fn a() {}"; @@ -903,9 +907,9 @@ fn test_autoindent_with_soft_tabs(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_with_hard_tabs(cx: &mut AppContext) { - let mut settings = Settings::test(cx); - settings.editor_overrides.hard_tabs = Some(true); - cx.set_global(settings); + init_settings(cx, |settings| { + settings.defaults.hard_tabs = Some(true); + }); cx.add_model(|cx| { let text = "fn a() {}"; @@ -945,8 +949,7 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) { - let settings = Settings::test(cx); - cx.set_global(settings); + init_settings(cx, |_| {}); cx.add_model(|cx| { let mut buffer = Buffer::new( @@ -1082,8 +1085,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC #[gpui::test] fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) { - let settings = Settings::test(cx); - cx.set_global(settings); + init_settings(cx, |_| {}); cx.add_model(|cx| { let mut buffer = Buffer::new( @@ -1145,7 +1147,8 @@ fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut Ap #[gpui::test] fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let mut buffer = Buffer::new( 0, @@ -1201,7 +1204,8 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let text = "a\nb"; let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx); @@ -1217,7 +1221,8 @@ fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_multi_line_insertion(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let text = " const a: usize = 1; @@ -1257,7 +1262,8 @@ fn test_autoindent_multi_line_insertion(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_block_mode(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let text = r#" fn a() { @@ -1339,7 +1345,8 @@ fn test_autoindent_block_mode(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let text = r#" fn a() { @@ -1417,7 +1424,8 @@ fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContex #[gpui::test] fn test_autoindent_language_without_indents_query(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let text = " * one @@ -1460,25 +1468,23 @@ fn test_autoindent_language_without_indents_query(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_with_injected_languages(cx: &mut AppContext) { - cx.set_global({ - let mut settings = Settings::test(cx); - settings.language_overrides.extend([ + init_settings(cx, |settings| { + settings.languages.extend([ ( "HTML".into(), - settings::EditorSettings { + LanguageSettingsContent { tab_size: Some(2.try_into().unwrap()), ..Default::default() }, ), ( "JavaScript".into(), - settings::EditorSettings { + LanguageSettingsContent { tab_size: Some(8.try_into().unwrap()), ..Default::default() }, ), - ]); - settings + ]) }); let html_language = Arc::new( @@ -1574,9 +1580,10 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) { #[gpui::test] fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) { - let mut settings = Settings::test(cx); - settings.editor_defaults.tab_size = Some(2.try_into().unwrap()); - cx.set_global(settings); + init_settings(cx, |settings| { + settings.defaults.tab_size = Some(2.try_into().unwrap()); + }); + cx.add_model(|cx| { let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(ruby_lang()), cx); @@ -1617,7 +1624,8 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) { #[gpui::test] fn test_language_config_at(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + init_settings(cx, |_| {}); + cx.add_model(|cx| { let language = Language::new( LanguageConfig { @@ -2199,7 +2207,6 @@ fn assert_bracket_pairs( language: Language, cx: &mut AppContext, ) { - cx.set_global(Settings::test(cx)); let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false); let buffer = cx.add_model(|cx| { Buffer::new(0, expected_text.clone(), cx).with_language(Arc::new(language), cx) @@ -2222,3 +2229,11 @@ fn assert_bracket_pairs( bracket_pairs ); } + +fn init_settings(cx: &mut AppContext, f: fn(&mut AllLanguageSettingsContent)) { + cx.set_global(SettingsStore::test(cx)); + crate::init(cx); + cx.update_global::(|settings, cx| { + settings.update_user_settings::(cx, f); + }); +} diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 85c9089952..87e4880b99 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1,6 +1,7 @@ mod buffer; mod diagnostic_set; mod highlight_map; +pub mod language_settings; mod outline; pub mod proto; mod syntax_map; @@ -58,6 +59,10 @@ pub use lsp::LanguageServerId; pub use outline::{Outline, OutlineItem}; pub use tree_sitter::{Parser, Tree}; +pub fn init(cx: &mut AppContext) { + language_settings::init(cx); +} + thread_local! { static PARSER: RefCell = RefCell::new(Parser::new()); } diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs new file mode 100644 index 0000000000..80cc81a4c3 --- /dev/null +++ b/crates/language/src/language_settings.rs @@ -0,0 +1,285 @@ +use anyhow::Result; +use collections::HashMap; +use gpui::AppContext; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::{num::NonZeroU32, path::Path, sync::Arc}; + +pub fn init(cx: &mut AppContext) { + settings::register_setting::(cx); +} + +pub fn language_settings<'a>( + path: Option<&Path>, + language: Option<&str>, + cx: &'a AppContext, +) -> &'a LanguageSettings { + settings::get_setting::(path, cx).language(language) +} + +pub fn all_language_settings<'a>( + path: Option<&Path>, + cx: &'a AppContext, +) -> &'a AllLanguageSettings { + settings::get_setting::(path, cx) +} + +#[derive(Debug, Clone)] +pub struct AllLanguageSettings { + pub copilot: CopilotSettings, + defaults: LanguageSettings, + languages: HashMap, LanguageSettings>, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct LanguageSettings { + pub tab_size: NonZeroU32, + pub hard_tabs: bool, + pub soft_wrap: SoftWrap, + pub preferred_line_length: u32, + pub format_on_save: FormatOnSave, + pub remove_trailing_whitespace_on_save: bool, + pub ensure_final_newline_on_save: bool, + pub formatter: Formatter, + pub enable_language_server: bool, + pub show_copilot_suggestions: bool, + pub show_whitespaces: ShowWhitespaceSetting, +} + +#[derive(Clone, Debug, Default)] +pub struct CopilotSettings { + pub feature_enabled: bool, + pub disabled_globs: Vec, +} + +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +pub struct AllLanguageSettingsContent { + #[serde(default)] + pub features: Option, + #[serde(default)] + pub copilot: Option, + #[serde(flatten)] + pub defaults: LanguageSettingsContent, + #[serde(default, alias = "language_overrides")] + pub languages: HashMap, LanguageSettingsContent>, +} + +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +pub struct LanguageSettingsContent { + #[serde(default)] + pub tab_size: Option, + #[serde(default)] + pub hard_tabs: Option, + #[serde(default)] + pub soft_wrap: Option, + #[serde(default)] + pub preferred_line_length: Option, + #[serde(default)] + pub format_on_save: Option, + #[serde(default)] + pub remove_trailing_whitespace_on_save: Option, + #[serde(default)] + pub ensure_final_newline_on_save: Option, + #[serde(default)] + pub formatter: Option, + #[serde(default)] + pub enable_language_server: Option, + #[serde(default)] + pub show_copilot_suggestions: Option, + #[serde(default)] + pub show_whitespaces: Option, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct CopilotSettingsContent { + #[serde(default)] + pub disabled_globs: Option>, +} + +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct FeaturesContent { + pub copilot: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum SoftWrap { + None, + EditorWidth, + PreferredLineLength, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum FormatOnSave { + On, + Off, + LanguageServer, + External { + command: Arc, + arguments: Arc<[String]>, + }, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ShowWhitespaceSetting { + Selection, + None, + All, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Formatter { + LanguageServer, + External { + command: Arc, + arguments: Arc<[String]>, + }, +} + +impl AllLanguageSettings { + pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings { + if let Some(name) = language_name { + if let Some(overrides) = self.languages.get(name) { + return overrides; + } + } + &self.defaults + } + + pub fn copilot_enabled_for_path(&self, path: &Path) -> bool { + !self + .copilot + .disabled_globs + .iter() + .any(|glob| glob.matches_path(path)) + } + + pub fn copilot_enabled(&self, language_name: Option<&str>, path: Option<&Path>) -> bool { + if !self.copilot.feature_enabled { + return false; + } + + if let Some(path) = path { + if !self.copilot_enabled_for_path(path) { + return false; + } + } + + self.language(language_name).show_copilot_suggestions + } +} + +impl settings::Setting for AllLanguageSettings { + const KEY: Option<&'static str> = None; + + type FileContent = AllLanguageSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_settings: &[&Self::FileContent], + _: &AppContext, + ) -> Result { + // A default is provided for all settings. + let mut defaults: LanguageSettings = + serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?; + + let mut languages = HashMap::default(); + for (language_name, settings) in &default_value.languages { + let mut language_settings = defaults.clone(); + merge_settings(&mut language_settings, &settings); + languages.insert(language_name.clone(), language_settings); + } + + let mut copilot_enabled = default_value + .features + .as_ref() + .and_then(|f| f.copilot) + .ok_or_else(Self::missing_default)?; + let mut copilot_globs = default_value + .copilot + .as_ref() + .and_then(|c| c.disabled_globs.as_ref()) + .ok_or_else(Self::missing_default)?; + + for user_settings in user_settings { + if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) { + copilot_enabled = copilot; + } + if let Some(globs) = user_settings + .copilot + .as_ref() + .and_then(|f| f.disabled_globs.as_ref()) + { + copilot_globs = globs; + } + + // A user's global settings override the default global settings and + // all default language-specific settings. + merge_settings(&mut defaults, &user_settings.defaults); + for language_settings in languages.values_mut() { + merge_settings(language_settings, &user_settings.defaults); + } + + // A user's language-specific settings override default language-specific settings. + for (language_name, user_language_settings) in &user_settings.languages { + merge_settings( + languages + .entry(language_name.clone()) + .or_insert_with(|| defaults.clone()), + &user_language_settings, + ); + } + } + + Ok(Self { + copilot: CopilotSettings { + feature_enabled: copilot_enabled, + disabled_globs: copilot_globs + .iter() + .filter_map(|pattern| glob::Pattern::new(pattern).ok()) + .collect(), + }, + defaults, + languages, + }) + } +} + +fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) { + merge(&mut settings.tab_size, src.tab_size); + merge(&mut settings.hard_tabs, src.hard_tabs); + merge(&mut settings.soft_wrap, src.soft_wrap); + merge( + &mut settings.preferred_line_length, + src.preferred_line_length, + ); + merge(&mut settings.formatter, src.formatter.clone()); + merge(&mut settings.format_on_save, src.format_on_save.clone()); + merge( + &mut settings.remove_trailing_whitespace_on_save, + src.remove_trailing_whitespace_on_save, + ); + merge( + &mut settings.ensure_final_newline_on_save, + src.ensure_final_newline_on_save, + ); + merge( + &mut settings.enable_language_server, + src.enable_language_server, + ); + merge( + &mut settings.show_copilot_suggestions, + src.show_copilot_suggestions, + ); + merge(&mut settings.show_whitespaces, src.show_whitespaces); + + fn merge(target: &mut T, value: Option) { + if let Some(value) = value { + *target = value; + } + } +} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 94872708c4..5d0b0a8884 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -23,6 +23,7 @@ use gpui::{ ModelHandle, Task, WeakModelHandle, }; use language::{ + language_settings::{all_language_settings, language_settings, FormatOnSave, Formatter}, point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, @@ -44,7 +45,7 @@ use postage::watch; use rand::prelude::*; use search::SearchQuery; use serde::Serialize; -use settings::{FormatOnSave, Formatter, Settings}; +use settings::{Settings, SettingsStore}; use sha2::{Digest, Sha256}; use similar::{ChangeTag, TextDiff}; use std::{ @@ -64,9 +65,7 @@ use std::{ }, time::{Duration, Instant, SystemTime}, }; - use terminals::Terminals; - use util::{debug_panic, defer, merge_json_value_into, post_inc, ResultExt, TryFutureExt as _}; pub use fs::*; @@ -454,7 +453,9 @@ impl Project { client_state: None, opened_buffer: watch::channel(), client_subscriptions: Vec::new(), - _subscriptions: vec![cx.observe_global::(Self::on_settings_changed)], + _subscriptions: vec![ + cx.observe_global::(Self::on_settings_changed) + ], _maintain_buffer_languages: Self::maintain_buffer_languages(&languages, cx), _maintain_workspace_config: Self::maintain_workspace_config(languages.clone(), cx), active_entry: None, @@ -622,7 +623,7 @@ impl Project { } fn on_settings_changed(&mut self, cx: &mut ModelContext) { - let settings = cx.global::(); + let settings = all_language_settings(None, cx); let mut language_servers_to_start = Vec::new(); for buffer in self.opened_buffers.values() { @@ -630,7 +631,10 @@ impl Project { let buffer = buffer.read(cx); if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { - if settings.enable_language_server(Some(&language.name())) { + if settings + .language(Some(&language.name())) + .enable_language_server + { let worktree = file.worktree.read(cx); language_servers_to_start.push(( worktree.id(), @@ -645,7 +649,10 @@ impl Project { let mut language_servers_to_stop = Vec::new(); for language in self.languages.to_vec() { for lsp_adapter in language.lsp_adapters() { - if !settings.enable_language_server(Some(&language.name())) { + if !settings + .language(Some(&language.name())) + .enable_language_server + { let lsp_name = &lsp_adapter.name; for (worktree_id, started_lsp_name) in self.language_server_ids.keys() { if lsp_name == started_lsp_name { @@ -2178,10 +2185,7 @@ impl Project { language: Arc, cx: &mut ModelContext, ) { - if !cx - .global::() - .enable_language_server(Some(&language.name())) - { + if !language_settings(None, Some(&language.name()), cx).enable_language_server { return; } @@ -3228,24 +3232,18 @@ impl Project { let mut project_transaction = ProjectTransaction::default(); for (buffer, buffer_abs_path, language_server) in &buffers_with_paths_and_servers { - let ( - format_on_save, - remove_trailing_whitespace, - ensure_final_newline, - formatter, - tab_size, - ) = buffer.read_with(&cx, |buffer, cx| { - let settings = cx.global::(); + let settings = buffer.read_with(&cx, |buffer, cx| { let language_name = buffer.language().map(|language| language.name()); - ( - settings.format_on_save(language_name.as_deref()), - settings.remove_trailing_whitespace_on_save(language_name.as_deref()), - settings.ensure_final_newline_on_save(language_name.as_deref()), - settings.formatter(language_name.as_deref()), - settings.tab_size(language_name.as_deref()), - ) + language_settings(buffer_abs_path.as_deref(), language_name.as_deref(), cx) + .clone() }); + let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save; + let ensure_final_newline = settings.ensure_final_newline_on_save; + let format_on_save = settings.format_on_save.clone(); + let formatter = settings.formatter.clone(); + let tab_size = settings.tab_size; + // First, format buffer's whitespace according to the settings. let trailing_whitespace_diff = if remove_trailing_whitespace { Some( diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 894b27f2ee..1cbef629f3 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1,10 +1,9 @@ use crate::{worktree::WorktreeHandle, Event, *}; -use fs::LineEnding; -use fs::{FakeFs, RealFs}; +use fs::{FakeFs, LineEnding, RealFs}; use futures::{future, StreamExt}; -use gpui::AppContext; -use gpui::{executor::Deterministic, test::subscribe}; +use gpui::{executor::Deterministic, test::subscribe, AppContext}; use language::{ + language_settings::{AllLanguageSettings, LanguageSettingsContent}, tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig, OffsetRangeExt, Point, ToPoint, }; @@ -26,6 +25,9 @@ fn init_logger() { #[gpui::test] async fn test_symlinks(cx: &mut gpui::TestAppContext) { + init_test(cx); + cx.foreground().allow_parking(); + let dir = temp_tree(json!({ "root": { "apple": "", @@ -65,7 +67,7 @@ async fn test_managing_language_servers( deterministic: Arc, cx: &mut gpui::TestAppContext, ) { - cx.foreground().forbid_parking(); + init_test(cx); let mut rust_language = Language::new( LanguageConfig { @@ -451,7 +453,7 @@ async fn test_managing_language_servers( #[gpui::test] async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let mut language = Language::new( LanguageConfig { @@ -556,7 +558,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon #[gpui::test] async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -648,7 +650,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -719,7 +721,7 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let progress_token = "the-progress-token"; let mut language = Language::new( @@ -847,7 +849,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let progress_token = "the-progress-token"; let mut language = Language::new( @@ -925,7 +927,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC #[gpui::test] async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let mut language = Language::new( LanguageConfig { @@ -973,11 +975,8 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T } #[gpui::test] -async fn test_toggling_enable_language_server( - deterministic: Arc, - cx: &mut gpui::TestAppContext, -) { - deterministic.forbid_parking(); +async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) { + init_test(cx); let mut rust = Language::new( LanguageConfig { @@ -1051,14 +1050,16 @@ async fn test_toggling_enable_language_server( // Disable Rust language server, ensuring only that server gets stopped. cx.update(|cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.language_overrides.insert( - Arc::from("Rust"), - settings::EditorSettings { - enable_language_server: Some(false), - ..Default::default() - }, - ); + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.languages.insert( + Arc::from("Rust"), + LanguageSettingsContent { + enable_language_server: Some(false), + ..Default::default() + }, + ); + }); }) }); fake_rust_server_1 @@ -1068,21 +1069,23 @@ async fn test_toggling_enable_language_server( // Enable Rust and disable JavaScript language servers, ensuring that the // former gets started again and that the latter stops. cx.update(|cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.language_overrides.insert( - Arc::from("Rust"), - settings::EditorSettings { - enable_language_server: Some(true), - ..Default::default() - }, - ); - settings.language_overrides.insert( - Arc::from("JavaScript"), - settings::EditorSettings { - enable_language_server: Some(false), - ..Default::default() - }, - ); + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.languages.insert( + Arc::from("Rust"), + LanguageSettingsContent { + enable_language_server: Some(true), + ..Default::default() + }, + ); + settings.languages.insert( + Arc::from("JavaScript"), + LanguageSettingsContent { + enable_language_server: Some(false), + ..Default::default() + }, + ); + }); }) }); let mut fake_rust_server_2 = fake_rust_servers.next().await.unwrap(); @@ -1102,7 +1105,7 @@ async fn test_toggling_enable_language_server( #[gpui::test] async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let mut language = Language::new( LanguageConfig { @@ -1388,7 +1391,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let text = concat!( "let one = ;\n", // @@ -1457,9 +1460,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppContext) { - println!("hello from stdout"); - eprintln!("hello from stderr"); - cx.foreground().forbid_parking(); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree("/dir", json!({ "a.rs": "one two three" })) @@ -1515,7 +1516,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC #[gpui::test] async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let mut language = Language::new( LanguageConfig { @@ -1673,7 +1674,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let text = " use a::b; @@ -1781,7 +1782,7 @@ async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestApp #[gpui::test] async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let text = " use a::b; @@ -1902,6 +1903,8 @@ fn chunks_with_diagnostics( #[gpui::test(iterations = 10)] async fn test_definition(cx: &mut gpui::TestAppContext) { + init_test(cx); + let mut language = Language::new( LanguageConfig { name: "Rust".into(), @@ -2001,6 +2004,8 @@ async fn test_definition(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) { + init_test(cx); + let mut language = Language::new( LanguageConfig { name: "TypeScript".into(), @@ -2085,6 +2090,8 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) { + init_test(cx); + let mut language = Language::new( LanguageConfig { name: "TypeScript".into(), @@ -2138,6 +2145,8 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) { #[gpui::test(iterations = 10)] async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) { + init_test(cx); + let mut language = Language::new( LanguageConfig { name: "TypeScript".into(), @@ -2254,6 +2263,8 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) { #[gpui::test(iterations = 10)] async fn test_save_file(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/dir", @@ -2284,6 +2295,8 @@ async fn test_save_file(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/dir", @@ -2313,6 +2326,8 @@ async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_save_as(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree("/dir", json!({})).await; @@ -2373,6 +2388,9 @@ async fn test_rescan_and_remote_updates( deterministic: Arc, cx: &mut gpui::TestAppContext, ) { + init_test(cx); + cx.foreground().allow_parking(); + let dir = temp_tree(json!({ "a": { "file1": "", @@ -2529,6 +2547,8 @@ async fn test_buffer_identity_across_renames( deterministic: Arc, cx: &mut gpui::TestAppContext, ) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/dir", @@ -2577,6 +2597,8 @@ async fn test_buffer_identity_across_renames( #[gpui::test] async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/dir", @@ -2621,6 +2643,8 @@ async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/dir", @@ -2765,6 +2789,8 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) { + init_test(cx); + let initial_contents = "aaa\nbbbbb\nc\n"; let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -2844,6 +2870,8 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/dir", @@ -2904,7 +2932,7 @@ async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -3146,7 +3174,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_rename(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); + init_test(cx); let mut language = Language::new( LanguageConfig { @@ -3284,6 +3312,8 @@ async fn test_rename(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_search(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/dir", @@ -3339,6 +3369,8 @@ async fn test_search(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) { + init_test(cx); + let search_query = "file"; let fs = FakeFs::new(cx.background()); @@ -3447,6 +3479,8 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) { + init_test(cx); + let search_query = "file"; let fs = FakeFs::new(cx.background()); @@ -3554,6 +3588,8 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) { #[gpui::test] async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContext) { + init_test(cx); + let search_query = "file"; let fs = FakeFs::new(cx.background()); @@ -3680,3 +3716,12 @@ async fn search( }) .collect()) } + +fn init_test(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + language::init(cx); + }); +} diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index d3b2876675..d52ec403fb 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -24,6 +24,7 @@ futures.workspace = true unicase = "2.6" [dev-dependencies] +language = { path = "../language", features = ["test-support"] } editor = { path = "../editor", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } workspace = { path = "../workspace", features = ["test-support"] } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 7602ff7db8..97684d874a 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1360,15 +1360,12 @@ mod tests { use gpui::{TestAppContext, ViewHandle}; use project::FakeFs; use serde_json::json; + use settings::SettingsStore; use std::{collections::HashSet, path::Path}; #[gpui::test] async fn test_visible_list(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - cx.update(|cx| { - let settings = Settings::test(cx); - cx.set_global(settings); - }); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -1456,11 +1453,7 @@ mod tests { #[gpui::test(iterations = 30)] async fn test_editing_files(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - cx.update(|cx| { - let settings = Settings::test(cx); - cx.set_global(settings); - }); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -1776,11 +1769,7 @@ mod tests { #[gpui::test] async fn test_copy_paste(cx: &mut gpui::TestAppContext) { - cx.foreground().forbid_parking(); - cx.update(|cx| { - let settings = Settings::test(cx); - cx.set_global(settings); - }); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -1940,4 +1929,12 @@ mod tests { result } + + fn init_test(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + language::init(cx); + }); + } } diff --git a/crates/project_symbols/Cargo.toml b/crates/project_symbols/Cargo.toml index 1c0d3a6165..be2510c895 100644 --- a/crates/project_symbols/Cargo.toml +++ b/crates/project_symbols/Cargo.toml @@ -30,3 +30,4 @@ gpui = { path = "../gpui", features = ["test-support"] } language = { path = "../language", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } project = { path = "../project", features = ["test-support"] } +workspace = { path = "../workspace", features = ["test-support"] } diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 25828f17ca..3176cad308 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -244,12 +244,12 @@ mod tests { use gpui::{serde_json::json, TestAppContext}; use language::{FakeLspAdapter, Language, LanguageConfig}; use project::FakeFs; + use settings::SettingsStore; use std::{path::Path, sync::Arc}; #[gpui::test] async fn test_project_symbols(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx); let mut language = Language::new( LanguageConfig { @@ -368,6 +368,15 @@ mod tests { }); } + fn init_test(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + cx.update(|cx| { + cx.set_global(Settings::test(cx)); + cx.set_global(SettingsStore::test(cx)); + language::init(cx); + }); + } + fn symbol(name: &str, path: impl AsRef) -> lsp::SymbolInformation { #[allow(deprecated)] lsp::SymbolInformation { diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index b0af51379d..38f3894743 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -655,19 +655,11 @@ mod tests { use editor::{DisplayPoint, Editor}; use gpui::{color::Color, test::EmptyView, TestAppContext}; use language::Buffer; - use std::sync::Arc; use unindent::Unindent as _; #[gpui::test] async fn test_search_simple(cx: &mut TestAppContext) { - let fonts = cx.font_cache(); - let mut theme = gpui::fonts::with_font_cache(fonts.clone(), theme::Theme::default); - theme.search.match_background = Color::red(); - cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.theme = Arc::new(theme); - cx.set_global(settings) - }); + crate::project_search::tests::init_test(cx); let buffer = cx.add_model(|cx| { Buffer::new( diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 05d27b824c..4dc947e5f6 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1146,25 +1146,18 @@ impl ToolbarItemView for ProjectSearchBar { } #[cfg(test)] -mod tests { +pub mod tests { use super::*; use editor::DisplayPoint; use gpui::{color::Color, executor::Deterministic, TestAppContext}; use project::FakeFs; use serde_json::json; + use settings::SettingsStore; use std::sync::Arc; #[gpui::test] async fn test_project_search(deterministic: Arc, cx: &mut TestAppContext) { - let fonts = cx.font_cache(); - let mut theme = gpui::fonts::with_font_cache(fonts.clone(), theme::Theme::default); - theme.search.match_background = Color::red(); - cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.theme = Arc::new(theme); - cx.set_global(settings); - cx.set_global(ActiveSearches::default()); - }); + init_test(cx); let fs = FakeFs::new(cx.background()); fs.insert_tree( @@ -1279,4 +1272,20 @@ mod tests { ); }); } + + pub fn init_test(cx: &mut TestAppContext) { + let fonts = cx.font_cache(); + let mut theme = gpui::fonts::with_font_cache(fonts.clone(), theme::Theme::default); + theme.search.match_background = Color::red(); + + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + cx.set_global(ActiveSearches::default()); + let mut settings = Settings::test(cx); + settings.theme = Arc::new(theme); + cx.set_global(settings); + + language::init(cx); + }); + } } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index c476024d62..f5e50aaffb 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -3,7 +3,7 @@ mod keymap_file; mod settings_file; mod settings_store; -use anyhow::bail; +use anyhow::{bail, Result}; use gpui::{ font_cache::{FamilyId, FontCache}, fonts, AppContext, AssetSource, @@ -19,7 +19,7 @@ use sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use std::{borrow::Cow, collections::HashMap, num::NonZeroU32, path::Path, str, sync::Arc}; +use std::{borrow::Cow, collections::HashMap, str, sync::Arc}; use theme::{Theme, ThemeRegistry}; use util::ResultExt as _; @@ -33,7 +33,6 @@ pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settin #[derive(Clone)] pub struct Settings { - pub features: Features, pub buffer_font_family_name: String, pub buffer_font_features: fonts::Features, pub buffer_font_family: FamilyId, @@ -46,13 +45,8 @@ pub struct Settings { pub show_call_status_icon: bool, pub autosave: Autosave, pub default_dock_anchor: DockAnchor, - pub editor_defaults: EditorSettings, - pub editor_overrides: EditorSettings, pub git: GitSettings, pub git_overrides: GitSettings, - pub copilot: CopilotSettings, - pub language_defaults: HashMap, EditorSettings>, - pub language_overrides: HashMap, EditorSettings>, pub lsp: HashMap, LspSettings>, pub theme: Arc, pub base_keymap: BaseKeymap, @@ -67,7 +61,7 @@ impl Setting for Settings { defaults: &Self::FileContent, user_values: &[&Self::FileContent], cx: &AppContext, - ) -> Self { + ) -> Result { let buffer_font_features = defaults.buffer_font_features.clone().unwrap(); let themes = cx.global::>(); @@ -90,50 +84,18 @@ impl Setting for Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), autosave: defaults.autosave.unwrap(), default_dock_anchor: defaults.default_dock_anchor.unwrap(), - editor_defaults: EditorSettings { - tab_size: defaults.editor.tab_size, - hard_tabs: defaults.editor.hard_tabs, - soft_wrap: defaults.editor.soft_wrap, - preferred_line_length: defaults.editor.preferred_line_length, - remove_trailing_whitespace_on_save: defaults - .editor - .remove_trailing_whitespace_on_save, - ensure_final_newline_on_save: defaults.editor.ensure_final_newline_on_save, - format_on_save: defaults.editor.format_on_save.clone(), - formatter: defaults.editor.formatter.clone(), - enable_language_server: defaults.editor.enable_language_server, - show_copilot_suggestions: defaults.editor.show_copilot_suggestions, - show_whitespaces: defaults.editor.show_whitespaces, - }, - editor_overrides: Default::default(), - copilot: CopilotSettings { - disabled_globs: defaults - .copilot - .clone() - .unwrap() - .disabled_globs - .unwrap() - .into_iter() - .map(|s| glob::Pattern::new(&s).unwrap()) - .collect(), - }, git: defaults.git.unwrap(), git_overrides: Default::default(), - language_defaults: defaults.languages.clone(), - language_overrides: Default::default(), lsp: defaults.lsp.clone(), theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), base_keymap: Default::default(), - features: Features { - copilot: defaults.features.copilot.unwrap(), - }, }; for value in user_values.into_iter().copied().cloned() { this.set_user_settings(value, themes.as_ref(), cx.font_cache()); } - this + Ok(this) } fn json_schema( @@ -247,18 +209,6 @@ impl BaseKeymap { .unwrap_or_default() } } - -#[derive(Clone, Debug, Default)] -pub struct CopilotSettings { - pub disabled_globs: Vec, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -pub struct CopilotSettingsContent { - #[serde(default)] - pub disabled_globs: Option>, -} - #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct GitSettings { pub git_gutter: Option, @@ -273,52 +223,6 @@ pub enum GitGutter { Hide, } -pub struct GitGutterConfig {} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -pub struct EditorSettings { - pub tab_size: Option, - pub hard_tabs: Option, - pub soft_wrap: Option, - pub preferred_line_length: Option, - pub format_on_save: Option, - pub remove_trailing_whitespace_on_save: Option, - pub ensure_final_newline_on_save: Option, - pub formatter: Option, - pub enable_language_server: Option, - pub show_copilot_suggestions: Option, - pub show_whitespaces: Option, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum SoftWrap { - None, - EditorWidth, - PreferredLineLength, -} -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum FormatOnSave { - On, - Off, - LanguageServer, - External { - command: String, - arguments: Vec, - }, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum Formatter { - LanguageServer, - External { - command: String, - arguments: Vec, - }, -} - #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum Autosave { @@ -374,8 +278,6 @@ pub struct SettingsFileContent { #[serde(default)] pub buffer_font_features: Option, #[serde(default)] - pub copilot: Option, - #[serde(default)] pub active_pane_magnification: Option, #[serde(default)] pub cursor_blink: Option, @@ -391,21 +293,14 @@ pub struct SettingsFileContent { pub autosave: Option, #[serde(default)] pub default_dock_anchor: Option, - #[serde(flatten)] - pub editor: EditorSettings, #[serde(default)] pub git: Option, #[serde(default)] - #[serde(alias = "language_overrides")] - pub languages: HashMap, EditorSettings>, - #[serde(default)] pub lsp: HashMap, LspSettings>, #[serde(default)] pub theme: Option, #[serde(default)] pub base_keymap: Option, - #[serde(default)] - pub features: FeaturesContent, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] @@ -414,26 +309,6 @@ pub struct LspSettings { pub initialization_options: Option, } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Features { - pub copilot: bool, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct FeaturesContent { - pub copilot: Option, -} - -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ShowWhitespaces { - #[default] - Selection, - None, - All, -} - impl Settings { pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> { match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() { @@ -448,12 +323,6 @@ impl Settings { font_cache: &FontCache, themes: &ThemeRegistry, ) -> Self { - #[track_caller] - fn required(value: Option) -> Option { - assert!(value.is_some(), "missing default setting value"); - value - } - let defaults: SettingsFileContent = settings_store::parse_json_with_comments( str::from_utf8(assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap().as_ref()).unwrap(), ) @@ -478,44 +347,11 @@ impl Settings { show_call_status_icon: defaults.show_call_status_icon.unwrap(), autosave: defaults.autosave.unwrap(), default_dock_anchor: defaults.default_dock_anchor.unwrap(), - editor_defaults: EditorSettings { - tab_size: required(defaults.editor.tab_size), - hard_tabs: required(defaults.editor.hard_tabs), - soft_wrap: required(defaults.editor.soft_wrap), - preferred_line_length: required(defaults.editor.preferred_line_length), - remove_trailing_whitespace_on_save: required( - defaults.editor.remove_trailing_whitespace_on_save, - ), - ensure_final_newline_on_save: required( - defaults.editor.ensure_final_newline_on_save, - ), - format_on_save: required(defaults.editor.format_on_save), - formatter: required(defaults.editor.formatter), - enable_language_server: required(defaults.editor.enable_language_server), - show_copilot_suggestions: required(defaults.editor.show_copilot_suggestions), - show_whitespaces: required(defaults.editor.show_whitespaces), - }, - editor_overrides: Default::default(), - copilot: CopilotSettings { - disabled_globs: defaults - .copilot - .unwrap() - .disabled_globs - .unwrap() - .into_iter() - .map(|s| glob::Pattern::new(&s).unwrap()) - .collect(), - }, git: defaults.git.unwrap(), git_overrides: Default::default(), - language_defaults: defaults.languages, - language_overrides: Default::default(), lsp: defaults.lsp.clone(), theme: themes.get(&defaults.theme.unwrap()).unwrap(), base_keymap: Default::default(), - features: Features { - copilot: defaults.features.copilot.unwrap(), - }, } } @@ -565,121 +401,11 @@ impl Settings { merge(&mut self.autosave, data.autosave); merge(&mut self.default_dock_anchor, data.default_dock_anchor); merge(&mut self.base_keymap, data.base_keymap); - merge(&mut self.features.copilot, data.features.copilot); - if let Some(copilot) = data.copilot { - if let Some(disabled_globs) = copilot.disabled_globs { - self.copilot.disabled_globs = disabled_globs - .into_iter() - .filter_map(|s| glob::Pattern::new(&s).ok()) - .collect() - } - } - self.editor_overrides = data.editor; self.git_overrides = data.git.unwrap_or_default(); - self.language_overrides = data.languages; self.lsp = data.lsp; } - pub fn with_language_defaults( - mut self, - language_name: impl Into>, - overrides: EditorSettings, - ) -> Self { - self.language_defaults - .insert(language_name.into(), overrides); - self - } - - pub fn features(&self) -> &Features { - &self.features - } - - pub fn show_copilot_suggestions(&self, language: Option<&str>, path: Option<&Path>) -> bool { - if !self.features.copilot { - return false; - } - - if !self.copilot_enabled_for_language(language) { - return false; - } - - if let Some(path) = path { - if !self.copilot_enabled_for_path(path) { - return false; - } - } - - true - } - - pub fn copilot_enabled_for_path(&self, path: &Path) -> bool { - !self - .copilot - .disabled_globs - .iter() - .any(|glob| glob.matches_path(path)) - } - - pub fn copilot_enabled_for_language(&self, language: Option<&str>) -> bool { - self.language_setting(language, |settings| settings.show_copilot_suggestions) - } - - pub fn tab_size(&self, language: Option<&str>) -> NonZeroU32 { - self.language_setting(language, |settings| settings.tab_size) - } - - pub fn show_whitespaces(&self, language: Option<&str>) -> ShowWhitespaces { - self.language_setting(language, |settings| settings.show_whitespaces) - } - - pub fn hard_tabs(&self, language: Option<&str>) -> bool { - self.language_setting(language, |settings| settings.hard_tabs) - } - - pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap { - self.language_setting(language, |settings| settings.soft_wrap) - } - - pub fn preferred_line_length(&self, language: Option<&str>) -> u32 { - self.language_setting(language, |settings| settings.preferred_line_length) - } - - pub fn remove_trailing_whitespace_on_save(&self, language: Option<&str>) -> bool { - self.language_setting(language, |settings| { - settings.remove_trailing_whitespace_on_save.clone() - }) - } - - pub fn ensure_final_newline_on_save(&self, language: Option<&str>) -> bool { - self.language_setting(language, |settings| { - settings.ensure_final_newline_on_save.clone() - }) - } - - pub fn format_on_save(&self, language: Option<&str>) -> FormatOnSave { - self.language_setting(language, |settings| settings.format_on_save.clone()) - } - - pub fn formatter(&self, language: Option<&str>) -> Formatter { - self.language_setting(language, |settings| settings.formatter.clone()) - } - - pub fn enable_language_server(&self, language: Option<&str>) -> bool { - self.language_setting(language, |settings| settings.enable_language_server) - } - - fn language_setting(&self, language: Option<&str>, f: F) -> R - where - F: Fn(&EditorSettings) -> Option, - { - None.or_else(|| language.and_then(|l| self.language_overrides.get(l).and_then(&f))) - .or_else(|| f(&self.editor_overrides)) - .or_else(|| language.and_then(|l| self.language_defaults.get(l).and_then(&f))) - .or_else(|| f(&self.editor_defaults)) - .expect("missing default") - } - pub fn git_gutter(&self) -> GitGutter { self.git_overrides.git_gutter.unwrap_or_else(|| { self.git @@ -706,29 +432,11 @@ impl Settings { show_call_status_icon: true, autosave: Autosave::Off, default_dock_anchor: DockAnchor::Bottom, - editor_defaults: EditorSettings { - tab_size: Some(4.try_into().unwrap()), - hard_tabs: Some(false), - soft_wrap: Some(SoftWrap::None), - preferred_line_length: Some(80), - remove_trailing_whitespace_on_save: Some(true), - ensure_final_newline_on_save: Some(true), - format_on_save: Some(FormatOnSave::On), - formatter: Some(Formatter::LanguageServer), - enable_language_server: Some(true), - show_copilot_suggestions: Some(true), - show_whitespaces: Some(ShowWhitespaces::None), - }, - editor_overrides: Default::default(), - copilot: Default::default(), git: Default::default(), git_overrides: Default::default(), - language_defaults: Default::default(), - language_overrides: Default::default(), lsp: Default::default(), theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), base_keymap: Default::default(), - features: Features { copilot: true }, } } diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 936f3a7099..070da5d1ea 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -42,12 +42,12 @@ pub fn test_settings() -> String { serde_json::json!({ "buffer_font_family": "Courier", "buffer_font_features": {}, - "default_buffer_font_size": 14, - "preferred_line_length": 80, + "buffer_font_size": 14, "theme": theme::EMPTY_THEME_NAME, }), &mut value, ); + value.as_object_mut().unwrap().remove("languages"); serde_json::to_string(&value).unwrap() } diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index caa77a3603..c389a7bf84 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -1,5 +1,5 @@ -use anyhow::{anyhow, Result}; -use collections::{btree_map, hash_map, BTreeMap, HashMap, HashSet}; +use anyhow::Result; +use collections::{btree_map, hash_map, BTreeMap, HashMap}; use gpui::AppContext; use lazy_static::lazy_static; use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema}; @@ -8,7 +8,6 @@ use smallvec::SmallVec; use std::{ any::{type_name, Any, TypeId}, fmt::Debug, - mem, ops::Range, path::Path, str, @@ -37,7 +36,9 @@ pub trait Setting: 'static { default_value: &Self::FileContent, user_values: &[&Self::FileContent], cx: &AppContext, - ) -> Self; + ) -> Result + where + Self: Sized; fn json_schema(generator: &mut SchemaGenerator, _: &SettingsJsonSchemaParams) -> RootSchema { generator.root_schema_for::() @@ -57,7 +58,7 @@ pub trait Setting: 'static { fn load_via_json_merge( default_value: &Self::FileContent, user_values: &[&Self::FileContent], - ) -> Self + ) -> Result where Self: DeserializeOwned, { @@ -65,7 +66,11 @@ pub trait Setting: 'static { for value in [default_value].iter().chain(user_values) { merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged); } - serde_json::from_value(merged).unwrap() + Ok(serde_json::from_value(merged)?) + } + + fn missing_default() -> anyhow::Error { + anyhow::anyhow!("missing default") } } @@ -78,10 +83,9 @@ pub struct SettingsJsonSchemaParams<'a> { #[derive(Default)] pub struct SettingsStore { setting_values: HashMap>, - default_deserialized_settings: Option, - user_deserialized_settings: Option, - local_deserialized_settings: BTreeMap, DeserializedSettingMap>, - changed_setting_types: HashSet, + default_deserialized_settings: Option, + user_deserialized_settings: Option, + local_deserialized_settings: BTreeMap, serde_json::Value>, tab_size_callback: Option<(TypeId, Box Option>)>, } @@ -98,9 +102,9 @@ trait AnySettingValue { fn load_setting( &self, default_value: &DeserializedSetting, - custom: &[&DeserializedSetting], + custom: &[DeserializedSetting], cx: &AppContext, - ) -> Box; + ) -> Result>; fn value_for_path(&self, path: Option<&Path>) -> &dyn Any; fn set_global_value(&mut self, value: Box); fn set_local_value(&mut self, path: Arc, value: Box); @@ -113,11 +117,6 @@ trait AnySettingValue { struct DeserializedSetting(Box); -struct DeserializedSettingMap { - untyped: serde_json::Value, - typed: HashMap, -} - impl SettingsStore { /// Add a new type of setting to the store. pub fn register_setting(&mut self, cx: &AppContext) { @@ -132,23 +131,27 @@ impl SettingsStore { local_values: Vec::new(), })); - if let Some(default_settings) = self.default_deserialized_settings.as_mut() { - Self::load_setting_in_map(setting_type_id, setting_value, default_settings); + if let Some(default_settings) = &self.default_deserialized_settings { + if let Some(default_settings) = setting_value + .deserialize_setting(default_settings) + .log_err() + { + let mut user_values_stack = Vec::new(); - let mut user_values_stack = Vec::new(); - if let Some(user_settings) = self.user_deserialized_settings.as_mut() { - Self::load_setting_in_map(setting_type_id, setting_value, user_settings); - if let Some(user_value) = user_settings.typed.get(&setting_type_id) { - user_values_stack = vec![user_value]; + if let Some(user_settings) = &self.user_deserialized_settings { + if let Some(user_settings) = + setting_value.deserialize_setting(user_settings).log_err() + { + user_values_stack = vec![user_settings]; + } } - } - if let Some(default_deserialized_value) = default_settings.typed.get(&setting_type_id) { - setting_value.set_global_value(setting_value.load_setting( - default_deserialized_value, - &user_values_stack, - cx, - )); + if let Some(setting) = setting_value + .load_setting(&default_settings, &user_values_stack, cx) + .log_err() + { + setting_value.set_global_value(setting); + } } } } @@ -173,7 +176,7 @@ impl SettingsStore { pub fn untyped_user_settings(&self) -> &serde_json::Value { self.user_deserialized_settings .as_ref() - .map_or(&serde_json::Value::Null, |s| &s.untyped) + .unwrap_or(&serde_json::Value::Null) } #[cfg(any(test, feature = "test-support"))] @@ -181,6 +184,7 @@ impl SettingsStore { let mut this = Self::default(); this.set_default_settings(&crate::test_settings(), cx) .unwrap(); + this.set_user_settings("{}", cx).unwrap(); this } @@ -194,11 +198,11 @@ impl SettingsStore { cx: &AppContext, update: impl FnOnce(&mut T::FileContent), ) { - let old_text = if let Some(user_settings) = &self.user_deserialized_settings { - serde_json::to_string(&user_settings.untyped).unwrap() - } else { - String::new() - }; + if self.user_deserialized_settings.is_none() { + self.set_user_settings("{}", cx).unwrap(); + } + let old_text = + serde_json::to_string(self.user_deserialized_settings.as_ref().unwrap()).unwrap(); let new_text = self.new_text_for_update::(old_text, update); self.set_user_settings(&new_text, cx).unwrap(); } @@ -212,7 +216,7 @@ impl SettingsStore { ) -> String { let edits = self.edits_for_update::(&old_text, update); let mut new_text = old_text; - for (range, replacement) in edits.into_iter().rev() { + for (range, replacement) in edits.into_iter() { new_text.replace_range(range, &replacement); } new_text @@ -226,26 +230,31 @@ impl SettingsStore { update: impl FnOnce(&mut T::FileContent), ) -> Vec<(Range, String)> { let setting_type_id = TypeId::of::(); + let old_content = self - .user_deserialized_settings - .as_ref() - .unwrap() - .typed + .setting_values .get(&setting_type_id) - .unwrap() + .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::())) + .deserialize_setting( + self.user_deserialized_settings + .as_ref() + .expect("no user settings loaded"), + ) + .unwrap_or_else(|e| { + panic!( + "could not deserialize setting type {} from user settings: {}", + type_name::(), + e + ) + }) .0 - .downcast_ref::() - .unwrap() - .clone(); + .downcast::() + .unwrap(); let mut new_content = old_content.clone(); update(&mut new_content); - let mut parser = tree_sitter::Parser::new(); - parser.set_language(tree_sitter_json::language()).unwrap(); - let tree = parser.parse(text, None).unwrap(); - - let old_value = &serde_json::to_value(old_content).unwrap(); - let new_value = &serde_json::to_value(new_content).unwrap(); + let old_value = &serde_json::to_value(&old_content).unwrap(); + let new_value = serde_json::to_value(new_content).unwrap(); let mut key_path = Vec::new(); if let Some(key) = T::KEY { @@ -254,16 +263,15 @@ impl SettingsStore { let mut edits = Vec::new(); let tab_size = self.json_tab_size(); + let mut text = text.to_string(); update_value_in_json_text( - &text, - &tree, + &mut text, &mut key_path, tab_size, &old_value, &new_value, &mut edits, ); - edits.sort_unstable_by_key(|e| e.0.start); return edits; } @@ -300,19 +308,8 @@ impl SettingsStore { default_settings_content: &str, cx: &AppContext, ) -> Result<()> { - let deserialized_setting_map = self.load_setting_map(default_settings_content)?; - if deserialized_setting_map.typed.len() != self.setting_values.len() { - return Err(anyhow!( - "default settings file is missing fields: {:?}", - self.setting_values - .iter() - .filter(|(type_id, _)| !deserialized_setting_map.typed.contains_key(type_id)) - .map(|(name, _)| *name) - .collect::>() - )); - } - self.default_deserialized_settings = Some(deserialized_setting_map); - self.recompute_values(false, None, None, cx); + self.default_deserialized_settings = Some(serde_json::from_str(default_settings_content)?); + self.recompute_values(None, cx)?; Ok(()) } @@ -322,10 +319,8 @@ impl SettingsStore { user_settings_content: &str, cx: &AppContext, ) -> Result<()> { - let user_settings = self.load_setting_map(user_settings_content)?; - let old_user_settings = - mem::replace(&mut self.user_deserialized_settings, Some(user_settings)); - self.recompute_values(true, None, old_user_settings, cx); + self.user_deserialized_settings = Some(serde_json::from_str(user_settings_content)?); + self.recompute_values(None, cx)?; Ok(()) } @@ -336,14 +331,13 @@ impl SettingsStore { settings_content: Option<&str>, cx: &AppContext, ) -> Result<()> { - let removed_map = if let Some(settings_content) = settings_content { + if let Some(content) = settings_content { self.local_deserialized_settings - .insert(path.clone(), self.load_setting_map(settings_content)?); - None + .insert(path.clone(), serde_json::from_str(content)?); } else { - self.local_deserialized_settings.remove(&path) - }; - self.recompute_values(true, Some(&path), removed_map, cx); + self.local_deserialized_settings.remove(&path); + } + self.recompute_values(Some(&path), cx)?; Ok(()) } @@ -422,136 +416,78 @@ impl SettingsStore { fn recompute_values( &mut self, - user_settings_changed: bool, changed_local_path: Option<&Path>, - old_settings_map: Option, cx: &AppContext, - ) { - // Identify all of the setting types that have changed. - let new_settings_map = if let Some(changed_path) = changed_local_path { - self.local_deserialized_settings.get(changed_path) - } else if user_settings_changed { - self.user_deserialized_settings.as_ref() - } else { - self.default_deserialized_settings.as_ref() - }; - self.changed_setting_types.clear(); - for map in [old_settings_map.as_ref(), new_settings_map] { - if let Some(map) = map { - self.changed_setting_types.extend(map.typed.keys()); - } - } + ) -> Result<()> { + // Reload the global and local values for every setting. + let mut user_settings_stack = Vec::::new(); + let mut paths_stack = Vec::>::new(); + for setting_value in self.setting_values.values_mut() { + if let Some(default_settings) = &self.default_deserialized_settings { + let default_settings = setting_value.deserialize_setting(default_settings)?; - // Reload the global and local values for every changed setting. - let mut user_values_stack = Vec::<&DeserializedSetting>::new(); - for setting_type_id in self.changed_setting_types.iter() { - let setting_value = self.setting_values.get_mut(setting_type_id).unwrap(); + user_settings_stack.clear(); + paths_stack.clear(); - // Build the prioritized list of deserialized values to pass to the setting's - // load function. - user_values_stack.clear(); - if let Some(user_settings) = &self.user_deserialized_settings { - if let Some(user_value) = user_settings.typed.get(setting_type_id) { - user_values_stack.push(&user_value); - } - } - - let default_deserialized_value = if let Some(value) = self - .default_deserialized_settings - .as_ref() - .and_then(|map| map.typed.get(setting_type_id)) - { - value - } else { - continue; - }; - - // If the global settings file changed, reload the global value for the field. - if changed_local_path.is_none() { - setting_value.set_global_value(setting_value.load_setting( - default_deserialized_value, - &user_values_stack, - cx, - )); - } - - // Reload the local values for the setting. - let user_value_stack_len = user_values_stack.len(); - for (path, deserialized_values) in &self.local_deserialized_settings { - // If a local settings file changed, then avoid recomputing local - // settings for any path outside of that directory. - if changed_local_path.map_or(false, |changed_local_path| { - !path.starts_with(changed_local_path) - }) { - continue; + if let Some(user_settings) = &self.user_deserialized_settings { + if let Some(user_settings) = + setting_value.deserialize_setting(user_settings).log_err() + { + user_settings_stack.push(user_settings); + paths_stack.push(None); + } } - // Ignore recomputing settings for any path that hasn't customized that setting. - let Some(deserialized_value) = deserialized_values.typed.get(setting_type_id) else { - continue; - }; + // If the global settings file changed, reload the global value for the field. + if changed_local_path.is_none() { + setting_value.set_global_value(setting_value.load_setting( + &default_settings, + &user_settings_stack, + cx, + )?); + } - // Build a stack of all of the local values for that setting. - user_values_stack.truncate(user_value_stack_len); - for (preceding_path, preceding_deserialized_values) in - &self.local_deserialized_settings - { - if preceding_path >= path { + // Reload the local values for the setting. + for (path, local_settings) in &self.local_deserialized_settings { + // Build a stack of all of the local values for that setting. + while let Some(prev_path) = paths_stack.last() { + if let Some(prev_path) = prev_path { + if !path.starts_with(prev_path) { + paths_stack.pop(); + user_settings_stack.pop(); + continue; + } + } break; } - if !path.starts_with(preceding_path) { - continue; - } - if let Some(preceding_deserialized_value) = - preceding_deserialized_values.typed.get(setting_type_id) + if let Some(local_settings) = + setting_value.deserialize_setting(&local_settings).log_err() { - user_values_stack.push(&*preceding_deserialized_value); + paths_stack.push(Some(path.as_ref())); + user_settings_stack.push(local_settings); + + // If a local settings file changed, then avoid recomputing local + // settings for any path outside of that directory. + if changed_local_path.map_or(false, |changed_local_path| { + !path.starts_with(changed_local_path) + }) { + continue; + } + + setting_value.set_local_value( + path.clone(), + setting_value.load_setting( + &default_settings, + &user_settings_stack, + cx, + )?, + ); } } - user_values_stack.push(&*deserialized_value); - - // Load the local value for the field. - setting_value.set_local_value( - path.clone(), - setting_value.load_setting(default_deserialized_value, &user_values_stack, cx), - ); } } - } - - /// Deserialize the given JSON string into a map keyed by setting type. - /// - /// Returns an error if the string doesn't contain a valid JSON object. - fn load_setting_map(&self, json: &str) -> Result { - let mut map = DeserializedSettingMap { - untyped: parse_json_with_comments(json)?, - typed: HashMap::default(), - }; - for (setting_type_id, setting_value) in self.setting_values.iter() { - Self::load_setting_in_map(*setting_type_id, setting_value, &mut map); - } - Ok(map) - } - - fn load_setting_in_map( - setting_type_id: TypeId, - setting_value: &Box, - map: &mut DeserializedSettingMap, - ) { - let value = if let Some(setting_key) = setting_value.key() { - if let Some(value) = map.untyped.get(setting_key) { - value - } else { - return; - } - } else { - &map.untyped - }; - - if let Some(deserialized_value) = setting_value.deserialize_setting(&value).log_err() { - map.typed.insert(setting_type_id, deserialized_value); - } + Ok(()) } } @@ -567,18 +503,21 @@ impl AnySettingValue for SettingValue { fn load_setting( &self, default_value: &DeserializedSetting, - user_values: &[&DeserializedSetting], + user_values: &[DeserializedSetting], cx: &AppContext, - ) -> Box { + ) -> Result> { let default_value = default_value.0.downcast_ref::().unwrap(); let values: SmallVec<[&T::FileContent; 6]> = user_values .iter() .map(|value| value.0.downcast_ref().unwrap()) .collect(); - Box::new(T::load(default_value, &values, cx)) + Ok(Box::new(T::load(default_value, &values, cx)?)) } - fn deserialize_setting(&self, json: &serde_json::Value) -> Result { + fn deserialize_setting(&self, mut json: &serde_json::Value) -> Result { + if let Some(key) = T::KEY { + json = json.get(key).unwrap_or(&serde_json::Value::Null); + } let value = T::FileContent::deserialize(json)?; Ok(DeserializedSetting(Box::new(value))) } @@ -593,7 +532,7 @@ impl AnySettingValue for SettingValue { } self.global_value .as_ref() - .expect("no default value for setting") + .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name())) } fn set_global_value(&mut self, value: Box) { @@ -634,8 +573,7 @@ impl AnySettingValue for SettingValue { // } fn update_value_in_json_text<'a>( - text: &str, - syntax_tree: &tree_sitter::Tree, + text: &mut String, key_path: &mut Vec<&'a str>, tab_size: usize, old_value: &'a serde_json::Value, @@ -653,7 +591,6 @@ fn update_value_in_json_text<'a>( let new_sub_value = new_object.get(key).unwrap_or(&serde_json::Value::Null); update_value_in_json_text( text, - syntax_tree, key_path, tab_size, old_sub_value, @@ -667,7 +604,6 @@ fn update_value_in_json_text<'a>( if !old_object.contains_key(key) { update_value_in_json_text( text, - syntax_tree, key_path, tab_size, &serde_json::Value::Null, @@ -679,14 +615,14 @@ fn update_value_in_json_text<'a>( } } else if old_value != new_value { let (range, replacement) = - replace_value_in_json_text(text, syntax_tree, &key_path, tab_size, &new_value); + replace_value_in_json_text(text, &key_path, tab_size, &new_value); + text.replace_range(range.clone(), &replacement); edits.push((range, replacement)); } } fn replace_value_in_json_text( text: &str, - syntax_tree: &tree_sitter::Tree, key_path: &[&str], tab_size: usize, new_value: impl Serialize, @@ -702,6 +638,10 @@ fn replace_value_in_json_text( .unwrap(); } + let mut parser = tree_sitter::Parser::new(); + parser.set_language(tree_sitter_json::language()).unwrap(); + let syntax_tree = parser.parse(text, None).unwrap(); + let mut cursor = tree_sitter::QueryCursor::new(); let has_language_overrides = text.contains(LANGUAGE_OVERRIDES); @@ -1152,7 +1092,7 @@ mod tests { store.set_user_settings(&old_json, cx).ok(); let edits = store.edits_for_update::(&old_json, update); let mut new_json = old_json; - for (range, replacement) in edits.into_iter().rev() { + for (range, replacement) in edits.into_iter() { new_json.replace_range(range, &replacement); } pretty_assertions::assert_eq!(new_json, expected_new_json); @@ -1180,7 +1120,7 @@ mod tests { default_value: &UserSettingsJson, user_values: &[&UserSettingsJson], _: &AppContext, - ) -> Self { + ) -> Result { Self::load_via_json_merge(default_value, user_values) } } @@ -1196,7 +1136,7 @@ mod tests { default_value: &Option, user_values: &[&Option], _: &AppContext, - ) -> Self { + ) -> Result { Self::load_via_json_merge(default_value, user_values) } } @@ -1224,7 +1164,7 @@ mod tests { default_value: &MultiKeySettingsJson, user_values: &[&MultiKeySettingsJson], _: &AppContext, - ) -> Self { + ) -> Result { Self::load_via_json_merge(default_value, user_values) } } @@ -1257,7 +1197,7 @@ mod tests { default_value: &JournalSettingsJson, user_values: &[&JournalSettingsJson], _: &AppContext, - ) -> Self { + ) -> Result { Self::load_via_json_merge(default_value, user_values) } } @@ -1278,7 +1218,7 @@ mod tests { type FileContent = Self; - fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Self { + fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Result { Self::load_via_json_merge(default_value, user_values) } } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index bf4fc46ee3..cc4580daa1 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -159,7 +159,7 @@ impl settings::Setting for TerminalSettings { default_value: &Self::FileContent, user_values: &[&Self::FileContent], _: &AppContext, - ) -> Self { + ) -> Result { Self::load_via_json_merge(default_value, user_values) } } diff --git a/crates/vim/Cargo.toml b/crates/vim/Cargo.toml index 5f7cf0a5a3..c34a5b469b 100644 --- a/crates/vim/Cargo.toml +++ b/crates/vim/Cargo.toml @@ -12,6 +12,7 @@ doctest = false neovim = ["nvim-rs", "async-compat", "async-trait", "tokio"] [dependencies] +anyhow.workspace = true serde.workspace = true serde_derive.workspace = true itertools = "0.10" diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index ac86a08235..9ac40831d1 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -17,14 +17,16 @@ pub struct VimTestContext<'a> { impl<'a> VimTestContext<'a> { pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> { let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await; + cx.update(|cx| { search::init(cx); crate::init(cx); + }); + cx.update(|cx| { cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::(cx, |s| *s = Some(enabled)); }); - settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap(); }); diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 65c46e81cf..23a28b3544 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -10,8 +10,7 @@ mod state; mod utils; mod visual; -use std::sync::Arc; - +use anyhow::Result; use collections::CommandPaletteFilter; use editor::{Bias, Cancel, Editor, EditorMode, Event}; use gpui::{ @@ -24,6 +23,7 @@ use normal::normal_replace; use serde::Deserialize; use settings::{Setting, SettingsStore}; use state::{Mode, Operator, VimState}; +use std::sync::Arc; use visual::visual_replace; use workspace::{self, Workspace}; @@ -343,14 +343,10 @@ impl Setting for VimModeSetting { default_value: &Self::FileContent, user_values: &[&Self::FileContent], _: &AppContext, - ) -> Self { - Self( - user_values - .first() - .map(|e| **e) - .flatten() - .unwrap_or(default_value.unwrap()), - ) + ) -> Result { + Ok(Self(user_values.iter().rev().find_map(|v| **v).unwrap_or( + default_value.ok_or_else(Self::missing_default)?, + ))) } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 0e3ce2a724..768142d729 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -369,8 +369,12 @@ pub struct AppState { impl AppState { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut AppContext) -> Arc { - cx.set_global(settings::SettingsStore::test(cx)); - cx.set_global(Settings::test(cx)); + use settings::SettingsStore; + + if !cx.has_global::() { + cx.set_global(SettingsStore::test(cx)); + cx.set_global(Settings::test(cx)); + } let fs = fs::FakeFs::new(cx.background().clone()); let languages = Arc::new(LanguageRegistry::test()); diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 84c5798b07..7e4ddcef19 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -249,16 +249,21 @@ impl super::LspAdapter for CLspAdapter { #[cfg(test)] mod tests { use gpui::TestAppContext; - use language::{AutoindentMode, Buffer}; - use settings::Settings; + use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer}; + use settings::SettingsStore; + use std::num::NonZeroU32; #[gpui::test] async fn test_c_autoindent(cx: &mut TestAppContext) { cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.editor_overrides.tab_size = Some(2.try_into().unwrap()); - cx.set_global(settings); + cx.set_global(SettingsStore::test(cx)); + language::init(cx); + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, |s| { + s.defaults.tab_size = NonZeroU32::new(2); + }); + }); }); let language = crate::languages::language("c", tree_sitter_c::language(), None).await; diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index acd31e8205..7aaddf5fe8 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -170,8 +170,9 @@ impl LspAdapter for PythonLspAdapter { #[cfg(test)] mod tests { use gpui::{ModelContext, TestAppContext}; - use language::{AutoindentMode, Buffer}; - use settings::Settings; + use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer}; + use settings::SettingsStore; + use std::num::NonZeroU32; #[gpui::test] async fn test_python_autoindent(cx: &mut TestAppContext) { @@ -179,9 +180,13 @@ mod tests { let language = crate::languages::language("python", tree_sitter_python::language(), None).await; cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.editor_overrides.tab_size = Some(2.try_into().unwrap()); - cx.set_global(settings); + cx.set_global(SettingsStore::test(cx)); + language::init(cx); + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, |s| { + s.defaults.tab_size = NonZeroU32::new(2); + }); + }); }); cx.add_model(|cx| { diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 92fb5bc3b2..15700ec80a 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -253,10 +253,13 @@ impl LspAdapter for RustLspAdapter { #[cfg(test)] mod tests { + use std::num::NonZeroU32; + use super::*; use crate::languages::language; use gpui::{color::Color, TestAppContext}; - use settings::Settings; + use language::language_settings::AllLanguageSettings; + use settings::SettingsStore; use theme::SyntaxTheme; #[gpui::test] @@ -435,9 +438,13 @@ mod tests { async fn test_rust_autoindent(cx: &mut TestAppContext) { cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); cx.update(|cx| { - let mut settings = Settings::test(cx); - settings.editor_overrides.tab_size = Some(2.try_into().unwrap()); - cx.set_global(settings); + cx.set_global(SettingsStore::test(cx)); + language::init(cx); + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, |s| { + s.defaults.tab_size = NonZeroU32::new(2); + }); + }); }); let language = crate::languages::language("rust", tree_sitter_rust::language(), None).await; diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index fed76cd5b9..b62168775c 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -2,10 +2,11 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::AppContext; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; +use language::{ + language_settings::language_settings, LanguageServerBinary, LanguageServerName, LspAdapter, +}; use node_runtime::NodeRuntime; use serde_json::Value; -use settings::Settings; use smol::fs; use std::{ any::Any, @@ -100,14 +101,13 @@ impl LspAdapter for YamlLspAdapter { } fn workspace_configuration(&self, cx: &mut AppContext) -> Option> { - let settings = cx.global::(); Some( future::ready(serde_json::json!({ "yaml": { "keyOrdering": false }, "[yaml]": { - "editor.tabSize": settings.tab_size(Some("YAML")) + "editor.tabSize": language_settings(None, Some("YAML"), cx).tab_size, } })) .boxed(), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 1398369e75..b8ffab4a3b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -597,7 +597,7 @@ mod tests { #[gpui::test] async fn test_open_paths_action(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -697,7 +697,7 @@ mod tests { #[gpui::test] async fn test_window_edit_state(executor: Arc, cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -777,7 +777,7 @@ mod tests { #[gpui::test] async fn test_new_empty_workspace(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); cx.update(|cx| { open_new(&app_state, cx, |workspace, cx| { Editor::new_file(workspace, &Default::default(), cx) @@ -816,7 +816,7 @@ mod tests { #[gpui::test] async fn test_open_entry(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -929,7 +929,7 @@ mod tests { #[gpui::test] async fn test_open_paths(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state .fs @@ -1099,7 +1099,7 @@ mod tests { #[gpui::test] async fn test_save_conflicting_item(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -1143,7 +1143,7 @@ mod tests { #[gpui::test] async fn test_open_and_save_new_file(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state.fs.create_dir(Path::new("/root")).await.unwrap(); let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; @@ -1232,7 +1232,7 @@ mod tests { #[gpui::test] async fn test_setting_language_when_saving_as_single_file_worktree(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state.fs.create_dir(Path::new("/root")).await.unwrap(); let project = Project::test(app_state.fs.clone(), [], cx).await; @@ -1271,7 +1271,7 @@ mod tests { #[gpui::test] async fn test_pane_actions(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -1345,7 +1345,7 @@ mod tests { #[gpui::test] async fn test_navigation(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -1622,7 +1622,7 @@ mod tests { #[gpui::test] async fn test_reopening_closed_items(cx: &mut TestAppContext) { - let app_state = init(cx); + let app_state = init_test(cx); app_state .fs .as_fake() @@ -1843,7 +1843,7 @@ mod tests { cx.foreground().run_until_parked(); } - fn init(cx: &mut TestAppContext) -> Arc { + fn init_test(cx: &mut TestAppContext) -> Arc { cx.foreground().forbid_parking(); cx.update(|cx| { let mut app_state = AppState::test(cx); @@ -1852,6 +1852,7 @@ mod tests { state.build_window_options = build_window_options; call::init(app_state.client.clone(), app_state.user_store.clone(), cx); workspace::init(app_state.clone(), cx); + language::init(cx); editor::init(cx); pane::init(cx); app_state From 65e3713d4d375a64df3c0584bc3e9ba49b3d521f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 16 May 2023 17:29:21 -0700 Subject: [PATCH 112/168] Define editor settings in editor crate --- Cargo.lock | 1 + crates/client/src/client.rs | 8 +++- crates/collab/src/tests.rs | 10 ++--- crates/collab/src/tests/integration_tests.rs | 45 +++++++++++--------- crates/editor/src/blink_manager.rs | 6 +-- crates/editor/src/editor.rs | 9 +++- crates/editor/src/editor_settings.rs | 34 +++++++++++++++ crates/editor/src/editor_tests.rs | 6 ++- crates/editor/src/hover_popover.rs | 6 +-- crates/project_panel/Cargo.toml | 1 + crates/project_panel/src/project_panel.rs | 1 + crates/search/src/project_search.rs | 2 + crates/settings/src/settings.rs | 18 -------- 13 files changed, 93 insertions(+), 54 deletions(-) create mode 100644 crates/editor/src/editor_settings.rs diff --git a/Cargo.lock b/Cargo.lock index f01022e294..794f5ffd8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4873,6 +4873,7 @@ dependencies = [ name = "project_panel" version = "0.1.0" dependencies = [ + "client", "context_menu", "drag_and_drop", "editor", diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index f1aee9540e..041126271d 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -70,10 +70,14 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5); actions!(client, [SignIn, SignOut]); -pub fn init(client: &Arc, cx: &mut AppContext) { - let client = Arc::downgrade(client); +pub fn init_settings(cx: &mut AppContext) { settings::register_setting::(cx); +} +pub fn init(client: &Arc, cx: &mut AppContext) { + init_settings(cx); + + let client = Arc::downgrade(client); cx.add_global_action({ let client = client.clone(); move |_: &SignIn, cx| { diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 02e5fa56e4..a69a08fedf 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -186,11 +186,6 @@ impl TestServer { }) }); - cx.update(|cx| { - client::init(&client, cx); - language::init(cx); - }); - let fs = FakeFs::new(cx.background()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx)); let app_state = Arc::new(workspace::AppState { @@ -205,8 +200,11 @@ impl TestServer { background_actions: || &[], }); - Project::init(&client); cx.update(|cx| { + Project::init(&client); + client::init(&client, cx); + language::init(cx); + editor::init_settings(cx); workspace::init(app_state.clone(), cx); call::init(client.clone(), user_store.clone(), cx); }); diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 2896c47808..5d1a915887 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -27,7 +27,7 @@ use lsp::LanguageServerId; use project::{search::SearchQuery, DiagnosticSummary, HoverBlockKind, Project, ProjectPath}; use rand::prelude::*; use serde_json::json; -use settings::{SettingsStore}; +use settings::SettingsStore; use std::{ cell::{Cell, RefCell}, env, future, mem, @@ -1439,7 +1439,6 @@ async fn test_host_disconnect( cx_b: &mut TestAppContext, cx_c: &mut TestAppContext, ) { - cx_b.update(editor::init); deterministic.forbid_parking(); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; @@ -1449,6 +1448,8 @@ async fn test_host_disconnect( .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) .await; + cx_b.update(editor::init); + client_a .fs .insert_tree( @@ -1546,7 +1547,6 @@ async fn test_project_reconnect( cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, ) { - cx_b.update(editor::init); deterministic.forbid_parking(); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; @@ -1555,6 +1555,8 @@ async fn test_project_reconnect( .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) .await; + cx_b.update(editor::init); + client_a .fs .insert_tree( @@ -4992,7 +4994,6 @@ async fn test_collaborating_with_code_actions( cx_b: &mut TestAppContext, ) { deterministic.forbid_parking(); - cx_b.update(editor::init); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -5001,6 +5002,8 @@ async fn test_collaborating_with_code_actions( .await; let active_call_a = cx_a.read(ActiveCall::global); + cx_b.update(editor::init); + // Set up a fake language server. let mut language = Language::new( LanguageConfig { @@ -5205,7 +5208,6 @@ async fn test_collaborating_with_renames( cx_b: &mut TestAppContext, ) { deterministic.forbid_parking(); - cx_b.update(editor::init); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -5214,6 +5216,8 @@ async fn test_collaborating_with_renames( .await; let active_call_a = cx_a.read(ActiveCall::global); + cx_b.update(editor::init); + // Set up a fake language server. let mut language = Language::new( LanguageConfig { @@ -5395,8 +5399,6 @@ async fn test_language_server_statuses( cx_b: &mut TestAppContext, ) { deterministic.forbid_parking(); - - cx_b.update(editor::init); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -5405,6 +5407,8 @@ async fn test_language_server_statuses( .await; let active_call_a = cx_a.read(ActiveCall::global); + cx_b.update(editor::init); + // Set up a fake language server. let mut language = Language::new( LanguageConfig { @@ -6112,8 +6116,6 @@ async fn test_basic_following( cx_d: &mut TestAppContext, ) { deterministic.forbid_parking(); - cx_a.update(editor::init); - cx_b.update(editor::init); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; @@ -6131,6 +6133,9 @@ async fn test_basic_following( let active_call_a = cx_a.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global); + cx_a.update(editor::init); + cx_b.update(editor::init); + client_a .fs .insert_tree( @@ -6709,9 +6714,6 @@ async fn test_following_tab_order( cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, ) { - cx_a.update(editor::init); - cx_b.update(editor::init); - let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -6721,6 +6723,9 @@ async fn test_following_tab_order( let active_call_a = cx_a.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global); + cx_a.update(editor::init); + cx_b.update(editor::init); + client_a .fs .insert_tree( @@ -6831,9 +6836,6 @@ async fn test_peers_following_each_other( cx_b: &mut TestAppContext, ) { deterministic.forbid_parking(); - cx_a.update(editor::init); - cx_b.update(editor::init); - let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -6843,6 +6845,9 @@ async fn test_peers_following_each_other( let active_call_a = cx_a.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global); + cx_a.update(editor::init); + cx_b.update(editor::init); + // Client A shares a project. client_a .fs @@ -7002,8 +7007,6 @@ async fn test_auto_unfollowing( cx_b: &mut TestAppContext, ) { deterministic.forbid_parking(); - cx_a.update(editor::init); - cx_b.update(editor::init); // 2 clients connect to a server. let mut server = TestServer::start(&deterministic).await; @@ -7015,6 +7018,9 @@ async fn test_auto_unfollowing( let active_call_a = cx_a.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global); + cx_a.update(editor::init); + cx_b.update(editor::init); + // Client A shares a project. client_a .fs @@ -7169,8 +7175,6 @@ async fn test_peers_simultaneously_following_each_other( cx_b: &mut TestAppContext, ) { deterministic.forbid_parking(); - cx_a.update(editor::init); - cx_b.update(editor::init); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; @@ -7180,6 +7184,9 @@ async fn test_peers_simultaneously_following_each_other( .await; let active_call_a = cx_a.read(ActiveCall::global); + cx_a.update(editor::init); + cx_b.update(editor::init); + client_a.fs.insert_tree("/a", json!({})).await; let (project_a, _) = client_a.build_local_project("/a", cx_a).await; let workspace_a = client_a.build_workspace(&project_a, cx_a); diff --git a/crates/editor/src/blink_manager.rs b/crates/editor/src/blink_manager.rs index 409b6f9b03..303d0960ed 100644 --- a/crates/editor/src/blink_manager.rs +++ b/crates/editor/src/blink_manager.rs @@ -1,8 +1,8 @@ -use std::time::Duration; - +use crate::EditorSettings; use gpui::{Entity, ModelContext}; use settings::Settings; use smol::Timer; +use std::time::Duration; pub struct BlinkManager { blink_interval: Duration, @@ -64,7 +64,7 @@ impl BlinkManager { } fn blink_cursors(&mut self, epoch: usize, cx: &mut ModelContext) { - if cx.global::().cursor_blink { + if settings::get_setting::(None, cx).cursor_blink { if epoch == self.blink_epoch && self.enabled && !self.blinking_paused { self.visible = !self.visible; cx.notify(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 41087c6eaa..791f67be29 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1,5 +1,6 @@ mod blink_manager; pub mod display_map; +mod editor_settings; mod element; mod git; @@ -28,6 +29,7 @@ use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use copilot::Copilot; pub use display_map::DisplayPoint; use display_map::*; +pub use editor_settings::EditorSettings; pub use element::*; use futures::FutureExt; use fuzzy::{StringMatch, StringMatchCandidate}; @@ -287,7 +289,12 @@ pub enum Direction { Next, } +pub fn init_settings(cx: &mut AppContext) { + settings::register_setting::(cx); +} + pub fn init(cx: &mut AppContext) { + init_settings(cx); cx.add_action(Editor::new_file); cx.add_action(Editor::cancel); cx.add_action(Editor::newline); @@ -2354,7 +2361,7 @@ impl Editor { } fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext) { - if !cx.global::().show_completions_on_input { + if !settings::get_setting::(None, cx).show_completions_on_input { return; } diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs new file mode 100644 index 0000000000..ed100d0a01 --- /dev/null +++ b/crates/editor/src/editor_settings.rs @@ -0,0 +1,34 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::Setting; + +#[derive(Deserialize)] +pub struct EditorSettings { + pub cursor_blink: bool, + pub hover_popover_enabled: bool, + pub show_completions_on_input: bool, +} + +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +pub struct EditorSettingsContent { + pub cursor_blink: Option, + pub hover_popover_enabled: Option, + pub show_completions_on_input: Option, +} + +impl Setting for EditorSettings { + const KEY: Option<&'static str> = None; + + type FileContent = EditorSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result + where + Self: Sized, + { + Self::load_via_json_merge(default_value, user_values) + } +} diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 7ee6a52955..6f40b157a1 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -4697,8 +4697,10 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { apply_additional_edits.await.unwrap(); cx.update(|cx| { - cx.update_global::(|settings, _| { - settings.show_completions_on_input = false; + cx.update_global::(|settings, cx| { + settings.update_user_settings::(cx, |settings| { + settings.show_completions_on_input = Some(false); + }); }) }); cx.set_state("editorˇ"); diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index c40cb0fbe3..89626c3ea4 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,6 +1,6 @@ use crate::{ - display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSnapshot, - EditorStyle, RangeToAnchorExt, + display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, + EditorSnapshot, EditorStyle, RangeToAnchorExt, }; use futures::FutureExt; use gpui::{ @@ -38,7 +38,7 @@ pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext) { /// The internal hover action dispatches between `show_hover` or `hide_hover` /// depending on whether a point to hover over is provided. pub fn hover_at(editor: &mut Editor, point: Option, cx: &mut ViewContext) { - if cx.global::().hover_popover_enabled { + if settings::get_setting::(None, cx).hover_popover_enabled { if let Some(point) = point { show_hover(editor, point, false, cx); } else { diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index d52ec403fb..6fcdf06d2c 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -24,6 +24,7 @@ futures.workspace = true unicase = "2.6" [dev-dependencies] +client = { path = "../client", features = ["test-support"] } language = { path = "../language", features = ["test-support"] } editor = { path = "../editor", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 97684d874a..d36536f334 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1935,6 +1935,7 @@ mod tests { cx.update(|cx| { cx.set_global(SettingsStore::test(cx)); language::init(cx); + editor::init_settings(cx); }); } } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 4dc947e5f6..e869efcd5c 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1281,11 +1281,13 @@ pub mod tests { cx.update(|cx| { cx.set_global(SettingsStore::test(cx)); cx.set_global(ActiveSearches::default()); + let mut settings = Settings::test(cx); settings.theme = Arc::new(theme); cx.set_global(settings); language::init(cx); + editor::init_settings(cx); }); } } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index f5e50aaffb..438dc0d02e 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -38,10 +38,7 @@ pub struct Settings { pub buffer_font_family: FamilyId, pub buffer_font_size: f32, pub active_pane_magnification: f32, - pub cursor_blink: bool, pub confirm_quit: bool, - pub hover_popover_enabled: bool, - pub show_completions_on_input: bool, pub show_call_status_icon: bool, pub autosave: Autosave, pub default_dock_anchor: DockAnchor, @@ -78,9 +75,6 @@ impl Setting for Settings { buffer_font_size: defaults.buffer_font_size.unwrap(), active_pane_magnification: defaults.active_pane_magnification.unwrap(), confirm_quit: defaults.confirm_quit.unwrap(), - cursor_blink: defaults.cursor_blink.unwrap(), - hover_popover_enabled: defaults.hover_popover_enabled.unwrap(), - show_completions_on_input: defaults.show_completions_on_input.unwrap(), show_call_status_icon: defaults.show_call_status_icon.unwrap(), autosave: defaults.autosave.unwrap(), default_dock_anchor: defaults.default_dock_anchor.unwrap(), @@ -341,9 +335,6 @@ impl Settings { buffer_font_size: defaults.buffer_font_size.unwrap(), active_pane_magnification: defaults.active_pane_magnification.unwrap(), confirm_quit: defaults.confirm_quit.unwrap(), - cursor_blink: defaults.cursor_blink.unwrap(), - hover_popover_enabled: defaults.hover_popover_enabled.unwrap(), - show_completions_on_input: defaults.show_completions_on_input.unwrap(), show_call_status_icon: defaults.show_call_status_icon.unwrap(), autosave: defaults.autosave.unwrap(), default_dock_anchor: defaults.default_dock_anchor.unwrap(), @@ -391,13 +382,7 @@ impl Settings { &mut self.active_pane_magnification, data.active_pane_magnification, ); - merge(&mut self.cursor_blink, data.cursor_blink); merge(&mut self.confirm_quit, data.confirm_quit); - merge(&mut self.hover_popover_enabled, data.hover_popover_enabled); - merge( - &mut self.show_completions_on_input, - data.show_completions_on_input, - ); merge(&mut self.autosave, data.autosave); merge(&mut self.default_dock_anchor, data.default_dock_anchor); merge(&mut self.base_keymap, data.base_keymap); @@ -426,9 +411,6 @@ impl Settings { buffer_font_size: 14., active_pane_magnification: 1., confirm_quit: false, - cursor_blink: true, - hover_popover_enabled: true, - show_completions_on_input: true, show_call_status_icon: true, autosave: Autosave::Off, default_dock_anchor: DockAnchor::Bottom, From cbd4771f107b457cb38ff4eb2e6c28465ccb4eda Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 16 May 2023 17:45:04 -0700 Subject: [PATCH 113/168] Define project settings in project crate --- Cargo.lock | 1 + Cargo.toml | 1 + crates/collab/Cargo.toml | 2 +- crates/collab/src/tests.rs | 2 +- crates/editor/src/editor_settings.rs | 5 +-- crates/editor/src/editor_tests.rs | 1 + .../src/test/editor_lsp_test_context.rs | 1 + crates/project/Cargo.toml | 3 +- crates/project/src/project.rs | 14 +++++++-- crates/project/src/project_settings.rs | 31 +++++++++++++++++++ crates/project/src/project_tests.rs | 1 + crates/project_symbols/src/project_symbols.rs | 1 + crates/settings/Cargo.toml | 2 +- crates/settings/src/settings.rs | 15 +-------- crates/theme/Cargo.toml | 2 +- crates/zed/Cargo.toml | 2 +- crates/zed/src/main.rs | 2 +- 17 files changed, 59 insertions(+), 27 deletions(-) create mode 100644 crates/project/src/project_settings.rs diff --git a/Cargo.lock b/Cargo.lock index 794f5ffd8b..e0f70b15c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4852,6 +4852,7 @@ dependencies = [ "rand 0.8.5", "regex", "rpc", + "schemars", "serde", "serde_derive", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 0a73b878f8..f14e1c7355 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,6 +94,7 @@ smol = { version = "1.2" } tempdir = { version = "0.3.7" } thiserror = { version = "1.0.29" } time = { version = "0.3", features = ["serde", "serde-well-known"] } +toml = { version = "0.5" } unindent = { version = "0.1.7" } [patch.crates-io] diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index a980fdc13e..84e58744b1 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -51,7 +51,7 @@ tokio = { version = "1", features = ["full"] } tokio-tungstenite = "0.17" tonic = "0.6" tower = "0.4" -toml = "0.5.8" +toml.workspace = true tracing = "0.1.34" tracing-log = "0.1.3" tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] } diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index a69a08fedf..f1593822af 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -201,7 +201,7 @@ impl TestServer { }); cx.update(|cx| { - Project::init(&client); + Project::init(&client, cx); client::init(&client, cx); language::init(cx); editor::init_settings(cx); diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index ed100d0a01..2cd1d30e9e 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -25,10 +25,7 @@ impl Setting for EditorSettings { default_value: &Self::FileContent, user_values: &[&Self::FileContent], _: &gpui::AppContext, - ) -> anyhow::Result - where - Self: Sized, - { + ) -> anyhow::Result { Self::load_via_json_merge(default_value, user_values) } } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 6f40b157a1..49e878947d 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -6684,6 +6684,7 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC cx.set_global(Settings::test(cx)); language::init(cx); crate::init(cx); + Project::init_settings(cx); }); update_test_settings(cx, f); diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index e9aaa3df1b..a5633a9e55 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -40,6 +40,7 @@ impl<'a> EditorLspTestContext<'a> { language::init(cx); crate::init(cx); pane::init(cx); + Project::init_settings(cx); }); let file_name = format!( diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 2b4892aab9..83af0afeb3 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -50,6 +50,7 @@ parking_lot.workspace = true postage.workspace = true rand.workspace = true regex.workspace = true +schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true @@ -57,7 +58,7 @@ sha2 = "0.10" similar = "1.3" smol.workspace = true thiserror.workspace = true -toml = "0.5" +toml.workspace = true itertools = "0.10" [dev-dependencies] diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5d0b0a8884..b8f55c1aa4 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1,6 +1,7 @@ mod ignore; mod lsp_command; mod lsp_glob_set; +mod project_settings; pub mod search; pub mod terminals; pub mod worktree; @@ -42,6 +43,7 @@ use lsp::{ use lsp_command::*; use lsp_glob_set::LspGlobSet; use postage::watch; +use project_settings::ProjectSettings; use rand::prelude::*; use search::SearchQuery; use serde::Serialize; @@ -385,7 +387,13 @@ impl FormatTrigger { } impl Project { - pub fn init(client: &Arc) { + pub fn init_settings(cx: &mut AppContext) { + settings::register_setting::(cx); + } + + pub fn init(client: &Arc, cx: &mut AppContext) { + Self::init_settings(cx); + client.add_model_message_handler(Self::handle_add_collaborator); client.add_model_message_handler(Self::handle_update_project_collaborator); client.add_model_message_handler(Self::handle_remove_collaborator); @@ -2206,7 +2214,9 @@ impl Project { None => continue, }; - let lsp = &cx.global::().lsp.get(&adapter.name.0); + let lsp = settings::get_setting::(None, cx) + .lsp + .get(&adapter.name.0); let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); let mut initialization_options = adapter.initialization_options.clone(); diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs new file mode 100644 index 0000000000..92e8cfcca7 --- /dev/null +++ b/crates/project/src/project_settings.rs @@ -0,0 +1,31 @@ +use collections::HashMap; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::Setting; +use std::sync::Arc; + +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +pub struct ProjectSettings { + #[serde(default)] + pub lsp: HashMap, LspSettings>, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct LspSettings { + pub initialization_options: Option, +} + +impl Setting for ProjectSettings { + const KEY: Option<&'static str> = None; + + type FileContent = Self; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result { + Self::load_via_json_merge(default_value, user_values) + } +} diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 1cbef629f3..e7b1a84924 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -3723,5 +3723,6 @@ fn init_test(cx: &mut gpui::TestAppContext) { cx.update(|cx| { cx.set_global(SettingsStore::test(cx)); language::init(cx); + Project::init_settings(cx); }); } diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 3176cad308..239a0f98f2 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -374,6 +374,7 @@ mod tests { cx.set_global(Settings::test(cx)); cx.set_global(SettingsStore::test(cx)); language::init(cx); + Project::init_settings(cx); }); } diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index ba9bc38b46..6d2b4ded91 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -32,7 +32,7 @@ serde.workspace = true serde_derive.workspace = true serde_json.workspace = true smallvec.workspace = true -toml = "0.5" +toml.workspace = true tree-sitter = "*" tree-sitter-json = "*" diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 438dc0d02e..b1f171d52e 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -19,7 +19,7 @@ use sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use std::{borrow::Cow, collections::HashMap, str, sync::Arc}; +use std::{borrow::Cow, str, sync::Arc}; use theme::{Theme, ThemeRegistry}; use util::ResultExt as _; @@ -44,7 +44,6 @@ pub struct Settings { pub default_dock_anchor: DockAnchor, pub git: GitSettings, pub git_overrides: GitSettings, - pub lsp: HashMap, LspSettings>, pub theme: Arc, pub base_keymap: BaseKeymap, } @@ -80,7 +79,6 @@ impl Setting for Settings { default_dock_anchor: defaults.default_dock_anchor.unwrap(), git: defaults.git.unwrap(), git_overrides: Default::default(), - lsp: defaults.lsp.clone(), theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), base_keymap: Default::default(), }; @@ -290,19 +288,11 @@ pub struct SettingsFileContent { #[serde(default)] pub git: Option, #[serde(default)] - pub lsp: HashMap, LspSettings>, - #[serde(default)] pub theme: Option, #[serde(default)] pub base_keymap: Option, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct LspSettings { - pub initialization_options: Option, -} - impl Settings { pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> { match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() { @@ -340,7 +330,6 @@ impl Settings { default_dock_anchor: defaults.default_dock_anchor.unwrap(), git: defaults.git.unwrap(), git_overrides: Default::default(), - lsp: defaults.lsp.clone(), theme: themes.get(&defaults.theme.unwrap()).unwrap(), base_keymap: Default::default(), } @@ -388,7 +377,6 @@ impl Settings { merge(&mut self.base_keymap, data.base_keymap); self.git_overrides = data.git.unwrap_or_default(); - self.lsp = data.lsp; } pub fn git_gutter(&self) -> GitGutter { @@ -416,7 +404,6 @@ impl Settings { default_dock_anchor: DockAnchor::Bottom, git: Default::default(), git_overrides: Default::default(), - lsp: Default::default(), theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), base_keymap: Default::default(), } diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index d37ac3465b..dedbf2dd6f 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -19,4 +19,4 @@ parking_lot.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true -toml = "0.5" +toml.workspace = true diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 70c71cc18e..d937ce7ffe 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -101,7 +101,7 @@ smol.workspace = true tempdir.workspace = true thiserror.workspace = true tiny_http = "0.8" -toml = "0.5" +toml.workspace = true tree-sitter = "0.20" tree-sitter-c = "0.20.1" tree-sitter-cpp = "0.20.0" diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 33de725e1b..9ce3db7c34 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -157,7 +157,7 @@ fn main() { cx.set_global(client.clone()); context_menu::init(cx); - project::Project::init(&client); + project::Project::init(&client, cx); client::init(&client, cx); command_palette::init(cx); editor::init(cx); From 6403bb86e11cdcbc2055bf704d8c891076deaed3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 16 May 2023 20:25:18 -0700 Subject: [PATCH 114/168] Define workspace settings in workspace crate --- Cargo.lock | 1 + .../collab_ui/src/sharing_status_indicator.rs | 6 +- crates/diagnostics/src/diagnostics.rs | 1 + crates/editor/src/editor_tests.rs | 1 + crates/editor/src/element.rs | 10 +- .../src/test/editor_lsp_test_context.rs | 1 + crates/file_finder/src/file_finder.rs | 1 + crates/project_panel/src/project_panel.rs | 1 + crates/project_symbols/src/project_symbols.rs | 1 + crates/search/src/project_search.rs | 1 + crates/settings/src/settings.rs | 128 +----------------- crates/terminal_view/src/terminal_view.rs | 47 +++---- crates/workspace/Cargo.toml | 1 + crates/workspace/src/dock.rs | 18 +-- crates/workspace/src/item.rs | 17 ++- crates/workspace/src/pane.rs | 38 ++++-- crates/workspace/src/pane_group.rs | 6 +- crates/workspace/src/persistence.rs | 9 +- crates/workspace/src/persistence/model.rs | 9 +- crates/workspace/src/workspace.rs | 92 ++++++++----- crates/workspace/src/workspace_settings.rs | 103 ++++++++++++++ crates/zed/src/zed.rs | 4 +- 22 files changed, 253 insertions(+), 243 deletions(-) create mode 100644 crates/workspace/src/workspace_settings.rs diff --git a/Cargo.lock b/Cargo.lock index e0f70b15c5..216103959c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8676,6 +8676,7 @@ dependencies = [ "parking_lot 0.11.2", "postage", "project", + "schemars", "serde", "serde_derive", "serde_json", diff --git a/crates/collab_ui/src/sharing_status_indicator.rs b/crates/collab_ui/src/sharing_status_indicator.rs index 9fbe57af65..8394036f39 100644 --- a/crates/collab_ui/src/sharing_status_indicator.rs +++ b/crates/collab_ui/src/sharing_status_indicator.rs @@ -6,7 +6,7 @@ use gpui::{ platform::{Appearance, MouseButton}, AnyElement, AppContext, Element, Entity, View, ViewContext, }; -use settings::Settings; +use workspace::WorkspaceSettings; pub fn init(cx: &mut AppContext) { let active_call = ActiveCall::global(cx); @@ -15,7 +15,9 @@ pub fn init(cx: &mut AppContext) { cx.observe(&active_call, move |call, cx| { if let Some(room) = call.read(cx).room() { if room.read(cx).is_screen_sharing() { - if status_indicator.is_none() && cx.global::().show_call_status_icon { + if status_indicator.is_none() + && settings::get_setting::(None, cx).show_call_status_icon + { status_indicator = Some(cx.add_status_bar_item(|_| SharingStatusIndicator)); } } else if let Some((window_id, _)) = status_indicator.take() { diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 62e03ca54f..f7746496aa 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -1499,6 +1499,7 @@ mod tests { cx.set_global(Settings::test(cx)); cx.set_global(SettingsStore::test(cx)); language::init(cx); + workspace::init_settings(cx); }); } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 49e878947d..cce10eb05e 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -6685,6 +6685,7 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC language::init(cx); crate::init(cx); Project::init_settings(cx); + workspace::init_settings(cx); }); update_test_settings(cx, f); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 8194d51b25..bc5998cc29 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -40,7 +40,7 @@ use language::{ Selection, }; use project::ProjectPath; -use settings::{GitGutter, Settings}; +use settings::Settings; use smallvec::SmallVec; use std::{ borrow::Cow, @@ -50,7 +50,7 @@ use std::{ ops::Range, sync::Arc, }; -use workspace::item::Item; +use workspace::{item::Item, GitGutterSetting, WorkspaceSettings}; enum FoldMarkers {} @@ -550,11 +550,11 @@ impl EditorElement { let scroll_top = scroll_position.y() * line_height; let show_gutter = matches!( - &cx.global::() - .git_overrides + settings::get_setting::(None, cx) + .git .git_gutter .unwrap_or_default(), - GitGutter::TrackedFiles + GitGutterSetting::TrackedFiles ); if show_gutter { diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index a5633a9e55..9f2ab4ffac 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -41,6 +41,7 @@ impl<'a> EditorLspTestContext<'a> { crate::init(cx); pane::init(cx); Project::init_settings(cx); + workspace::init_settings(cx); }); let file_name = format!( diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 7d5aff79d0..ad865ec2f7 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -658,6 +658,7 @@ mod tests { language::init(cx); super::init(cx); editor::init(cx); + workspace::init_settings(cx); state }) } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index d36536f334..fb82fbfdc4 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1936,6 +1936,7 @@ mod tests { cx.set_global(SettingsStore::test(cx)); language::init(cx); editor::init_settings(cx); + workspace::init_settings(cx); }); } } diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 239a0f98f2..74edd45c21 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -375,6 +375,7 @@ mod tests { cx.set_global(SettingsStore::test(cx)); language::init(cx); Project::init_settings(cx); + workspace::init_settings(cx); }); } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index e869efcd5c..7364431407 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1288,6 +1288,7 @@ pub mod tests { language::init(cx); editor::init_settings(cx); + workspace::init_settings(cx); }); } } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index b1f171d52e..cd63776f5f 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -3,7 +3,7 @@ mod keymap_file; mod settings_file; mod settings_store; -use anyhow::{bail, Result}; +use anyhow::Result; use gpui::{ font_cache::{FamilyId, FontCache}, fonts, AppContext, AssetSource, @@ -15,10 +15,6 @@ use schemars::{ }; use serde::{Deserialize, Serialize}; use serde_json::Value; -use sqlez::{ - bindable::{Bind, Column, StaticColumnCount}, - statement::Statement, -}; use std::{borrow::Cow, str, sync::Arc}; use theme::{Theme, ThemeRegistry}; use util::ResultExt as _; @@ -37,13 +33,6 @@ pub struct Settings { pub buffer_font_features: fonts::Features, pub buffer_font_family: FamilyId, pub buffer_font_size: f32, - pub active_pane_magnification: f32, - pub confirm_quit: bool, - pub show_call_status_icon: bool, - pub autosave: Autosave, - pub default_dock_anchor: DockAnchor, - pub git: GitSettings, - pub git_overrides: GitSettings, pub theme: Arc, pub base_keymap: BaseKeymap, } @@ -72,13 +61,6 @@ impl Setting for Settings { buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(), buffer_font_features, buffer_font_size: defaults.buffer_font_size.unwrap(), - active_pane_magnification: defaults.active_pane_magnification.unwrap(), - confirm_quit: defaults.confirm_quit.unwrap(), - show_call_status_icon: defaults.show_call_status_icon.unwrap(), - autosave: defaults.autosave.unwrap(), - default_dock_anchor: defaults.default_dock_anchor.unwrap(), - git: defaults.git.unwrap(), - git_overrides: Default::default(), theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), base_keymap: Default::default(), }; @@ -201,65 +183,6 @@ impl BaseKeymap { .unwrap_or_default() } } -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -pub struct GitSettings { - pub git_gutter: Option, - pub gutter_debounce: Option, -} - -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum GitGutter { - #[default] - TrackedFiles, - Hide, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum Autosave { - Off, - AfterDelay { milliseconds: u64 }, - OnFocusChange, - OnWindowChange, -} - -#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum DockAnchor { - #[default] - Bottom, - Right, - Expanded, -} - -impl StaticColumnCount for DockAnchor {} -impl Bind for DockAnchor { - fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { - match self { - DockAnchor::Bottom => "Bottom", - DockAnchor::Right => "Right", - DockAnchor::Expanded => "Expanded", - } - .bind(statement, start_index) - } -} - -impl Column for DockAnchor { - fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> { - String::column(statement, start_index).and_then(|(anchor_text, next_index)| { - Ok(( - match anchor_text.as_ref() { - "Bottom" => DockAnchor::Bottom, - "Right" => DockAnchor::Right, - "Expanded" => DockAnchor::Expanded, - _ => bail!("Stored dock anchor is incorrect"), - }, - next_index, - )) - }) - } -} #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct SettingsFileContent { @@ -270,24 +193,6 @@ pub struct SettingsFileContent { #[serde(default)] pub buffer_font_features: Option, #[serde(default)] - pub active_pane_magnification: Option, - #[serde(default)] - pub cursor_blink: Option, - #[serde(default)] - pub confirm_quit: Option, - #[serde(default)] - pub hover_popover_enabled: Option, - #[serde(default)] - pub show_completions_on_input: Option, - #[serde(default)] - pub show_call_status_icon: Option, - #[serde(default)] - pub autosave: Option, - #[serde(default)] - pub default_dock_anchor: Option, - #[serde(default)] - pub git: Option, - #[serde(default)] pub theme: Option, #[serde(default)] pub base_keymap: Option, @@ -323,13 +228,6 @@ impl Settings { buffer_font_family_name: defaults.buffer_font_family.unwrap(), buffer_font_features, buffer_font_size: defaults.buffer_font_size.unwrap(), - active_pane_magnification: defaults.active_pane_magnification.unwrap(), - confirm_quit: defaults.confirm_quit.unwrap(), - show_call_status_icon: defaults.show_call_status_icon.unwrap(), - autosave: defaults.autosave.unwrap(), - default_dock_anchor: defaults.default_dock_anchor.unwrap(), - git: defaults.git.unwrap(), - git_overrides: Default::default(), theme: themes.get(&defaults.theme.unwrap()).unwrap(), base_keymap: Default::default(), } @@ -367,24 +265,7 @@ impl Settings { } merge(&mut self.buffer_font_size, data.buffer_font_size); - merge( - &mut self.active_pane_magnification, - data.active_pane_magnification, - ); - merge(&mut self.confirm_quit, data.confirm_quit); - merge(&mut self.autosave, data.autosave); - merge(&mut self.default_dock_anchor, data.default_dock_anchor); merge(&mut self.base_keymap, data.base_keymap); - - self.git_overrides = data.git.unwrap_or_default(); - } - - pub fn git_gutter(&self) -> GitGutter { - self.git_overrides.git_gutter.unwrap_or_else(|| { - self.git - .git_gutter - .expect("git_gutter should be some by setting setup") - }) } #[cfg(any(test, feature = "test-support"))] @@ -397,13 +278,6 @@ impl Settings { .load_family(&["Monaco"], &Default::default()) .unwrap(), buffer_font_size: 14., - active_pane_magnification: 1., - confirm_quit: false, - show_call_status_icon: true, - autosave: Autosave::Off, - default_dock_anchor: DockAnchor::Bottom, - git: Default::default(), - git_overrides: Default::default(), theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), base_keymap: Default::default(), } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 1615387ca2..76bc9b4a38 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -787,22 +787,18 @@ fn get_path_from_wt(wt: &LocalWorktree) -> Option { #[cfg(test)] mod tests { - use super::*; use gpui::TestAppContext; use project::{Entry, Project, ProjectPath, Worktree}; + use std::path::Path; use workspace::AppState; - use std::path::Path; + // Working directory calculation tests - ///Working directory calculation tests - - ///No Worktrees in project -> home_dir() + // No Worktrees in project -> home_dir() #[gpui::test] async fn no_worktree(cx: &mut TestAppContext) { - //Setup variables - let (project, workspace) = blank_workspace(cx).await; - //Test + let (project, workspace) = init_test(cx).await; cx.read(|cx| { let workspace = workspace.read(cx); let active_entry = project.read(cx).active_entry(); @@ -818,14 +814,12 @@ mod tests { }); } - ///No active entry, but a worktree, worktree is a file -> home_dir() + // No active entry, but a worktree, worktree is a file -> home_dir() #[gpui::test] async fn no_active_entry_worktree_is_file(cx: &mut TestAppContext) { - //Setup variables + let (project, workspace) = init_test(cx).await; - let (project, workspace) = blank_workspace(cx).await; create_file_wt(project.clone(), "/root.txt", cx).await; - cx.read(|cx| { let workspace = workspace.read(cx); let active_entry = project.read(cx).active_entry(); @@ -841,14 +835,12 @@ mod tests { }); } - //No active entry, but a worktree, worktree is a folder -> worktree_folder + // No active entry, but a worktree, worktree is a folder -> worktree_folder #[gpui::test] async fn no_active_entry_worktree_is_dir(cx: &mut TestAppContext) { - //Setup variables - let (project, workspace) = blank_workspace(cx).await; - let (_wt, _entry) = create_folder_wt(project.clone(), "/root/", cx).await; + let (project, workspace) = init_test(cx).await; - //Test + let (_wt, _entry) = create_folder_wt(project.clone(), "/root/", cx).await; cx.update(|cx| { let workspace = workspace.read(cx); let active_entry = project.read(cx).active_entry(); @@ -863,17 +855,15 @@ mod tests { }); } - //Active entry with a work tree, worktree is a file -> home_dir() + // Active entry with a work tree, worktree is a file -> home_dir() #[gpui::test] async fn active_entry_worktree_is_file(cx: &mut TestAppContext) { - //Setup variables + let (project, workspace) = init_test(cx).await; - let (project, workspace) = blank_workspace(cx).await; let (_wt, _entry) = create_folder_wt(project.clone(), "/root1/", cx).await; let (wt2, entry2) = create_file_wt(project.clone(), "/root2.txt", cx).await; insert_active_entry_for(wt2, entry2, project.clone(), cx); - //Test cx.update(|cx| { let workspace = workspace.read(cx); let active_entry = project.read(cx).active_entry(); @@ -887,16 +877,15 @@ mod tests { }); } - //Active entry, with a worktree, worktree is a folder -> worktree_folder + // Active entry, with a worktree, worktree is a folder -> worktree_folder #[gpui::test] async fn active_entry_worktree_is_dir(cx: &mut TestAppContext) { - //Setup variables - let (project, workspace) = blank_workspace(cx).await; + let (project, workspace) = init_test(cx).await; + let (_wt, _entry) = create_folder_wt(project.clone(), "/root1/", cx).await; let (wt2, entry2) = create_folder_wt(project.clone(), "/root2/", cx).await; insert_active_entry_for(wt2, entry2, project.clone(), cx); - //Test cx.update(|cx| { let workspace = workspace.read(cx); let active_entry = project.read(cx).active_entry(); @@ -910,8 +899,8 @@ mod tests { }); } - ///Creates a worktree with 1 file: /root.txt - pub async fn blank_workspace( + /// Creates a worktree with 1 file: /root.txt + pub async fn init_test( cx: &mut TestAppContext, ) -> (ModelHandle, ViewHandle) { let params = cx.update(AppState::test); @@ -922,7 +911,7 @@ mod tests { (project, workspace) } - ///Creates a worktree with 1 folder: /root{suffix}/ + /// Creates a worktree with 1 folder: /root{suffix}/ async fn create_folder_wt( project: ModelHandle, path: impl AsRef, @@ -931,7 +920,7 @@ mod tests { create_wt(project, true, path, cx).await } - ///Creates a worktree with 1 file: /root{suffix}.txt + /// Creates a worktree with 1 file: /root{suffix}.txt async fn create_file_wt( project: ModelHandle, path: impl AsRef, diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 177dc0a292..26797e8d6c 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -45,6 +45,7 @@ lazy_static.workspace = true log.workspace = true parking_lot.workspace = true postage.workspace = true +schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 7efcb7f9d3..5116391976 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,5 +1,9 @@ mod toggle_dock_button; +use crate::{ + sidebar::SidebarSide, BackgroundActions, DockAnchor, ItemHandle, Pane, Workspace, + WorkspaceSettings, +}; use collections::HashMap; use gpui::{ actions, @@ -8,10 +12,7 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyElement, AppContext, Border, Element, SizeConstraint, ViewContext, ViewHandle, }; -use settings::{DockAnchor, Settings}; use theme::Theme; - -use crate::{sidebar::SidebarSide, BackgroundActions, ItemHandle, Pane, Workspace}; pub use toggle_dock_button::ToggleDockButton; actions!( @@ -171,7 +172,9 @@ impl Dock { background_actions: BackgroundActions, cx: &mut ViewContext, ) -> Self { - let position = DockPosition::Hidden(cx.global::().default_dock_anchor); + let position = DockPosition::Hidden( + settings::get_setting::(None, cx).default_dock_anchor, + ); let workspace = cx.weak_handle(); let pane = cx.add_view(|cx| Pane::new(workspace, Some(position.anchor()), background_actions, cx)); @@ -405,7 +408,6 @@ mod tests { use gpui::{AppContext, BorrowWindowContext, TestAppContext, ViewContext, WindowContext}; use project::{FakeFs, Project}; - use settings::Settings; use theme::ThemeRegistry; use super::*; @@ -417,6 +419,7 @@ mod tests { }, register_deserializable_item, sidebar::Sidebar, + tests::init_test, AppState, ItemHandle, Workspace, }; @@ -429,8 +432,7 @@ mod tests { #[gpui::test] async fn test_dock_workspace_infinite_loop(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); cx.update(|cx| { register_deserializable_item::(cx); @@ -598,7 +600,7 @@ mod tests { impl<'a> DockTestContext<'a> { pub async fn new(cx: &'a mut TestAppContext) -> DockTestContext<'a> { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); cx.update(|cx| init(cx)); diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 43c4544611..e8c10c6cd2 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -3,6 +3,7 @@ use crate::{ FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId, }; +use crate::{AutosaveSetting, WorkspaceSettings}; use anyhow::Result; use client::{proto, Client}; use gpui::{ @@ -10,7 +11,6 @@ use gpui::{ ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use project::{Project, ProjectEntryId, ProjectPath}; -use settings::{Autosave, Settings}; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, @@ -450,8 +450,11 @@ impl ItemHandle for ViewHandle { } ItemEvent::Edit => { - if let Autosave::AfterDelay { milliseconds } = - cx.global::().autosave + let settings = settings::get_setting::(None, cx); + let debounce_delay = settings.git.gutter_debounce; + + if let AutosaveSetting::AfterDelay { milliseconds } = + settings.autosave { let delay = Duration::from_millis(milliseconds); let item = item.clone(); @@ -460,9 +463,6 @@ impl ItemHandle for ViewHandle { }); } - let settings = cx.global::(); - let debounce_delay = settings.git_overrides.gutter_debounce; - let item = item.clone(); if let Some(delay) = debounce_delay { @@ -500,7 +500,10 @@ impl ItemHandle for ViewHandle { })); cx.observe_focus(self, move |workspace, item, focused, cx| { - if !focused && cx.global::().autosave == Autosave::OnFocusChange { + if !focused + && settings::get_setting::(None, cx).autosave + == AutosaveSetting::OnFocusChange + { Pane::autosave_item(&item, workspace.project.clone(), cx) .detach_and_log_err(cx); } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 96320a4baf..ca454d2c75 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -5,7 +5,8 @@ use crate::{ dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, Dock, ExpandDock}, item::WeakItemHandle, toolbar::Toolbar, - Item, NewFile, NewSearch, NewTerminal, Workspace, + AutosaveSetting, DockAnchor, Item, NewFile, NewSearch, NewTerminal, Workspace, + WorkspaceSettings, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -29,7 +30,7 @@ use gpui::{ }; use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; -use settings::{Autosave, DockAnchor, Settings}; +use settings::Settings; use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc}; use theme::Theme; use util::ResultExt; @@ -1024,8 +1025,8 @@ impl Pane { } else if is_dirty && (can_save || is_singleton) { let will_autosave = cx.read(|cx| { matches!( - cx.global::().autosave, - Autosave::OnFocusChange | Autosave::OnWindowChange + settings::get_setting::(None, cx).autosave, + AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange ) && Self::can_autosave_item(&*item, cx) }); let should_save = if should_prompt_for_save && !will_autosave { @@ -2087,10 +2088,11 @@ mod tests { use crate::item::test::{TestItem, TestProjectItem}; use gpui::{executor::Deterministic, TestAppContext}; use project::FakeFs; + use settings::SettingsStore; #[gpui::test] async fn test_remove_active_empty(cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2104,7 +2106,7 @@ mod tests { #[gpui::test] async fn test_add_item_with_new_item(cx: &mut TestAppContext) { cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2192,7 +2194,7 @@ mod tests { #[gpui::test] async fn test_add_item_with_existing_item(cx: &mut TestAppContext) { cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2268,7 +2270,7 @@ mod tests { #[gpui::test] async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) { cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2377,7 +2379,7 @@ mod tests { #[gpui::test] async fn test_remove_item_ordering(deterministic: Arc, cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2424,7 +2426,7 @@ mod tests { #[gpui::test] async fn test_close_inactive_items(deterministic: Arc, cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2443,7 +2445,7 @@ mod tests { #[gpui::test] async fn test_close_clean_items(deterministic: Arc, cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2470,7 +2472,7 @@ mod tests { deterministic: Arc, cx: &mut TestAppContext, ) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2492,7 +2494,7 @@ mod tests { deterministic: Arc, cx: &mut TestAppContext, ) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2511,7 +2513,7 @@ mod tests { #[gpui::test] async fn test_close_all_items(deterministic: Arc, cx: &mut TestAppContext) { - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -2531,6 +2533,14 @@ mod tests { assert_item_labels(&pane, [], cx); } + fn init_test(cx: &mut TestAppContext) { + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + cx.set_global(settings::Settings::test(cx)); + crate::init_settings(cx); + }); + } + fn add_labeled_item( workspace: &ViewHandle, pane: &ViewHandle, diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 55032b4bc1..edc7c617d0 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::{AppState, FollowerStatesByLeader, Pane, Workspace}; +use crate::{AppState, FollowerStatesByLeader, Pane, Workspace, WorkspaceSettings}; use anyhow::{anyhow, Result}; use call::{ActiveCall, ParticipantLocation}; use gpui::{ @@ -11,7 +11,6 @@ use gpui::{ }; use project::Project; use serde::Deserialize; -use settings::Settings; use theme::Theme; #[derive(Clone, Debug, Eq, PartialEq)] @@ -380,7 +379,8 @@ impl PaneAxis { .with_children(self.members.iter().enumerate().map(|(ix, member)| { let mut flex = 1.0; if member.contains(active_pane) { - flex = cx.global::().active_pane_magnification; + flex = settings::get_setting::(None, cx) + .active_pane_magnification; } let mut member = member.render( diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index d5d79c3ddd..4ffae0d7e3 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -497,13 +497,10 @@ impl WorkspaceDb { #[cfg(test)] mod tests { - - use std::sync::Arc; - - use db::open_test_db; - use settings::DockAnchor; - use super::*; + use crate::DockAnchor; + use db::open_test_db; + use std::sync::Arc; #[gpui::test] async fn test_next_id_stability() { diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index a92c369e7a..ac1bcf6eed 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -1,5 +1,6 @@ use crate::{ - dock::DockPosition, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, + dock::DockPosition, DockAnchor, ItemDeserializers, Member, Pane, PaneAxis, Workspace, + WorkspaceId, }; use anyhow::{anyhow, Context, Result}; use async_recursion::async_recursion; @@ -11,7 +12,6 @@ use gpui::{ platform::WindowBounds, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WeakViewHandle, }; use project::Project; -use settings::DockAnchor; use std::{ path::{Path, PathBuf}, sync::Arc, @@ -305,10 +305,9 @@ impl Column for DockPosition { #[cfg(test)] mod tests { - use db::sqlez::connection::Connection; - use settings::DockAnchor; - use super::WorkspaceLocation; + use crate::DockAnchor; + use db::sqlez::connection::Connection; #[test] fn test_workspace_round_trips() { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 768142d729..dcc4d017dd 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -13,6 +13,7 @@ pub mod shared_screen; pub mod sidebar; mod status_bar; mod toolbar; +mod workspace_settings; use anyhow::{anyhow, Context, Result}; use assets::Assets; @@ -75,7 +76,7 @@ pub use persistence::{ use postage::prelude::Stream; use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; -use settings::{Autosave, DockAnchor, Settings}; +use settings::Settings; use shared_screen::SharedScreen; use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem}; use status_bar::StatusBar; @@ -83,6 +84,7 @@ pub use status_bar::StatusItemView; use theme::{Theme, ThemeRegistry}; pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; use util::{paths, ResultExt}; +pub use workspace_settings::{AutosaveSetting, DockAnchor, GitGutterSetting, WorkspaceSettings}; lazy_static! { static ref ZED_WINDOW_SIZE: Option = env::var("ZED_WINDOW_SIZE") @@ -183,7 +185,12 @@ pub type WorkspaceId = i64; impl_actions!(workspace, [ActivatePane]); +pub fn init_settings(cx: &mut AppContext) { + settings::register_setting::(cx); +} + pub fn init(app_state: Arc, cx: &mut AppContext) { + init_settings(cx); pane::init(cx); dock::init(cx); notifications::init(cx); @@ -384,6 +391,7 @@ impl AppState { let themes = ThemeRegistry::new((), cx.font_cache().clone()); client::init(&client, cx); + crate::init_settings(cx); Arc::new(Self { client, @@ -672,7 +680,9 @@ impl Workspace { Self::load_from_serialized_workspace(weak_handle, serialized_workspace, cx) }); } else if project.read(cx).is_local() { - if cx.global::().default_dock_anchor != DockAnchor::Expanded { + if settings::get_setting::(None, cx).default_dock_anchor + != DockAnchor::Expanded + { Dock::show(&mut this, false, cx); } } @@ -2406,8 +2416,8 @@ impl Workspace { item.workspace_deactivated(cx); } if matches!( - cx.global::().autosave, - Autosave::OnWindowChange | Autosave::OnFocusChange + settings::get_setting::(None, cx).autosave, + AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange ) { for item in pane.items() { Pane::autosave_item(item.as_ref(), self.project.clone(), cx) @@ -3067,7 +3077,7 @@ pub fn join_remote_project( } pub fn restart(_: &Restart, cx: &mut AppContext) { - let should_confirm = cx.global::().confirm_quit; + let should_confirm = settings::get_setting::(None, cx).confirm_quit; cx.spawn(|mut cx| async move { let mut workspaces = cx .window_ids() @@ -3128,20 +3138,18 @@ fn parse_pixel_position_env_var(value: &str) -> Option { #[cfg(test)] mod tests { - use std::{cell::RefCell, rc::Rc}; - - use crate::item::test::{TestItem, TestItemEvent, TestProjectItem}; - use super::*; + use crate::item::test::{TestItem, TestItemEvent, TestProjectItem}; use fs::FakeFs; use gpui::{executor::Deterministic, TestAppContext}; use project::{Project, ProjectEntryId}; use serde_json::json; + use settings::SettingsStore; + use std::{cell::RefCell, rc::Rc}; #[gpui::test] async fn test_tab_disambiguation(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; @@ -3189,8 +3197,8 @@ mod tests { #[gpui::test] async fn test_tracking_active_path(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree( "/root1", @@ -3293,8 +3301,8 @@ mod tests { #[gpui::test] async fn test_close_window(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); fs.insert_tree("/root", json!({ "one": "" })).await; @@ -3329,8 +3337,8 @@ mod tests { #[gpui::test] async fn test_close_pane_items(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; @@ -3436,8 +3444,8 @@ mod tests { #[gpui::test] async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) { - cx.foreground().forbid_parking(); - Settings::test_async(cx); + init_test(cx); + let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; @@ -3542,9 +3550,8 @@ mod tests { #[gpui::test] async fn test_autosave(deterministic: Arc, cx: &mut gpui::TestAppContext) { - deterministic.forbid_parking(); + init_test(cx); - Settings::test_async(cx); let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; @@ -3560,8 +3567,10 @@ mod tests { // Autosave on window change. item.update(cx, |item, cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.autosave = Autosave::OnWindowChange; + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.autosave = Some(AutosaveSetting::OnWindowChange); + }) }); item.is_dirty = true; }); @@ -3574,8 +3583,10 @@ mod tests { // Autosave on focus change. item.update(cx, |item, cx| { cx.focus_self(); - cx.update_global(|settings: &mut Settings, _| { - settings.autosave = Autosave::OnFocusChange; + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.autosave = Some(AutosaveSetting::OnFocusChange); + }) }); item.is_dirty = true; }); @@ -3598,8 +3609,10 @@ mod tests { // Autosave after delay. item.update(cx, |item, cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.autosave = Autosave::AfterDelay { milliseconds: 500 }; + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 }); + }) }); item.is_dirty = true; cx.emit(TestItemEvent::Edit); @@ -3615,8 +3628,10 @@ mod tests { // Autosave on focus change, ensuring closing the tab counts as such. item.update(cx, |item, cx| { - cx.update_global(|settings: &mut Settings, _| { - settings.autosave = Autosave::OnFocusChange; + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings::(cx, |settings| { + settings.autosave = Some(AutosaveSetting::OnFocusChange); + }) }); item.is_dirty = true; }); @@ -3656,12 +3671,9 @@ mod tests { } #[gpui::test] - async fn test_pane_navigation( - deterministic: Arc, - cx: &mut gpui::TestAppContext, - ) { - deterministic.forbid_parking(); - Settings::test_async(cx); + async fn test_pane_navigation(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; @@ -3713,4 +3725,14 @@ mod tests { assert!(pane.can_navigate_forward()); }); } + + pub fn init_test(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + cx.set_global(Settings::test(cx)); + language::init(cx); + crate::init_settings(cx); + }); + } } diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs new file mode 100644 index 0000000000..41e4796491 --- /dev/null +++ b/crates/workspace/src/workspace_settings.rs @@ -0,0 +1,103 @@ +use anyhow::bail; +use db::sqlez::{ + bindable::{Bind, Column, StaticColumnCount}, + statement::Statement, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::Setting; + +#[derive(Deserialize)] +pub struct WorkspaceSettings { + pub active_pane_magnification: f32, + pub confirm_quit: bool, + pub show_call_status_icon: bool, + pub autosave: AutosaveSetting, + pub default_dock_anchor: DockAnchor, + pub git: GitSettings, +} + +#[derive(Clone, Serialize, Deserialize, JsonSchema)] +pub struct WorkspaceSettingsContent { + pub active_pane_magnification: Option, + pub confirm_quit: Option, + pub show_call_status_icon: Option, + pub autosave: Option, + pub default_dock_anchor: Option, + pub git: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AutosaveSetting { + Off, + AfterDelay { milliseconds: u64 }, + OnFocusChange, + OnWindowChange, +} + +#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum DockAnchor { + #[default] + Bottom, + Right, + Expanded, +} + +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct GitSettings { + pub git_gutter: Option, + pub gutter_debounce: Option, +} + +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum GitGutterSetting { + #[default] + TrackedFiles, + Hide, +} + +impl StaticColumnCount for DockAnchor {} + +impl Bind for DockAnchor { + fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { + match self { + DockAnchor::Bottom => "Bottom", + DockAnchor::Right => "Right", + DockAnchor::Expanded => "Expanded", + } + .bind(statement, start_index) + } +} + +impl Column for DockAnchor { + fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> { + String::column(statement, start_index).and_then(|(anchor_text, next_index)| { + Ok(( + match anchor_text.as_ref() { + "Bottom" => DockAnchor::Bottom, + "Right" => DockAnchor::Right, + "Expanded" => DockAnchor::Expanded, + _ => bail!("Stored dock anchor is incorrect"), + }, + next_index, + )) + }) + } +} + +impl Setting for WorkspaceSettings { + const KEY: Option<&'static str> = None; + + type FileContent = WorkspaceSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result { + Self::load_via_json_merge(default_value, user_values) + } +} diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b8ffab4a3b..923a3d6d9c 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -37,7 +37,7 @@ use uuid::Uuid; pub use workspace; use workspace::{ create_and_open_local_file, open_new, sidebar::SidebarSide, AppState, NewFile, NewWindow, - Workspace, + Workspace, WorkspaceSettings, }; #[derive(Deserialize, Clone, PartialEq)] @@ -367,7 +367,7 @@ pub fn build_window_options( } fn quit(_: &Quit, cx: &mut gpui::AppContext) { - let should_confirm = cx.global::().confirm_quit; + let should_confirm = settings::get_setting::(None, cx).confirm_quit; cx.spawn(|mut cx| async move { let mut workspaces = cx .window_ids() From d2ba18eae9d77774248007cf883361893785653a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 May 2023 09:26:32 -0700 Subject: [PATCH 115/168] Customize language settings JSON schema in language crate --- crates/language/src/language_settings.rs | 60 +++++++++++++++++++++++- crates/settings/src/settings.rs | 57 +++++----------------- crates/settings/src/settings_store.rs | 7 +-- crates/zed/src/main.rs | 1 + 4 files changed, 77 insertions(+), 48 deletions(-) diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 80cc81a4c3..55afd2f572 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -1,7 +1,10 @@ use anyhow::Result; use collections::HashMap; use gpui::AppContext; -use schemars::JsonSchema; +use schemars::{ + schema::{InstanceType, ObjectValidation, Schema, SchemaObject}, + JsonSchema, +}; use serde::{Deserialize, Serialize}; use std::{num::NonZeroU32, path::Path, sync::Arc}; @@ -247,6 +250,61 @@ impl settings::Setting for AllLanguageSettings { languages, }) } + + fn json_schema( + generator: &mut schemars::gen::SchemaGenerator, + params: &settings::SettingsJsonSchemaParams, + ) -> schemars::schema::RootSchema { + let mut root_schema = generator.root_schema_for::(); + + // Create a schema for a 'languages overrides' object, associating editor + // settings with specific langauges. + assert!(root_schema + .definitions + .contains_key("LanguageSettingsContent")); + + let languages_object_schema = SchemaObject { + instance_type: Some(InstanceType::Object.into()), + object: Some(Box::new(ObjectValidation { + properties: params + .language_names + .iter() + .map(|name| { + ( + name.clone(), + Schema::new_ref("#/definitions/LanguageSettingsContent".into()), + ) + }) + .collect(), + ..Default::default() + })), + ..Default::default() + }; + + root_schema + .definitions + .extend([("Languages".into(), languages_object_schema.into())]); + + root_schema + .schema + .object + .as_mut() + .unwrap() + .properties + .extend([ + ( + "languages".to_owned(), + Schema::new_ref("#/definitions/Languages".into()), + ), + // For backward compatibility + ( + "language_overrides".to_owned(), + Schema::new_ref("#/definitions/Languages".into()), + ), + ]); + + root_schema + } } fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index cd63776f5f..0e4cd4d922 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -10,7 +10,7 @@ use gpui::{ }; use schemars::{ gen::SchemaGenerator, - schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec}, + schema::{InstanceType, Schema, SchemaObject}, JsonSchema, }; use serde::{Deserialize, Serialize}; @@ -80,7 +80,7 @@ impl Setting for Settings { // Create a schema for a theme name. let theme_name_schema = SchemaObject { - instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))), + instance_type: Some(InstanceType::String.into()), enum_values: Some( params .theme_names @@ -92,51 +92,20 @@ impl Setting for Settings { ..Default::default() }; - // Create a schema for a 'languages overrides' object, associating editor - // settings with specific langauges. - assert!(root_schema.definitions.contains_key("EditorSettings")); + root_schema + .definitions + .extend([("ThemeName".into(), theme_name_schema.into())]); - let languages_object_schema = SchemaObject { - instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))), - object: Some(Box::new(ObjectValidation { - properties: params - .language_names - .iter() - .map(|name| { - ( - name.clone(), - Schema::new_ref("#/definitions/EditorSettings".into()), - ) - }) - .collect(), - ..Default::default() - })), - ..Default::default() - }; - - // Add these new schemas as definitions, and modify properties of the root - // schema to reference them. - root_schema.definitions.extend([ - ("ThemeName".into(), theme_name_schema.into()), - ("Languages".into(), languages_object_schema.into()), - ]); - let root_schema_object = &mut root_schema.schema.object.as_mut().unwrap(); - - root_schema_object.properties.extend([ - ( + root_schema + .schema + .object + .as_mut() + .unwrap() + .properties + .extend([( "theme".to_owned(), Schema::new_ref("#/definitions/ThemeName".into()), - ), - ( - "languages".to_owned(), - Schema::new_ref("#/definitions/Languages".into()), - ), - // For backward compatibility - ( - "language_overrides".to_owned(), - Schema::new_ref("#/definitions/Languages".into()), - ), - ]); + )]); root_schema } diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index c389a7bf84..cdc7659189 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -308,7 +308,8 @@ impl SettingsStore { default_settings_content: &str, cx: &AppContext, ) -> Result<()> { - self.default_deserialized_settings = Some(serde_json::from_str(default_settings_content)?); + self.default_deserialized_settings = + Some(parse_json_with_comments(default_settings_content)?); self.recompute_values(None, cx)?; Ok(()) } @@ -319,7 +320,7 @@ impl SettingsStore { user_settings_content: &str, cx: &AppContext, ) -> Result<()> { - self.user_deserialized_settings = Some(serde_json::from_str(user_settings_content)?); + self.user_deserialized_settings = Some(parse_json_with_comments(user_settings_content)?); self.recompute_values(None, cx)?; Ok(()) } @@ -333,7 +334,7 @@ impl SettingsStore { ) -> Result<()> { if let Some(content) = settings_content { self.local_deserialized_settings - .insert(path.clone(), serde_json::from_str(content)?); + .insert(path.clone(), parse_json_with_comments(content)?); } else { self.local_deserialized_settings.remove(&path); } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 9ce3db7c34..3466daebe9 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -160,6 +160,7 @@ fn main() { project::Project::init(&client, cx); client::init(&client, cx); command_palette::init(cx); + language::init(cx); editor::init(cx); go_to_line::init(cx); file_finder::init(cx); From cb0c613da29805a3a08d26f0e571c312e8db8edc Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 17 May 2023 12:39:08 -0400 Subject: [PATCH 116/168] v0.88.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 41c3eab821..f5b23fe5f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8544,7 +8544,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zed" -version = "0.87.0" +version = "0.88.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index b74057c907..ea9ee23971 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.87.0" +version = "0.88.0" publish = false [lib] From 9e5983305b4ec495886351e5d798084710503174 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 17 May 2023 13:38:54 -0400 Subject: [PATCH 117/168] Update font-kit to resolve panics when loading malformed fonts --- Cargo.lock | 64 ++++++++++++++++++++---------------------- crates/gpui/Cargo.toml | 2 +- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 41c3eab821..5664adfa46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1321,6 +1321,12 @@ dependencies = [ "crossbeam-utils 0.8.14", ] +[[package]] +name = "const-cstr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" + [[package]] name = "context_menu" version = "0.1.0" @@ -1930,6 +1936,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + [[package]] name = "dotenvy" version = "0.15.6" @@ -2107,16 +2122,6 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" -[[package]] -name = "expat-sys" -version = "2.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" -dependencies = [ - "cmake", - "pkg-config", -] - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -2252,8 +2257,8 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "font-kit" -version = "0.10.0" -source = "git+https://github.com/zed-industries/font-kit?rev=8eaf7a918eafa28b0a37dc759e2e0e7683fa24f1#8eaf7a918eafa28b0a37dc759e2e0e7683fa24f1" +version = "0.11.0" +source = "git+https://github.com/zed-industries/font-kit?rev=b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18#b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" dependencies = [ "bitflags", "byteorder", @@ -2269,9 +2274,9 @@ dependencies = [ "log", "pathfinder_geometry", "pathfinder_simd", - "servo-fontconfig", "walkdir", "winapi 0.3.9", + "yeslogic-fontconfig-sys", ] [[package]] @@ -5930,27 +5935,6 @@ dependencies = [ "yaml-rust", ] -[[package]] -name = "servo-fontconfig" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c" -dependencies = [ - "libc", - "servo-fontconfig-sys", -] - -[[package]] -name = "servo-fontconfig-sys" -version = "5.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388" -dependencies = [ - "expat-sys", - "freetype-sys", - "pkg-config", -] - [[package]] name = "settings" version = "0.1.0" @@ -8542,6 +8526,18 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yeslogic-fontconfig-sys" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2bbd69036d397ebbff671b1b8e4d918610c181c5a16073b96f984a38d08c386" +dependencies = [ + "const-cstr", + "dlib", + "once_cell", + "pkg-config", +] + [[package]] name = "zed" version = "0.87.0" diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 04862d1814..c1dc13084e 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -72,7 +72,7 @@ cocoa = "0.24" core-foundation = { version = "0.9.3", features = ["with-uuid"] } core-graphics = "0.22.3" core-text = "19.2" -font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "8eaf7a918eafa28b0a37dc759e2e0e7683fa24f1" } +font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" } foreign-types = "0.3" log.workspace = true metal = "0.21.0" From 5c729c0e56a57ee81b84f9e806855216dff09a8a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 May 2023 11:23:09 -0700 Subject: [PATCH 118/168] Define base keymap setting in welcome crate --- Cargo.lock | 2 + assets/settings/default.json | 9 + crates/settings/src/keymap_file.rs | 20 +- crates/settings/src/settings.rs | 49 ----- crates/settings/src/settings_file.rs | 218 +------------------- crates/vim/src/test/vim_test_context.rs | 2 +- crates/welcome/Cargo.toml | 7 +- crates/welcome/src/base_keymap_picker.rs | 11 +- crates/welcome/src/base_keymap_setting.rs | 65 ++++++ crates/welcome/src/welcome.rs | 10 +- crates/zed/src/main.rs | 7 +- crates/zed/src/zed.rs | 231 +++++++++++++++++++++- 12 files changed, 334 insertions(+), 297 deletions(-) create mode 100644 crates/welcome/src/base_keymap_setting.rs diff --git a/Cargo.lock b/Cargo.lock index c2e0790cf4..e5922f9e66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8353,6 +8353,8 @@ dependencies = [ "log", "picker", "project", + "schemars", + "serde", "settings", "theme", "theme_selector", diff --git a/assets/settings/default.json b/assets/settings/default.json index 20d07acae0..3a80a5d5af 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1,6 +1,15 @@ { // The name of the Zed theme to use for the UI "theme": "One Dark", + // The name of a base set of key bindings to use. + // This setting can take four values, each named after another + // text editor: + // + // 1. "VSCode" + // 2. "JetBrains" + // 3. "SublimeText" + // 4. "Atom" + "base_keymap": "VSCode", // Features that can be globally enabled or disabled "features": { // Show Copilot icon in status bar diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index e0b3f547f9..0b638da924 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -1,4 +1,4 @@ -use crate::{settings_store::parse_json_with_comments, Settings}; +use crate::settings_store::parse_json_with_comments; use anyhow::{Context, Result}; use assets::Assets; use collections::BTreeMap; @@ -41,20 +41,14 @@ impl JsonSchema for KeymapAction { struct ActionWithData(Box, Box); impl KeymapFileContent { - pub fn load_defaults(cx: &mut AppContext) { - for path in ["keymaps/default.json", "keymaps/vim.json"] { - Self::load(path, cx).unwrap(); - } - - if let Some(asset_path) = cx.global::().base_keymap.asset_path() { - Self::load(asset_path, cx).log_err(); - } - } - - pub fn load(asset_path: &str, cx: &mut AppContext) -> Result<()> { + pub fn load_asset(asset_path: &str, cx: &mut AppContext) -> Result<()> { let content = Assets::get(asset_path).unwrap().data; let content_str = std::str::from_utf8(content.as_ref()).unwrap(); - parse_json_with_comments::(content_str)?.add_to_cx(cx) + Self::parse(content_str)?.add_to_cx(cx) + } + + pub fn parse(content: &str) -> Result { + parse_json_with_comments::(content) } pub fn add_to_cx(self, cx: &mut AppContext) -> Result<()> { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 0e4cd4d922..bd1104105f 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -34,7 +34,6 @@ pub struct Settings { pub buffer_font_family: FamilyId, pub buffer_font_size: f32, pub theme: Arc, - pub base_keymap: BaseKeymap, } impl Setting for Settings { @@ -62,7 +61,6 @@ impl Setting for Settings { buffer_font_features, buffer_font_size: defaults.buffer_font_size.unwrap(), theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), - base_keymap: Default::default(), }; for value in user_values.into_iter().copied().cloned() { @@ -111,48 +109,6 @@ impl Setting for Settings { } } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] -pub enum BaseKeymap { - #[default] - VSCode, - JetBrains, - SublimeText, - Atom, - TextMate, -} - -impl BaseKeymap { - pub const OPTIONS: [(&'static str, Self); 5] = [ - ("VSCode (Default)", Self::VSCode), - ("Atom", Self::Atom), - ("JetBrains", Self::JetBrains), - ("Sublime Text", Self::SublimeText), - ("TextMate", Self::TextMate), - ]; - - pub fn asset_path(&self) -> Option<&'static str> { - match self { - BaseKeymap::JetBrains => Some("keymaps/jetbrains.json"), - BaseKeymap::SublimeText => Some("keymaps/sublime_text.json"), - BaseKeymap::Atom => Some("keymaps/atom.json"), - BaseKeymap::TextMate => Some("keymaps/textmate.json"), - BaseKeymap::VSCode => None, - } - } - - pub fn names() -> impl Iterator { - Self::OPTIONS.iter().map(|(name, _)| *name) - } - - pub fn from_names(option: &str) -> BaseKeymap { - Self::OPTIONS - .iter() - .copied() - .find_map(|(name, value)| (name == option).then(|| value)) - .unwrap_or_default() - } -} - #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct SettingsFileContent { #[serde(default)] @@ -163,8 +119,6 @@ pub struct SettingsFileContent { pub buffer_font_features: Option, #[serde(default)] pub theme: Option, - #[serde(default)] - pub base_keymap: Option, } impl Settings { @@ -198,7 +152,6 @@ impl Settings { buffer_font_features, buffer_font_size: defaults.buffer_font_size.unwrap(), theme: themes.get(&defaults.theme.unwrap()).unwrap(), - base_keymap: Default::default(), } } @@ -234,7 +187,6 @@ impl Settings { } merge(&mut self.buffer_font_size, data.buffer_font_size); - merge(&mut self.base_keymap, data.base_keymap); } #[cfg(any(test, feature = "test-support"))] @@ -248,7 +200,6 @@ impl Settings { .unwrap(), buffer_font_size: 14., theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), - base_keymap: Default::default(), } } diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 070da5d1ea..cf1787d7c0 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -1,6 +1,6 @@ use crate::{ - settings_store::parse_json_with_comments, settings_store::SettingsStore, KeymapFileContent, - Setting, Settings, DEFAULT_SETTINGS_ASSET_PATH, + settings_store::parse_json_with_comments, settings_store::SettingsStore, Setting, Settings, + DEFAULT_SETTINGS_ASSET_PATH, }; use anyhow::Result; use assets::Assets; @@ -76,43 +76,6 @@ pub fn watch_config_file( rx } -pub fn handle_keymap_file_changes( - mut user_keymap_file_rx: mpsc::UnboundedReceiver, - cx: &mut AppContext, -) { - cx.spawn(move |mut cx| async move { - let mut settings_subscription = None; - while let Some(user_keymap_content) = user_keymap_file_rx.next().await { - if let Ok(keymap_content) = - parse_json_with_comments::(&user_keymap_content) - { - cx.update(|cx| { - cx.clear_bindings(); - KeymapFileContent::load_defaults(cx); - keymap_content.clone().add_to_cx(cx).log_err(); - }); - - let mut old_base_keymap = cx.read(|cx| cx.global::().base_keymap.clone()); - drop(settings_subscription); - settings_subscription = Some(cx.update(|cx| { - cx.observe_global::(move |cx| { - let settings = cx.global::(); - if settings.base_keymap != old_base_keymap { - old_base_keymap = settings.base_keymap.clone(); - - cx.clear_bindings(); - KeymapFileContent::load_defaults(cx); - keymap_content.clone().add_to_cx(cx).log_err(); - } - }) - .detach(); - })); - } - } - }) - .detach(); -} - pub fn handle_settings_file_changes( mut user_settings_file_rx: mpsc::UnboundedReceiver, cx: &mut AppContext, @@ -184,180 +147,3 @@ pub fn update_settings_file( }) .detach_and_log_err(cx); } - -#[cfg(test)] -mod tests { - use super::*; - use fs::FakeFs; - use gpui::{actions, elements::*, Action, Entity, TestAppContext, View, ViewContext}; - use theme::ThemeRegistry; - - struct TestView; - - impl Entity for TestView { - type Event = (); - } - - impl View for TestView { - fn ui_name() -> &'static str { - "TestView" - } - - fn render(&mut self, _: &mut ViewContext) -> AnyElement { - Empty::new().into_any() - } - } - - #[gpui::test] - async fn test_base_keymap(cx: &mut gpui::TestAppContext) { - let executor = cx.background(); - let fs = FakeFs::new(executor.clone()); - - actions!(test, [A, B]); - // From the Atom keymap - actions!(workspace, [ActivatePreviousPane]); - // From the JetBrains keymap - actions!(pane, [ActivatePrevItem]); - - fs.save( - "/settings.json".as_ref(), - &r#" - { - "base_keymap": "Atom" - } - "# - .into(), - Default::default(), - ) - .await - .unwrap(); - - fs.save( - "/keymap.json".as_ref(), - &r#" - [ - { - "bindings": { - "backspace": "test::A" - } - } - ] - "# - .into(), - Default::default(), - ) - .await - .unwrap(); - - cx.update(|cx| { - let mut store = SettingsStore::default(); - store.set_default_settings(&test_settings(), cx).unwrap(); - cx.set_global(store); - cx.set_global(ThemeRegistry::new(Assets, cx.font_cache().clone())); - cx.add_global_action(|_: &A, _cx| {}); - cx.add_global_action(|_: &B, _cx| {}); - cx.add_global_action(|_: &ActivatePreviousPane, _cx| {}); - cx.add_global_action(|_: &ActivatePrevItem, _cx| {}); - - let settings_rx = watch_config_file( - executor.clone(), - fs.clone(), - PathBuf::from("/settings.json"), - ); - let keymap_rx = - watch_config_file(executor.clone(), fs.clone(), PathBuf::from("/keymap.json")); - - handle_keymap_file_changes(keymap_rx, cx); - handle_settings_file_changes(settings_rx, cx); - }); - - cx.foreground().run_until_parked(); - - let (window_id, _view) = cx.add_window(|_| TestView); - - // Test loading the keymap base at all - assert_key_bindings_for( - window_id, - cx, - vec![("backspace", &A), ("k", &ActivatePreviousPane)], - line!(), - ); - - // Test modifying the users keymap, while retaining the base keymap - fs.save( - "/keymap.json".as_ref(), - &r#" - [ - { - "bindings": { - "backspace": "test::B" - } - } - ] - "# - .into(), - Default::default(), - ) - .await - .unwrap(); - - cx.foreground().run_until_parked(); - - assert_key_bindings_for( - window_id, - cx, - vec![("backspace", &B), ("k", &ActivatePreviousPane)], - line!(), - ); - - // Test modifying the base, while retaining the users keymap - fs.save( - "/settings.json".as_ref(), - &r#" - { - "base_keymap": "JetBrains" - } - "# - .into(), - Default::default(), - ) - .await - .unwrap(); - - cx.foreground().run_until_parked(); - - assert_key_bindings_for( - window_id, - cx, - vec![("backspace", &B), ("[", &ActivatePrevItem)], - line!(), - ); - } - - fn assert_key_bindings_for<'a>( - window_id: usize, - cx: &TestAppContext, - actions: Vec<(&'static str, &'a dyn Action)>, - line: u32, - ) { - for (key, action) in actions { - // assert that... - assert!( - cx.available_actions(window_id, 0) - .into_iter() - .any(|(_, bound_action, b)| { - // action names match... - bound_action.name() == action.name() - && bound_action.namespace() == action.namespace() - // and key strokes contain the given key - && b.iter() - .any(|binding| binding.keystrokes().iter().any(|k| k.key == key)) - }), - "On {} Failed to find {} with key binding {}", - line, - action.name(), - key - ); - } - } -} diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 9ac40831d1..531fbf0bba 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -27,7 +27,7 @@ impl<'a> VimTestContext<'a> { cx.update_global(|store: &mut SettingsStore, cx| { store.update_user_settings::(cx, |s| *s = Some(enabled)); }); - settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap(); + settings::KeymapFileContent::load_asset("keymaps/vim.json", cx).unwrap(); }); // Setup search toolbars and keypress hook diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index 82cd4ca35a..65f5151584 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -11,8 +11,6 @@ path = "src/welcome.rs" test-support = [] [dependencies] -anyhow.workspace = true -log.workspace = true client = { path = "../client" } editor = { path = "../editor" } fs = { path = "../fs" } @@ -27,3 +25,8 @@ theme_selector = { path = "../theme_selector" } util = { path = "../util" } picker = { path = "../picker" } workspace = { path = "../workspace" } + +anyhow.workspace = true +log.workspace = true +schemars.workspace = true +serde.workspace = true diff --git a/crates/welcome/src/base_keymap_picker.rs b/crates/welcome/src/base_keymap_picker.rs index 24600d5b82..815de5e6ae 100644 --- a/crates/welcome/src/base_keymap_picker.rs +++ b/crates/welcome/src/base_keymap_picker.rs @@ -1,3 +1,4 @@ +use super::base_keymap_setting::BaseKeymap; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use gpui::{ actions, @@ -6,7 +7,7 @@ use gpui::{ }; use picker::{Picker, PickerDelegate, PickerEvent}; use project::Fs; -use settings::{update_settings_file, BaseKeymap, Settings}; +use settings::{update_settings_file, Settings}; use std::sync::Arc; use util::ResultExt; use workspace::Workspace; @@ -39,10 +40,10 @@ pub struct BaseKeymapSelectorDelegate { impl BaseKeymapSelectorDelegate { fn new(fs: Arc, cx: &mut ViewContext) -> Self { - let base = cx.global::().base_keymap; + let base = settings::get_setting::(None, cx); let selected_index = BaseKeymap::OPTIONS .iter() - .position(|(_, value)| *value == base) + .position(|(_, value)| value == base) .unwrap_or(0); Self { matches: Vec::new(), @@ -122,8 +123,8 @@ impl PickerDelegate for BaseKeymapSelectorDelegate { fn confirm(&mut self, cx: &mut ViewContext) { if let Some(selection) = self.matches.get(self.selected_index) { let base_keymap = BaseKeymap::from_names(&selection.string); - update_settings_file::(self.fs.clone(), cx, move |settings| { - settings.base_keymap = Some(base_keymap) + update_settings_file::(self.fs.clone(), cx, move |setting| { + *setting = Some(base_keymap) }); } cx.emit(PickerEvent::Dismiss); diff --git a/crates/welcome/src/base_keymap_setting.rs b/crates/welcome/src/base_keymap_setting.rs new file mode 100644 index 0000000000..c5b6171f9b --- /dev/null +++ b/crates/welcome/src/base_keymap_setting.rs @@ -0,0 +1,65 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::Setting; + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] +pub enum BaseKeymap { + #[default] + VSCode, + JetBrains, + SublimeText, + Atom, + TextMate, +} + +impl BaseKeymap { + pub const OPTIONS: [(&'static str, Self); 5] = [ + ("VSCode (Default)", Self::VSCode), + ("Atom", Self::Atom), + ("JetBrains", Self::JetBrains), + ("Sublime Text", Self::SublimeText), + ("TextMate", Self::TextMate), + ]; + + pub fn asset_path(&self) -> Option<&'static str> { + match self { + BaseKeymap::JetBrains => Some("keymaps/jetbrains.json"), + BaseKeymap::SublimeText => Some("keymaps/sublime_text.json"), + BaseKeymap::Atom => Some("keymaps/atom.json"), + BaseKeymap::TextMate => Some("keymaps/textmate.json"), + BaseKeymap::VSCode => None, + } + } + + pub fn names() -> impl Iterator { + Self::OPTIONS.iter().map(|(name, _)| *name) + } + + pub fn from_names(option: &str) -> BaseKeymap { + Self::OPTIONS + .iter() + .copied() + .find_map(|(name, value)| (name == option).then(|| value)) + .unwrap_or_default() + } +} + +impl Setting for BaseKeymap { + const KEY: Option<&'static str> = Some("base_keymap"); + + type FileContent = Option; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result + where + Self: Sized, + { + Ok(user_values + .first() + .and_then(|v| **v) + .unwrap_or(default_value.unwrap())) + } +} diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 6b8fe7312d..ca04d5f6dc 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -1,7 +1,7 @@ mod base_keymap_picker; +mod base_keymap_setting; -use std::{borrow::Cow, sync::Arc}; - +use crate::base_keymap_picker::ToggleBaseKeymapSelector; use client::TelemetrySettings; use db::kvp::KEY_VALUE_STORE; use gpui::{ @@ -9,17 +9,19 @@ use gpui::{ AnyElement, AppContext, Element, Entity, Subscription, View, ViewContext, WeakViewHandle, }; use settings::{update_settings_file, Settings}; - +use std::{borrow::Cow, sync::Arc}; use workspace::{ item::Item, open_new, sidebar::SidebarSide, AppState, PaneBackdrop, Welcome, Workspace, WorkspaceId, }; -use crate::base_keymap_picker::ToggleBaseKeymapSelector; +pub use base_keymap_setting::BaseKeymap; pub const FIRST_OPEN: &str = "first_open"; pub fn init(cx: &mut AppContext) { + settings::register_setting::(cx); + cx.add_action(|workspace: &mut Workspace, _: &Welcome, cx| { let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx)); workspace.add_item(Box::new(welcome_page), cx) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index b290460cca..def5049633 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -24,8 +24,7 @@ use parking_lot::Mutex; use project::Fs; use serde::{Deserialize, Serialize}; use settings::{ - default_settings, handle_keymap_file_changes, handle_settings_file_changes, watch_config_file, - Settings, SettingsStore, + default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore, }; use simplelog::ConfigBuilder; use smol::process::Command; @@ -63,7 +62,9 @@ use workspace::{ dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, Workspace, }; -use zed::{self, build_window_options, initialize_workspace, languages, menus}; +use zed::{ + self, build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus, +}; fn main() { let http = http::client(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 923a3d6d9c..d6e4f26b67 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -15,7 +15,7 @@ use anyhow::anyhow; use feedback::{ feedback_info_text::FeedbackInfoText, submit_feedback_button::SubmitFeedbackButton, }; -use futures::StreamExt; +use futures::{channel::mpsc, StreamExt}; use gpui::{ actions, geometry::vector::vec2f, @@ -29,11 +29,14 @@ use project_panel::ProjectPanel; use search::{BufferSearchBar, ProjectSearchBar}; use serde::Deserialize; use serde_json::to_string_pretty; -use settings::{adjust_font_size_delta, Settings, DEFAULT_SETTINGS_ASSET_PATH}; +use settings::{ + adjust_font_size_delta, KeymapFileContent, Settings, SettingsStore, DEFAULT_SETTINGS_ASSET_PATH, +}; use std::{borrow::Cow, str, sync::Arc}; use terminal_view::terminal_button::TerminalButton; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; +use welcome::BaseKeymap; pub use workspace; use workspace::{ create_and_open_local_file, open_new, sidebar::SidebarSide, AppState, NewFile, NewWindow, @@ -258,7 +261,7 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { activity_indicator::init(cx); lsp_log::init(cx); call::init(app_state.client.clone(), app_state.user_store.clone(), cx); - settings::KeymapFileContent::load_defaults(cx); + load_default_keymap(cx); } pub fn initialize_workspace( @@ -478,6 +481,52 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext) { .detach(); } +pub fn load_default_keymap(cx: &mut AppContext) { + for path in ["keymaps/default.json", "keymaps/vim.json"] { + KeymapFileContent::load_asset(path, cx).unwrap(); + } + + if let Some(asset_path) = settings::get_setting::(None, cx).asset_path() { + KeymapFileContent::load_asset(asset_path, cx).unwrap(); + } +} + +pub fn handle_keymap_file_changes( + mut user_keymap_file_rx: mpsc::UnboundedReceiver, + cx: &mut AppContext, +) { + cx.spawn(move |mut cx| async move { + let mut settings_subscription = None; + while let Some(user_keymap_content) = user_keymap_file_rx.next().await { + if let Ok(keymap_content) = KeymapFileContent::parse(&user_keymap_content) { + cx.update(|cx| { + cx.clear_bindings(); + load_default_keymap(cx); + keymap_content.clone().add_to_cx(cx).log_err(); + }); + + let mut old_base_keymap = + cx.read(|cx| *settings::get_setting::(None, cx)); + drop(settings_subscription); + settings_subscription = Some(cx.update(|cx| { + cx.observe_global::(move |cx| { + let new_base_keymap = *settings::get_setting::(None, cx); + if new_base_keymap != old_base_keymap { + old_base_keymap = new_base_keymap.clone(); + + cx.clear_bindings(); + load_default_keymap(cx); + keymap_content.clone().add_to_cx(cx).log_err(); + } + }) + .detach(); + })); + } + } + }) + .detach(); +} + fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext) { workspace.with_local_workspace(cx, move |workspace, cx| { let app_state = workspace.app_state().clone(); @@ -579,11 +628,16 @@ mod tests { use super::*; use assets::Assets; use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor}; - use gpui::{executor::Deterministic, AppContext, AssetSource, TestAppContext, ViewHandle}; + use fs::{FakeFs, Fs}; + use gpui::{ + elements::Empty, executor::Deterministic, Action, AnyElement, AppContext, AssetSource, + Element, Entity, TestAppContext, View, ViewHandle, + }; use language::LanguageRegistry; use node_runtime::NodeRuntime; use project::{Project, ProjectPath}; use serde_json::json; + use settings::{handle_settings_file_changes, watch_config_file, SettingsStore}; use std::{ collections::HashSet, path::{Path, PathBuf}, @@ -1797,6 +1851,175 @@ mod tests { } } + #[gpui::test] + async fn test_base_keymap(cx: &mut gpui::TestAppContext) { + struct TestView; + + impl Entity for TestView { + type Event = (); + } + + impl View for TestView { + fn ui_name() -> &'static str { + "TestView" + } + + fn render(&mut self, _: &mut ViewContext) -> AnyElement { + Empty::new().into_any() + } + } + + let executor = cx.background(); + let fs = FakeFs::new(executor.clone()); + + actions!(test, [A, B]); + // From the Atom keymap + actions!(workspace, [ActivatePreviousPane]); + // From the JetBrains keymap + actions!(pane, [ActivatePrevItem]); + + fs.save( + "/settings.json".as_ref(), + &r#" + { + "base_keymap": "Atom" + } + "# + .into(), + Default::default(), + ) + .await + .unwrap(); + + fs.save( + "/keymap.json".as_ref(), + &r#" + [ + { + "bindings": { + "backspace": "test::A" + } + } + ] + "# + .into(), + Default::default(), + ) + .await + .unwrap(); + + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + cx.set_global(ThemeRegistry::new(Assets, cx.font_cache().clone())); + welcome::init(cx); + + cx.add_global_action(|_: &A, _cx| {}); + cx.add_global_action(|_: &B, _cx| {}); + cx.add_global_action(|_: &ActivatePreviousPane, _cx| {}); + cx.add_global_action(|_: &ActivatePrevItem, _cx| {}); + + let settings_rx = watch_config_file( + executor.clone(), + fs.clone(), + PathBuf::from("/settings.json"), + ); + let keymap_rx = + watch_config_file(executor.clone(), fs.clone(), PathBuf::from("/keymap.json")); + + handle_keymap_file_changes(keymap_rx, cx); + handle_settings_file_changes(settings_rx, cx); + }); + + cx.foreground().run_until_parked(); + + let (window_id, _view) = cx.add_window(|_| TestView); + + // Test loading the keymap base at all + assert_key_bindings_for( + window_id, + cx, + vec![("backspace", &A), ("k", &ActivatePreviousPane)], + line!(), + ); + + // Test modifying the users keymap, while retaining the base keymap + fs.save( + "/keymap.json".as_ref(), + &r#" + [ + { + "bindings": { + "backspace": "test::B" + } + } + ] + "# + .into(), + Default::default(), + ) + .await + .unwrap(); + + cx.foreground().run_until_parked(); + + assert_key_bindings_for( + window_id, + cx, + vec![("backspace", &B), ("k", &ActivatePreviousPane)], + line!(), + ); + + // Test modifying the base, while retaining the users keymap + fs.save( + "/settings.json".as_ref(), + &r#" + { + "base_keymap": "JetBrains" + } + "# + .into(), + Default::default(), + ) + .await + .unwrap(); + + cx.foreground().run_until_parked(); + + assert_key_bindings_for( + window_id, + cx, + vec![("backspace", &B), ("[", &ActivatePrevItem)], + line!(), + ); + + fn assert_key_bindings_for<'a>( + window_id: usize, + cx: &TestAppContext, + actions: Vec<(&'static str, &'a dyn Action)>, + line: u32, + ) { + for (key, action) in actions { + // assert that... + assert!( + cx.available_actions(window_id, 0) + .into_iter() + .any(|(_, bound_action, b)| { + // action names match... + bound_action.name() == action.name() + && bound_action.namespace() == action.namespace() + // and key strokes contain the given key + && b.iter() + .any(|binding| binding.keystrokes().iter().any(|k| k.key == key)) + }), + "On {} Failed to find {} with key binding {}", + line, + action.name(), + key + ); + } + } + } + #[gpui::test] fn test_bundled_settings_and_themes(cx: &mut AppContext) { cx.platform() From aa89632286487304ed2a6626e786dd42233277eb Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 17 May 2023 11:40:30 -0700 Subject: [PATCH 119/168] Add diff hunks to the scroll bar --- crates/editor/src/element.rs | 49 ++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index c1710b7337..1d4aff39de 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1022,15 +1022,16 @@ impl EditorElement { let mut first_row_y_offset = 0.0; // Impose a minimum height on the scrollbar thumb + let row_height = height / max_row; let min_thumb_height = style.min_height_factor * cx.font_cache.line_height(self.style.text.font_size); - let thumb_height = (row_range.end - row_range.start) * height / max_row; + let thumb_height = (row_range.end - row_range.start) * row_height; if thumb_height < min_thumb_height { first_row_y_offset = (min_thumb_height - thumb_height) / 2.0; height -= min_thumb_height - thumb_height; } - let y_for_row = |row: f32| -> f32 { top + first_row_y_offset + row * height / max_row }; + let y_for_row = |row: f32| -> f32 { top + first_row_y_offset + row * row_height }; let thumb_top = y_for_row(row_range.start) - first_row_y_offset; let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset; @@ -1044,6 +1045,50 @@ impl EditorElement { background: style.track.background_color, ..Default::default() }); + + let diff_style = cx.global::().theme.editor.diff.clone(); + for hunk in layout + .position_map + .snapshot + .buffer_snapshot + .git_diff_hunks_in_range(0..(max_row.floor() as u32), false) + { + let start_y = y_for_row(hunk.buffer_range.start as f32); + let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end { + y_for_row((hunk.buffer_range.end + 1) as f32) + } else { + y_for_row((hunk.buffer_range.end) as f32) + }; + + if end_y - start_y < 1. { + end_y = start_y + 1.; + } + let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y)); + + let color = match hunk.status() { + DiffHunkStatus::Added => diff_style.inserted, + DiffHunkStatus::Modified => diff_style.modified, + DiffHunkStatus::Removed => diff_style.deleted, + }; + + let border = Border { + width: 1., + color: style.thumb.border.color, + overlay: false, + top: false, + right: true, + bottom: false, + left: true, + }; + + scene.push_quad(Quad { + bounds, + background: Some(color), + border, + corner_radius: style.thumb.corner_radius, + }) + } + scene.push_quad(Quad { bounds: thumb_bounds, border: style.thumb.border, From ae3394f4decb8ea5cfb305d2faf9bb7ebec86044 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 17 May 2023 14:09:26 -0700 Subject: [PATCH 120/168] Add scrollbars setting --- assets/settings/default.json | 11 +++++++++++ crates/editor/src/editor.rs | 9 +++++++++ crates/editor/src/element.rs | 10 +++++++++- crates/settings/src/settings.rs | 15 +++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 20d07acae0..05e79ae9cc 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -43,6 +43,17 @@ // 3. Draw all invisible symbols: // "all" "show_whitespaces": "selection", + // Whether to show the scrollbar in the editor. + // This setting can take four values: + // + // 1. Show the scrollbar if there's important information or + // follow the system's configured behavior (default): + // "auto" + // 2. Match the system's configured behavior: + // "system" + // 3. Always show the scrollbar: + // "always" + "show_scrollbars": "auto", // Whether the screen sharing icon is shown in the os status bar. "show_call_status_icon": true, // Whether to use language servers to provide code intelligence. diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 21d495d6ee..218daf08c4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -516,6 +516,15 @@ pub struct EditorSnapshot { ongoing_scroll: OngoingScroll, } +impl EditorSnapshot { + fn has_scrollbar_info(&self) -> bool { + self.buffer_snapshot + .git_diff_hunks_in_range(0..self.max_point().row(), false) + .next() + .is_some() + } +} + #[derive(Clone, Debug)] struct SelectionHistoryEntry { selections: Arc<[Selection]>, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 1d4aff39de..e5fcb024c6 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2058,7 +2058,15 @@ impl Element for EditorElement { )); } - let show_scrollbars = editor.scroll_manager.scrollbars_visible(); + let show_scrollbars = match cx.global::().show_scrollbars { + settings::ShowScrollbars::Auto => { + snapshot.has_scrollbar_info() + || editor.scroll_manager.scrollbars_visible() + } + settings::ShowScrollbars::System => editor.scroll_manager.scrollbars_visible(), + settings::ShowScrollbars::Always => true, + }; + let include_root = editor .project .as_ref() diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 7ba52fcb5e..284d2579a5 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -46,6 +46,7 @@ pub struct Settings { pub hover_popover_enabled: bool, pub show_completions_on_input: bool, pub show_call_status_icon: bool, + pub show_scrollbars: ShowScrollbars, pub vim_mode: bool, pub autosave: Autosave, pub default_dock_anchor: DockAnchor, @@ -68,6 +69,15 @@ pub struct Settings { pub base_keymap: BaseKeymap, } +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] +#[serde(rename_all = "snake_case")] +pub enum ShowScrollbars { + #[default] + Auto, + System, + Always, +} + #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] pub enum BaseKeymap { #[default] @@ -390,6 +400,8 @@ pub struct SettingsFileContent { #[serde(default)] pub active_pane_magnification: Option, #[serde(default)] + pub show_scrollbars: Option, + #[serde(default)] pub cursor_blink: Option, #[serde(default)] pub confirm_quit: Option, @@ -547,6 +559,7 @@ impl Settings { features: Features { copilot: defaults.features.copilot.unwrap(), }, + show_scrollbars: defaults.show_scrollbars.unwrap(), } } @@ -598,6 +611,7 @@ impl Settings { merge(&mut self.autosave, data.autosave); merge(&mut self.default_dock_anchor, data.default_dock_anchor); merge(&mut self.base_keymap, data.base_keymap); + merge(&mut self.show_scrollbars, data.show_scrollbars); merge(&mut self.features.copilot, data.features.copilot); if let Some(copilot) = data.copilot { @@ -830,6 +844,7 @@ impl Settings { auto_update: true, base_keymap: Default::default(), features: Features { copilot: true }, + show_scrollbars: Default::default(), } } From 6cf439e73412bf332bfe358b845770d3f4813f78 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 17 May 2023 14:12:04 -0700 Subject: [PATCH 121/168] fmt --- crates/editor/src/element.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index e5fcb024c6..755c3091cb 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2060,8 +2060,7 @@ impl Element for EditorElement { let show_scrollbars = match cx.global::().show_scrollbars { settings::ShowScrollbars::Auto => { - snapshot.has_scrollbar_info() - || editor.scroll_manager.scrollbars_visible() + snapshot.has_scrollbar_info() || editor.scroll_manager.scrollbars_visible() } settings::ShowScrollbars::System => editor.scroll_manager.scrollbars_visible(), settings::ShowScrollbars::Always => true, From 88bc55ddf5d31b9cffbb4b68d596a72b6c6153c6 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 17 May 2023 14:17:10 -0700 Subject: [PATCH 122/168] Add a dependency on rustfmt to the CI tests --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27af9e1164..a4176bd9b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,7 @@ jobs: runs-on: - self-hosted - test + needs: rustfmt env: RUSTFLAGS: -D warnings steps: From f4e99ecde4cd6c57c6e317f4ba5ccbcea6affcb9 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 17 May 2023 14:19:35 -0700 Subject: [PATCH 123/168] Add never option to scrollbar settings --- assets/settings/default.json | 2 ++ crates/editor/src/element.rs | 1 + crates/settings/src/settings.rs | 1 + 3 files changed, 4 insertions(+) diff --git a/assets/settings/default.json b/assets/settings/default.json index 05e79ae9cc..1395edca4a 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -53,6 +53,8 @@ // "system" // 3. Always show the scrollbar: // "always" + // 4. Never show the scrollbar: + // "never" "show_scrollbars": "auto", // Whether the screen sharing icon is shown in the os status bar. "show_call_status_icon": true, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 755c3091cb..e1e38f2e24 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2064,6 +2064,7 @@ impl Element for EditorElement { } settings::ShowScrollbars::System => editor.scroll_manager.scrollbars_visible(), settings::ShowScrollbars::Always => true, + settings::ShowScrollbars::Never => false, }; let include_root = editor diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 284d2579a5..55598845c7 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -76,6 +76,7 @@ pub enum ShowScrollbars { Auto, System, Always, + Never, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] From 67a25126d48b173ba8a5dffdff1aaf94002c3781 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 May 2023 14:44:55 -0700 Subject: [PATCH 124/168] Define theme/ui text style settings in theme crate --- Cargo.lock | 9 +- crates/activity_indicator/Cargo.toml | 2 + .../src/activity_indicator.rs | 8 +- crates/auto_update/src/update_notification.rs | 3 +- crates/breadcrumbs/src/breadcrumbs.rs | 3 +- crates/collab/src/tests.rs | 6 +- .../src/tests/randomized_integration_tests.rs | 4 +- crates/collab_ui/src/collab_titlebar_item.rs | 7 +- crates/collab_ui/src/contact_finder.rs | 3 +- crates/collab_ui/src/contact_list.rs | 5 +- crates/collab_ui/src/contacts_popover.rs | 3 +- .../src/incoming_call_notification.rs | 28 +-- crates/collab_ui/src/notifications.rs | 3 +- .../src/project_shared_notification.rs | 27 +-- crates/command_palette/src/command_palette.rs | 5 +- crates/context_menu/src/context_menu.rs | 5 +- crates/copilot/src/sign_in.rs | 5 +- crates/copilot_button/src/copilot_button.rs | 11 +- crates/diagnostics/Cargo.toml | 1 + crates/diagnostics/src/diagnostics.rs | 38 ++-- crates/diagnostics/src/items.rs | 10 +- crates/editor/src/blink_manager.rs | 6 +- crates/editor/src/display_map/block_map.rs | 13 +- crates/editor/src/display_map/fold_map.rs | 14 +- .../editor/src/display_map/suggestion_map.rs | 10 +- crates/editor/src/display_map/wrap_map.rs | 14 +- crates/editor/src/editor.rs | 22 +- crates/editor/src/editor_tests.rs | 6 +- crates/editor/src/element.rs | 9 +- crates/editor/src/hover_popover.rs | 3 +- crates/editor/src/items.rs | 3 +- crates/editor/src/link_go_to_definition.rs | 3 +- crates/editor/src/movement.rs | 5 +- crates/editor/src/multi_buffer.rs | 6 +- .../src/test/editor_lsp_test_context.rs | 1 + crates/feedback/src/deploy_feedback_button.rs | 3 +- crates/feedback/src/feedback_info_text.rs | 3 +- crates/feedback/src/submit_feedback_button.rs | 3 +- crates/file_finder/src/file_finder.rs | 6 +- crates/go_to_line/Cargo.toml | 1 + crates/go_to_line/src/go_to_line.rs | 3 +- crates/language/src/language_settings.rs | 1 + .../src/active_buffer_language.rs | 3 +- .../src/language_selector.rs | 4 +- crates/lsp_log/src/lsp_log.rs | 3 +- crates/outline/Cargo.toml | 2 + crates/outline/src/outline.rs | 7 +- crates/picker/src/picker.rs | 2 +- crates/project/src/project.rs | 10 +- crates/project_panel/src/project_panel.rs | 13 +- crates/project_symbols/Cargo.toml | 3 + crates/project_symbols/src/project_symbols.rs | 12 +- crates/recent_projects/Cargo.toml | 1 + crates/recent_projects/src/recent_projects.rs | 6 +- crates/search/src/buffer_search.rs | 25 +-- crates/search/src/project_search.rs | 35 ++- crates/settings/Cargo.toml | 8 +- crates/settings/src/settings.rs | 209 +----------------- crates/settings/src/settings_file.rs | 25 +-- crates/settings/src/settings_store.rs | 30 ++- crates/terminal/src/terminal.rs | 3 +- crates/terminal_view/src/terminal_button.rs | 3 +- crates/terminal_view/src/terminal_element.rs | 6 +- crates/terminal_view/src/terminal_view.rs | 1 + crates/theme/Cargo.toml | 15 +- crates/theme/src/theme.rs | 18 +- crates/theme/src/theme_registry.rs | 12 +- crates/theme/src/theme_settings.rs | 136 ++++++++++++ crates/theme_selector/src/theme_selector.rs | 54 ++--- crates/theme_testbench/src/theme_testbench.rs | 7 +- crates/welcome/src/base_keymap_picker.rs | 4 +- crates/welcome/src/welcome.rs | 10 +- crates/workspace/src/dock.rs | 3 - .../workspace/src/dock/toggle_dock_button.rs | 3 +- crates/workspace/src/notifications.rs | 9 +- crates/workspace/src/pane.rs | 17 +- .../src/pane/dragged_item_receiver.rs | 12 +- crates/workspace/src/shared_screen.rs | 3 +- crates/workspace/src/sidebar.rs | 5 +- crates/workspace/src/status_bar.rs | 3 +- crates/workspace/src/toolbar.rs | 5 +- crates/workspace/src/workspace.rs | 17 +- crates/zed/src/languages.rs | 8 +- crates/zed/src/languages/json.rs | 31 +-- crates/zed/src/main.rs | 33 ++- crates/zed/src/zed.rs | 30 ++- 86 files changed, 530 insertions(+), 637 deletions(-) create mode 100644 crates/theme/src/theme_settings.rs diff --git a/Cargo.lock b/Cargo.lock index e5922f9e66..af412a8c1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,7 @@ dependencies = [ "project", "settings", "smallvec", + "theme", "util", "workspace", ] @@ -2750,6 +2751,7 @@ dependencies = [ "postage", "settings", "text", + "theme", "util", "workspace", ] @@ -4398,6 +4400,7 @@ dependencies = [ "settings", "smol", "text", + "theme", "workspace", ] @@ -4915,6 +4918,7 @@ dependencies = [ "settings", "smol", "text", + "theme", "util", "workspace", ] @@ -5235,6 +5239,7 @@ dependencies = [ "settings", "smol", "text", + "theme", "util", "workspace", ] @@ -6135,7 +6140,6 @@ dependencies = [ "smallvec", "sqlez", "staff_mode", - "theme", "toml", "tree-sitter", "tree-sitter-json 0.19.0", @@ -6863,10 +6867,13 @@ dependencies = [ "gpui", "indexmap", "parking_lot 0.11.2", + "schemars", "serde", "serde_derive", "serde_json", + "settings", "toml", + "util", ] [[package]] diff --git a/crates/activity_indicator/Cargo.toml b/crates/activity_indicator/Cargo.toml index 629aa2c032..917383234a 100644 --- a/crates/activity_indicator/Cargo.toml +++ b/crates/activity_indicator/Cargo.toml @@ -16,6 +16,8 @@ gpui = { path = "../gpui" } project = { path = "../project" } settings = { path = "../settings" } util = { path = "../util" } +theme = { path = "../theme" } workspace = { path = "../workspace" } + futures.workspace = true smallvec.workspace = true diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index d5ee1364b3..801c8f7172 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -9,7 +9,6 @@ use gpui::{ }; use language::{LanguageRegistry, LanguageServerBinaryStatus}; use project::{LanguageServerProgress, Project}; -use settings::Settings; use smallvec::SmallVec; use std::{cmp::Reverse, fmt::Write, sync::Arc}; use util::ResultExt; @@ -325,12 +324,7 @@ impl View for ActivityIndicator { } = self.content_to_render(cx); let mut element = MouseEventHandler::::new(0, cx, |state, cx| { - let theme = &cx - .global::() - .theme - .workspace - .status_bar - .lsp_status; + let theme = &theme::current(cx).workspace.status_bar.lsp_status; let style = if state.hovered() && on_click.is_some() { theme.hover.as_ref().unwrap_or(&theme.default) } else { diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs index b48ac2a413..6f31df614d 100644 --- a/crates/auto_update/src/update_notification.rs +++ b/crates/auto_update/src/update_notification.rs @@ -5,7 +5,6 @@ use gpui::{ Element, Entity, View, ViewContext, }; use menu::Cancel; -use settings::Settings; use util::channel::ReleaseChannel; use workspace::notifications::Notification; @@ -27,7 +26,7 @@ impl View for UpdateNotification { } fn render(&mut self, cx: &mut gpui::ViewContext) -> gpui::AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let theme = &theme.update_notification; let app_name = cx.global::().display_name(); diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs index f3be60f8de..906d70abb7 100644 --- a/crates/breadcrumbs/src/breadcrumbs.rs +++ b/crates/breadcrumbs/src/breadcrumbs.rs @@ -4,7 +4,6 @@ use gpui::{ }; use itertools::Itertools; use search::ProjectSearchView; -use settings::Settings; use workspace::{ item::{ItemEvent, ItemHandle}, ToolbarItemLocation, ToolbarItemView, Workspace, @@ -50,7 +49,7 @@ impl View for Breadcrumbs { }; let not_editor = active_item.downcast::().is_none(); - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let style = &theme.workspace.breadcrumbs; let breadcrumbs = match active_item.breadcrumbs(&theme, cx) { diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index f1593822af..3c571327eb 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -19,7 +19,7 @@ use gpui::{ use language::LanguageRegistry; use parking_lot::Mutex; use project::{Project, WorktreeId}; -use settings::{Settings, SettingsStore}; +use settings::SettingsStore; use std::{ cell::{Ref, RefCell, RefMut}, env, @@ -30,7 +30,6 @@ use std::{ Arc, }, }; -use theme::ThemeRegistry; use util::http::FakeHttpClient; use workspace::Workspace; @@ -103,7 +102,6 @@ impl TestServer { async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient { cx.update(|cx| { cx.set_global(SettingsStore::test(cx)); - cx.set_global(Settings::test(cx)); }); let http = FakeHttpClient::with_404_response(); @@ -192,7 +190,6 @@ impl TestServer { client: client.clone(), user_store: user_store.clone(), languages: Arc::new(LanguageRegistry::test()), - themes: ThemeRegistry::new((), cx.font_cache()), fs: fs.clone(), build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _| unimplemented!(), @@ -201,6 +198,7 @@ impl TestServer { }); cx.update(|cx| { + theme::init((), cx); Project::init(&client, cx); client::init(&client, cx); language::init(cx); diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index fb0645b147..3beff6942a 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -21,7 +21,7 @@ use rand::{ prelude::*, }; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore}; +use settings::SettingsStore; use std::{ env, ops::Range, @@ -150,10 +150,8 @@ async fn test_random_collaboration( for (client, mut cx) in clients { cx.update(|cx| { let store = cx.remove_global::(); - let settings = cx.remove_global::(); cx.clear_globals(); cx.set_global(store); - cx.set_global(settings); drop(client); }); } diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 7374b166ca..eb1755a9ff 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -18,7 +18,6 @@ use gpui::{ ViewContext, ViewHandle, WeakViewHandle, }; use project::Project; -use settings::Settings; use std::{ops::Range, sync::Arc}; use theme::{AvatarStyle, Theme}; use util::ResultExt; @@ -70,7 +69,7 @@ impl View for CollabTitlebarItem { }; let project = self.project.read(cx); - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let mut left_container = Flex::row(); let mut right_container = Flex::row().align_children_center(); @@ -298,7 +297,7 @@ impl CollabTitlebarItem { } pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext) { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let avatar_style = theme.workspace.titlebar.leader_avatar.clone(); let item_style = theme.context_menu.item.disabled_style().clone(); self.user_menu.update(cx, |user_menu, cx| { @@ -866,7 +865,7 @@ impl CollabTitlebarItem { ) -> Option> { enum ConnectionStatusButton {} - let theme = &cx.global::().theme.clone(); + let theme = &theme::current(cx).clone(); match status { client::Status::ConnectionError | client::Status::ConnectionLost diff --git a/crates/collab_ui/src/contact_finder.rs b/crates/collab_ui/src/contact_finder.rs index 8530867f14..b5f2416a5b 100644 --- a/crates/collab_ui/src/contact_finder.rs +++ b/crates/collab_ui/src/contact_finder.rs @@ -1,7 +1,6 @@ use client::{ContactRequestStatus, User, UserStore}; use gpui::{elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext}; use picker::{Picker, PickerDelegate, PickerEvent}; -use settings::Settings; use std::sync::Arc; use util::TryFutureExt; @@ -98,7 +97,7 @@ impl PickerDelegate for ContactFinderDelegate { selected: bool, cx: &gpui::AppContext, ) -> AnyElement> { - let theme = &cx.global::().theme; + let theme = &theme::current(cx); let user = &self.potential_contacts[ix]; let request_status = self.user_store.read(cx).contact_request_status(user); diff --git a/crates/collab_ui/src/contact_list.rs b/crates/collab_ui/src/contact_list.rs index 452867b8c4..e8dae210c4 100644 --- a/crates/collab_ui/src/contact_list.rs +++ b/crates/collab_ui/src/contact_list.rs @@ -14,7 +14,6 @@ use gpui::{ use menu::{Confirm, SelectNext, SelectPrev}; use project::Project; use serde::Deserialize; -use settings::Settings; use std::{mem, sync::Arc}; use theme::IconButton; use workspace::Workspace; @@ -192,7 +191,7 @@ impl ContactList { .detach(); let list_state = ListState::::new(0, Orientation::Top, 1000., move |this, ix, cx| { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let is_selected = this.selection == Some(ix); let current_project_id = this.project.read(cx).remote_id(); @@ -1313,7 +1312,7 @@ impl View for ContactList { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { enum AddContact {} - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); Flex::column() .with_child( diff --git a/crates/collab_ui/src/contacts_popover.rs b/crates/collab_ui/src/contacts_popover.rs index 35734d81f4..1d6d1c84c7 100644 --- a/crates/collab_ui/src/contacts_popover.rs +++ b/crates/collab_ui/src/contacts_popover.rs @@ -9,7 +9,6 @@ use gpui::{ }; use picker::PickerEvent; use project::Project; -use settings::Settings; use workspace::Workspace; actions!(contacts_popover, [ToggleContactFinder]); @@ -108,7 +107,7 @@ impl View for ContactsPopover { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let child = match &self.child { Child::ContactList(child) => ChildView::new(child, cx), Child::ContactFinder(child) => ChildView::new(child, cx), diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index 35484b3309..ed3e648555 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -9,7 +9,6 @@ use gpui::{ platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions}, AnyElement, AppContext, Entity, View, ViewContext, }; -use settings::Settings; use util::ResultExt; use workspace::AppState; @@ -26,7 +25,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { if let Some(incoming_call) = incoming_call { const PADDING: f32 = 16.; let window_size = cx.read(|cx| { - let theme = &cx.global::().theme.incoming_call_notification; + let theme = &theme::current(cx).incoming_call_notification; vec2f(theme.window_width, theme.window_height) }); @@ -106,7 +105,7 @@ impl IncomingCallNotification { } fn render_caller(&self, cx: &mut ViewContext) -> AnyElement { - let theme = &cx.global::().theme.incoming_call_notification; + let theme = &theme::current(cx).incoming_call_notification; let default_project = proto::ParticipantProject::default(); let initial_project = self .call @@ -170,10 +169,11 @@ impl IncomingCallNotification { enum Accept {} enum Decline {} + let theme = theme::current(cx); Flex::column() .with_child( - MouseEventHandler::::new(0, cx, |_, cx| { - let theme = &cx.global::().theme.incoming_call_notification; + MouseEventHandler::::new(0, cx, |_, _| { + let theme = &theme.incoming_call_notification; Label::new("Accept", theme.accept_button.text.clone()) .aligned() .contained() @@ -186,8 +186,8 @@ impl IncomingCallNotification { .flex(1., true), ) .with_child( - MouseEventHandler::::new(0, cx, |_, cx| { - let theme = &cx.global::().theme.incoming_call_notification; + MouseEventHandler::::new(0, cx, |_, _| { + let theme = &theme.incoming_call_notification; Label::new("Decline", theme.decline_button.text.clone()) .aligned() .contained() @@ -200,12 +200,7 @@ impl IncomingCallNotification { .flex(1., true), ) .constrained() - .with_width( - cx.global::() - .theme - .incoming_call_notification - .button_width, - ) + .with_width(theme.incoming_call_notification.button_width) .into_any() } } @@ -220,12 +215,7 @@ impl View for IncomingCallNotification { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let background = cx - .global::() - .theme - .incoming_call_notification - .background; - + let background = theme::current(cx).incoming_call_notification.background; Flex::row() .with_child(self.render_caller(cx)) .with_child(self.render_buttons(cx)) diff --git a/crates/collab_ui/src/notifications.rs b/crates/collab_ui/src/notifications.rs index 1dec5a8411..abeb65b1dc 100644 --- a/crates/collab_ui/src/notifications.rs +++ b/crates/collab_ui/src/notifications.rs @@ -4,7 +4,6 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyElement, Element, View, ViewContext, }; -use settings::Settings; use std::sync::Arc; enum Dismiss {} @@ -22,7 +21,7 @@ where F: 'static + Fn(&mut V, &mut ViewContext), V: View, { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let theme = &theme.contact_notification; Flex::column() diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 8a41368276..155209f470 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -7,7 +7,6 @@ use gpui::{ platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions}, AppContext, Entity, View, ViewContext, }; -use settings::Settings; use std::sync::{Arc, Weak}; use workspace::AppState; @@ -22,7 +21,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { worktree_root_names, } => { const PADDING: f32 = 16.; - let theme = &cx.global::().theme.project_shared_notification; + let theme = &theme::current(cx).project_shared_notification; let window_size = vec2f(theme.window_width, theme.window_height); for screen in cx.platform().screens() { @@ -109,7 +108,7 @@ impl ProjectSharedNotification { } fn render_owner(&self, cx: &mut ViewContext) -> AnyElement { - let theme = &cx.global::().theme.project_shared_notification; + let theme = &theme::current(cx).project_shared_notification; Flex::row() .with_children(self.owner.avatar.clone().map(|avatar| { Image::from_data(avatar) @@ -167,10 +166,11 @@ impl ProjectSharedNotification { enum Open {} enum Dismiss {} + let theme = theme::current(cx); Flex::column() .with_child( - MouseEventHandler::::new(0, cx, |_, cx| { - let theme = &cx.global::().theme.project_shared_notification; + MouseEventHandler::::new(0, cx, |_, _| { + let theme = &theme.project_shared_notification; Label::new("Open", theme.open_button.text.clone()) .aligned() .contained() @@ -181,8 +181,8 @@ impl ProjectSharedNotification { .flex(1., true), ) .with_child( - MouseEventHandler::::new(0, cx, |_, cx| { - let theme = &cx.global::().theme.project_shared_notification; + MouseEventHandler::::new(0, cx, |_, _| { + let theme = &theme.project_shared_notification; Label::new("Dismiss", theme.dismiss_button.text.clone()) .aligned() .contained() @@ -195,12 +195,7 @@ impl ProjectSharedNotification { .flex(1., true), ) .constrained() - .with_width( - cx.global::() - .theme - .project_shared_notification - .button_width, - ) + .with_width(theme.project_shared_notification.button_width) .into_any() } } @@ -215,11 +210,7 @@ impl View for ProjectSharedNotification { } fn render(&mut self, cx: &mut ViewContext) -> gpui::AnyElement { - let background = cx - .global::() - .theme - .project_shared_notification - .background; + let background = theme::current(cx).project_shared_notification.background; Flex::row() .with_child(self.render_owner(cx)) .with_child(self.render_buttons(cx)) diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 12134f6d6e..2ee93a0734 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -5,7 +5,6 @@ use gpui::{ ViewContext, }; use picker::{Picker, PickerDelegate, PickerEvent}; -use settings::Settings; use std::cmp; use util::ResultExt; use workspace::Workspace; @@ -185,8 +184,7 @@ impl PickerDelegate for CommandPaletteDelegate { ) -> AnyElement> { let mat = &self.matches[ix]; let command = &self.actions[mat.candidate_id]; - let settings = cx.global::(); - let theme = &settings.theme; + let theme = theme::current(cx); let style = theme.picker.item.style_for(mouse_state, selected); let key_style = &theme.command_palette.key.style_for(mouse_state, selected); let keystroke_spacing = theme.command_palette.keystroke_spacing; @@ -366,6 +364,7 @@ mod tests { fn init_test(cx: &mut TestAppContext) -> Arc { cx.update(|cx| { let app_state = AppState::test(cx); + theme::init((), cx); language::init(cx); editor::init(cx); workspace::init(app_state.clone(), cx); diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index f0d477e42f..fb455fe1d0 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -8,7 +8,6 @@ use gpui::{ View, ViewContext, }; use menu::*; -use settings::Settings; use std::{any::TypeId, borrow::Cow, sync::Arc, time::Duration}; pub fn init(cx: &mut AppContext) { @@ -323,7 +322,7 @@ impl ContextMenu { } fn render_menu_for_measurement(&self, cx: &mut ViewContext) -> impl Element { - let style = cx.global::().theme.context_menu.clone(); + let style = theme::current(cx).context_menu.clone(); Flex::row() .with_child( Flex::column().with_children(self.items.iter().enumerate().map(|(ix, item)| { @@ -403,7 +402,7 @@ impl ContextMenu { enum Menu {} enum MenuItem {} - let style = cx.global::().theme.context_menu.clone(); + let style = theme::current(cx).context_menu.clone(); MouseEventHandler::::new(0, cx, |_, cx| { Flex::column() diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index da3c96956e..764a0e4df1 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -6,7 +6,6 @@ use gpui::{ AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext, ViewHandle, }; -use settings::Settings; use theme::ui::modal; #[derive(PartialEq, Eq, Debug, Clone)] @@ -68,7 +67,7 @@ fn create_copilot_auth_window( cx: &mut AppContext, status: &Status, ) -> ViewHandle { - let window_size = cx.global::().theme.copilot.modal.dimensions(); + let window_size = theme::current(cx).copilot.modal.dimensions(); let window_options = WindowOptions { bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)), titlebar: None, @@ -338,7 +337,7 @@ impl View for CopilotCodeVerification { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { enum ConnectModal {} - let style = cx.global::().theme.clone(); + let style = theme::current(cx).clone(); modal::( "Connect Copilot to Zed", diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index d04f1e0e75..560506a529 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -10,7 +10,7 @@ use gpui::{ ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use language::language_settings::{self, all_language_settings, AllLanguageSettings}; -use settings::{update_settings_file, Settings, SettingsStore}; +use settings::{update_settings_file, SettingsStore}; use std::{path::Path, sync::Arc}; use util::{paths, ResultExt}; use workspace::{ @@ -46,8 +46,7 @@ impl View for CopilotButton { return Empty::new().into_any(); } - let settings = cx.global::(); - let theme = settings.theme.clone(); + let theme = theme::current(cx).clone(); let active = self.popup_menu.read(cx).visible(); let Some(copilot) = Copilot::global(cx) else { return Empty::new().into_any(); @@ -158,7 +157,7 @@ impl CopilotButton { Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach()); - cx.observe_global::(move |_, cx| cx.notify()) + cx.observe_global::(move |_, cx| cx.notify()) .detach(); Self { @@ -249,7 +248,7 @@ impl CopilotButton { menu_options.push(ContextMenuItem::Separator); - let icon_style = cx.global::().theme.copilot.out_link_icon.clone(); + let icon_style = theme::current(cx).copilot.out_link_icon.clone(); menu_options.push(ContextMenuItem::action( move |state: &mut MouseState, style: &theme::ContextMenuItem| { Flex::row() @@ -316,7 +315,7 @@ async fn configure_disabled_globs( let settings_editor = workspace .update(&mut cx, |_, cx| { create_and_open_local_file(&paths::SETTINGS, cx, || { - Settings::initial_user_settings_content(&assets::Assets) + settings::initial_user_settings_content(&assets::Assets) .as_ref() .into() }) diff --git a/crates/diagnostics/Cargo.toml b/crates/diagnostics/Cargo.toml index 9d455f520e..4e898cca0a 100644 --- a/crates/diagnostics/Cargo.toml +++ b/crates/diagnostics/Cargo.toml @@ -31,6 +31,7 @@ language = { path = "../language", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } workspace = { path = "../workspace", features = ["test-support"] } +theme = { path = "../theme", features = ["test-support"] } serde_json.workspace = true unindent.workspace = true diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 4ce1303ba4..458ca51ce3 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -20,7 +20,6 @@ use language::{ use lsp::LanguageServerId; use project::{DiagnosticSummary, Project, ProjectPath}; use serde_json::json; -use settings::Settings; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, @@ -30,6 +29,7 @@ use std::{ path::PathBuf, sync::Arc, }; +use theme::ThemeSettings; use util::TryFutureExt; use workspace::{ item::{BreadcrumbText, Item, ItemEvent, ItemHandle}, @@ -89,7 +89,7 @@ impl View for ProjectDiagnosticsEditor { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if self.path_states.is_empty() { - let theme = &cx.global::().theme.project_diagnostics; + let theme = &theme::current(cx).project_diagnostics; Label::new("No problems in workspace", theme.empty_message.clone()) .aligned() .contained() @@ -537,7 +537,7 @@ impl Item for ProjectDiagnosticsEditor { render_summary( &self.summary, &style.label.text, - &cx.global::().theme.project_diagnostics, + &theme::current(cx).project_diagnostics, ) } @@ -679,7 +679,7 @@ impl Item for ProjectDiagnosticsEditor { fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { let (message, highlights) = highlight_diagnostic_message(Vec::new(), &diagnostic.message); Arc::new(move |cx| { - let settings = cx.global::(); + let settings = settings::get_setting::(None, cx); let theme = &settings.theme.editor; let style = theme.diagnostic_header.clone(); let font_size = (style.text_scale_factor @@ -832,23 +832,23 @@ mod tests { "/test", json!({ "consts.rs": " - const a: i32 = 'a'; - const b: i32 = c; - " + const a: i32 = 'a'; + const b: i32 = c; + " .unindent(), "main.rs": " - fn main() { - let x = vec![]; - let y = vec![]; - a(x); - b(y); - // comment 1 - // comment 2 - c(y); - d(x); - } - " + fn main() { + let x = vec![]; + let y = vec![]; + a(x); + b(y); + // comment 1 + // comment 2 + c(y); + d(x); + } + " .unindent(), }), ) @@ -1496,8 +1496,8 @@ mod tests { fn init_test(cx: &mut TestAppContext) { cx.update(|cx| { - cx.set_global(Settings::test(cx)); cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); language::init(cx); client::init_settings(cx); workspace::init_settings(cx); diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index f0ceacc619..f84846eae1 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -7,7 +7,6 @@ use gpui::{ }; use language::Diagnostic; use lsp::LanguageServerId; -use settings::Settings; use workspace::{item::ItemHandle, StatusItemView, Workspace}; use crate::ProjectDiagnosticsEditor; @@ -92,13 +91,12 @@ impl View for DiagnosticIndicator { enum Summary {} enum Message {} - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); let in_progress = !self.in_progress_checks.is_empty(); let mut element = Flex::row().with_child( MouseEventHandler::::new(0, cx, |state, cx| { - let style = cx - .global::() - .theme + let theme = theme::current(cx); + let style = theme .workspace .status_bar .diagnostic_summary @@ -184,7 +182,7 @@ impl View for DiagnosticIndicator { .into_any(), ); - let style = &cx.global::().theme.workspace.status_bar; + let style = &theme::current(cx).workspace.status_bar; let item_spacing = style.item_spacing; if in_progress { diff --git a/crates/editor/src/blink_manager.rs b/crates/editor/src/blink_manager.rs index 303d0960ed..43d1ec6130 100644 --- a/crates/editor/src/blink_manager.rs +++ b/crates/editor/src/blink_manager.rs @@ -1,6 +1,6 @@ use crate::EditorSettings; use gpui::{Entity, ModelContext}; -use settings::Settings; +use settings::SettingsStore; use smol::Timer; use std::time::Duration; @@ -15,8 +15,8 @@ pub struct BlinkManager { impl BlinkManager { pub fn new(blink_interval: Duration, cx: &mut ModelContext) -> Self { - cx.observe_global::(move |this, cx| { - // Make sure we blink the cursors if the setting is re-enabled + // Make sure we blink the cursors if the setting is re-enabled + cx.observe_global::(move |this, cx| { this.blink_cursors(this.blink_epoch, cx) }) .detach(); diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 93e43f876c..05ff9886f1 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -993,7 +993,7 @@ mod tests { use crate::multi_buffer::MultiBuffer; use gpui::{elements::Empty, Element}; use rand::prelude::*; - use settings::Settings; + use settings::SettingsStore; use std::env; use util::RandomCharIter; @@ -1013,7 +1013,7 @@ mod tests { #[gpui::test] fn test_basic_blocks(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); let family_id = cx .font_cache() @@ -1189,7 +1189,7 @@ mod tests { #[gpui::test] fn test_blocks_on_wrapped_lines(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); let family_id = cx .font_cache() @@ -1239,7 +1239,7 @@ mod tests { #[gpui::test(iterations = 100)] fn test_random_blocks(cx: &mut gpui::AppContext, mut rng: StdRng) { - cx.set_global(Settings::test(cx)); + init_test(cx); let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) @@ -1647,6 +1647,11 @@ mod tests { } } + fn init_test(cx: &mut gpui::AppContext) { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + } + impl TransformBlock { fn as_custom(&self) -> Option<&Block> { match self { diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index bd3cd1a620..6ef1ebce1d 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1204,7 +1204,7 @@ mod tests { use crate::{MultiBuffer, ToPoint}; use collections::HashSet; use rand::prelude::*; - use settings::Settings; + use settings::SettingsStore; use std::{cmp::Reverse, env, mem, sync::Arc}; use sum_tree::TreeMap; use util::test::sample_text; @@ -1213,7 +1213,7 @@ mod tests { #[gpui::test] fn test_basic_folds(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); @@ -1286,7 +1286,7 @@ mod tests { #[gpui::test] fn test_adjacent_folds(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); let buffer = MultiBuffer::build_simple("abcdefghijkl", cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); @@ -1349,7 +1349,7 @@ mod tests { #[gpui::test] fn test_merging_folds_via_edit(cx: &mut gpui::AppContext) { - cx.set_global(Settings::test(cx)); + init_test(cx); let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); @@ -1400,7 +1400,7 @@ mod tests { #[gpui::test(iterations = 100)] fn test_random_folds(cx: &mut gpui::AppContext, mut rng: StdRng) { - cx.set_global(Settings::test(cx)); + init_test(cx); let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); @@ -1676,6 +1676,10 @@ mod tests { assert_eq!(snapshot.buffer_rows(3).collect::>(), [Some(6)]); } + fn init_test(cx: &mut gpui::AppContext) { + cx.set_global(SettingsStore::test(cx)); + } + impl FoldMap { fn merged_fold_ranges(&self) -> Vec> { let buffer = self.buffer.lock().clone(); diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index f48efc76f4..eac903d0af 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -578,7 +578,7 @@ mod tests { use crate::{display_map::fold_map::FoldMap, MultiBuffer}; use gpui::AppContext; use rand::{prelude::StdRng, Rng}; - use settings::Settings; + use settings::SettingsStore; use std::{ env, ops::{Bound, RangeBounds}, @@ -631,7 +631,8 @@ mod tests { #[gpui::test(iterations = 100)] fn test_random_suggestions(cx: &mut AppContext, mut rng: StdRng) { - cx.set_global(Settings::test(cx)); + init_test(cx); + let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); @@ -834,6 +835,11 @@ mod tests { } } + fn init_test(cx: &mut AppContext) { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + } + impl SuggestionMap { pub fn randomly_mutate( &self, diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index e1de4fce77..478eaf4c7e 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1043,16 +1043,16 @@ mod tests { }; use gpui::test::observe; use rand::prelude::*; - use settings::Settings; + use settings::SettingsStore; use smol::stream::StreamExt; use std::{cmp, env, num::NonZeroU32}; use text::Rope; #[gpui::test(iterations = 100)] async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) { - cx.update(|cx| cx.set_global(Settings::test(cx))); + init_test(cx); + cx.foreground().set_block_on_ticks(0..=50); - cx.foreground().forbid_parking(); let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); @@ -1287,6 +1287,14 @@ mod tests { wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty())); } + fn init_test(cx: &mut gpui::TestAppContext) { + cx.foreground().forbid_parking(); + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + }); + } + fn wrap_text( unwrapped_text: &str, wrap_width: Option, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 915e041100..78844ac332 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -73,7 +73,7 @@ use scroll::{ }; use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection}; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore}; +use settings::SettingsStore; use smallvec::SmallVec; use snippet::Snippet; use std::{ @@ -88,7 +88,7 @@ use std::{ time::{Duration, Instant}, }; pub use sum_tree::Bias; -use theme::{DiagnosticStyle, Theme}; +use theme::{DiagnosticStyle, Theme, ThemeSettings}; use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{ItemNavHistory, ViewId, Workspace}; @@ -1237,8 +1237,8 @@ impl Editor { ) -> Self { let editor_view_id = cx.view_id(); let display_map = cx.add_model(|cx| { - let settings = cx.global::(); - let style = build_style(&*settings, get_field_editor_theme.as_deref(), None, cx); + let settings = settings::get_setting::(None, cx); + let style = build_style(settings, get_field_editor_theme.as_deref(), None, cx); DisplayMap::new( buffer.clone(), style.text.font_id, @@ -1319,7 +1319,7 @@ impl Editor { cx.subscribe(&buffer, Self::on_buffer_event), cx.observe(&display_map, Self::on_display_map_changed), cx.observe(&blink_manager, |_, _, cx| cx.notify()), - cx.observe_global::(Self::settings_changed), + cx.observe_global::(Self::settings_changed), ], }; @@ -1418,7 +1418,7 @@ impl Editor { fn style(&self, cx: &AppContext) -> EditorStyle { build_style( - cx.global::(), + settings::get_setting::(None, cx), self.get_field_editor_theme.as_deref(), self.override_text_style.as_deref(), cx, @@ -6561,8 +6561,8 @@ impl Editor { let buffer = &snapshot.buffer_snapshot; let start = buffer.anchor_before(0); let end = buffer.anchor_after(buffer.len()); - let theme = cx.global::().theme.as_ref(); - self.background_highlights_in_range(start..end, &snapshot, theme) + let theme = theme::current(cx); + self.background_highlights_in_range(start..end, &snapshot, theme.as_ref()) } fn document_highlights_for_position<'a>( @@ -6985,7 +6985,7 @@ impl Editor { let mut lines = Vec::new(); let mut line: VecDeque = VecDeque::new(); - let theme = &cx.global::().theme.editor.syntax; + let theme = &theme::current(cx).editor.syntax; for chunk in chunks { let highlight = chunk.syntax_highlight_id.and_then(|id| id.name(theme)); @@ -7407,7 +7407,7 @@ impl View for Editor { } fn build_style( - settings: &Settings, + settings: &ThemeSettings, get_field_editor_theme: Option<&GetFieldEditorTheme>, override_text_style: Option<&OverrideTextStyle>, cx: &AppContext, @@ -7607,7 +7607,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend } Arc::new(move |cx: &mut BlockContext| { - let settings = cx.global::(); + let settings = settings::get_setting::(None, cx); let theme = &settings.theme.editor; let style = diagnostic_style(diagnostic.severity, is_valid, theme); let font_size = (style.text_scale_factor diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index cf72f0286c..b61c2a780f 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5452,7 +5452,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) { let mut highlighted_ranges = editor.background_highlights_in_range( anchor_range(Point::new(3, 4)..Point::new(7, 4)), &snapshot, - cx.global::().theme.as_ref(), + theme::current(cx).as_ref(), ); // Enforce a consistent ordering based on color without relying on the ordering of the // highlight's `TypeId` which is non-deterministic. @@ -5482,7 +5482,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) { editor.background_highlights_in_range( anchor_range(Point::new(5, 6)..Point::new(6, 4)), &snapshot, - cx.global::().theme.as_ref(), + theme::current(cx).as_ref(), ), &[( DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5), @@ -6681,7 +6681,7 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC cx.update(|cx| { cx.set_global(SettingsStore::test(cx)); - cx.set_global(Settings::test(cx)); + theme::init((), cx); client::init_settings(cx); language::init(cx); Project::init_settings(cx); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index bc5998cc29..56a4f4ad18 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -40,7 +40,6 @@ use language::{ Selection, }; use project::ProjectPath; -use settings::Settings; use smallvec::SmallVec; use std::{ borrow::Cow, @@ -611,7 +610,7 @@ impl EditorElement { layout: &mut LayoutState, cx: &mut ViewContext, ) { - let diff_style = &cx.global::().theme.editor.diff.clone(); + let diff_style = &theme::current(cx).editor.diff.clone(); let line_height = layout.position_map.line_height; let scroll_position = layout.position_map.snapshot.scroll_position(); @@ -1417,7 +1416,7 @@ impl EditorElement { editor: &mut Editor, cx: &mut LayoutContext, ) -> (f32, Vec) { - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); let scroll_x = snapshot.scroll_anchor.offset.x(); let (fixed_blocks, non_fixed_blocks) = snapshot .blocks_in_range(rows.clone()) @@ -1936,11 +1935,11 @@ impl Element for EditorElement { let is_singleton = editor.is_singleton(cx); let highlighted_rows = editor.highlighted_rows(); - let theme = cx.global::().theme.as_ref(); + let theme = theme::current(cx); let highlighted_ranges = editor.background_highlights_in_range( start_anchor..end_anchor, &snapshot.display_snapshot, - theme, + theme.as_ref(), ); fold_ranges.extend( diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 29d32e4a43..24f09f1082 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -12,7 +12,6 @@ use gpui::{ }; use language::{Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry}; use project::{HoverBlock, HoverBlockKind, Project}; -use settings::Settings; use std::{ops::Range, sync::Arc, time::Duration}; use util::TryFutureExt; @@ -654,7 +653,7 @@ impl DiagnosticPopover { _ => style.hover_popover.container, }; - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); MouseEventHandler::::new(0, cx, |_, _| { text.with_soft_wrap(true) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 9e122cc63d..988f263337 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -16,7 +16,6 @@ use language::{ }; use project::{FormatTrigger, Item as _, Project, ProjectPath}; use rpc::proto::{self, update_view}; -use settings::Settings; use smallvec::SmallVec; use std::{ borrow::Cow, @@ -1116,7 +1115,7 @@ impl View for CursorPosition { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(position) = self.position { - let theme = &cx.global::().theme.workspace.status_bar; + let theme = &theme::current(cx).workspace.status_bar; let mut text = format!( "{}{FILE_ROW_COLUMN_DELIMITER}{}", position.row + 1, diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index 04cf4611f1..a52647fb55 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -2,7 +2,6 @@ use crate::{Anchor, DisplayPoint, Editor, EditorSnapshot, SelectPhase}; use gpui::{Task, ViewContext}; use language::{Bias, ToOffset}; use project::LocationLink; -use settings::Settings; use std::ops::Range; use util::TryFutureExt; @@ -210,7 +209,7 @@ pub fn show_link_definition( }); // Highlight symbol using theme link definition highlight style - let style = cx.global::().theme.editor.link_definition; + let style = theme::current(cx).editor.link_definition; this.highlight_text::( vec![highlight_range], style, diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 311616cfe0..6c9bd6cb4f 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -367,10 +367,9 @@ pub fn split_display_range_by_lines( #[cfg(test)] mod tests { - use settings::{Settings, SettingsStore}; - use super::*; use crate::{test::marked_display_snapshot, Buffer, DisplayMap, ExcerptRange, MultiBuffer}; + use settings::SettingsStore; #[gpui::test] fn test_previous_word_start(cx: &mut gpui::AppContext) { @@ -703,7 +702,7 @@ mod tests { fn init_test(cx: &mut gpui::AppContext) { cx.set_global(SettingsStore::test(cx)); - cx.set_global(Settings::test(cx)); + theme::init((), cx); language::init(cx); crate::init(cx); } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 1e07bba065..99df8ce796 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -3806,10 +3806,9 @@ mod tests { use gpui::{AppContext, TestAppContext}; use language::{Buffer, Rope}; use rand::prelude::*; - use settings::Settings; + use settings::SettingsStore; use std::{env, rc::Rc}; use unindent::Unindent; - use util::test::sample_text; #[gpui::test] @@ -5055,7 +5054,8 @@ mod tests { #[gpui::test] fn test_history(cx: &mut AppContext) { - cx.set_global(Settings::test(cx)); + cx.set_global(SettingsStore::test(cx)); + let buffer_1 = cx.add_model(|cx| Buffer::new(0, "1234", cx)); let buffer_2 = cx.add_model(|cx| Buffer::new(0, "5678", cx)); let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index 9f2ab4ffac..0fe49d4d04 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -37,6 +37,7 @@ impl<'a> EditorLspTestContext<'a> { let app_state = cx.update(AppState::test); cx.update(|cx| { + theme::init((), cx); language::init(cx); crate::init(cx); pane::init(cx); diff --git a/crates/feedback/src/deploy_feedback_button.rs b/crates/feedback/src/deploy_feedback_button.rs index b464d00887..9133174475 100644 --- a/crates/feedback/src/deploy_feedback_button.rs +++ b/crates/feedback/src/deploy_feedback_button.rs @@ -3,7 +3,6 @@ use gpui::{ platform::{CursorStyle, MouseButton}, Entity, View, ViewContext, WeakViewHandle, }; -use settings::Settings; use workspace::{item::ItemHandle, StatusItemView, Workspace}; use crate::feedback_editor::{FeedbackEditor, GiveFeedback}; @@ -33,7 +32,7 @@ impl View for DeployFeedbackButton { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { let active = self.active; - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); Stack::new() .with_child( MouseEventHandler::::new(0, cx, |state, _| { diff --git a/crates/feedback/src/feedback_info_text.rs b/crates/feedback/src/feedback_info_text.rs index 9aee4e0e68..5852cd9a61 100644 --- a/crates/feedback/src/feedback_info_text.rs +++ b/crates/feedback/src/feedback_info_text.rs @@ -3,7 +3,6 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyElement, Element, Entity, View, ViewContext, ViewHandle, }; -use settings::Settings; use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView}; use crate::{feedback_editor::FeedbackEditor, open_zed_community_repo, OpenZedCommunityRepo}; @@ -30,7 +29,7 @@ impl View for FeedbackInfoText { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); Flex::row() .with_child( diff --git a/crates/feedback/src/submit_feedback_button.rs b/crates/feedback/src/submit_feedback_button.rs index ccd58c3dc9..56bc235570 100644 --- a/crates/feedback/src/submit_feedback_button.rs +++ b/crates/feedback/src/submit_feedback_button.rs @@ -5,7 +5,6 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyElement, AppContext, Element, Entity, Task, View, ViewContext, ViewHandle, }; -use settings::Settings; use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView}; pub fn init(cx: &mut AppContext) { @@ -46,7 +45,7 @@ impl View for SubmitFeedbackButton { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); enum SubmitFeedbackButton {} MouseEventHandler::::new(0, cx, |state, _| { let style = theme.feedback.submit_button.style_for(state, false); diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 37ab4218a8..b318f1d167 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -5,7 +5,6 @@ use gpui::{ }; use picker::{Picker, PickerDelegate}; use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId}; -use settings::Settings; use std::{ path::Path, sync::{ @@ -324,8 +323,8 @@ impl PickerDelegate for FileFinderDelegate { cx: &AppContext, ) -> AnyElement> { let path_match = &self.matches[ix]; - let settings = cx.global::(); - let style = settings.theme.picker.item.style_for(mouse_state, selected); + let theme = theme::current(cx); + let style = theme.picker.item.style_for(mouse_state, selected); let (file_name, file_name_positions, full_path, full_path_positions) = self.labels_for_match(path_match); Flex::column() @@ -909,6 +908,7 @@ mod tests { cx.foreground().forbid_parking(); cx.update(|cx| { let state = AppState::test(cx); + theme::init((), cx); language::init(cx); super::init(cx); editor::init(cx); diff --git a/crates/go_to_line/Cargo.toml b/crates/go_to_line/Cargo.toml index 8f99aa366c..441f7ef7e4 100644 --- a/crates/go_to_line/Cargo.toml +++ b/crates/go_to_line/Cargo.toml @@ -16,4 +16,5 @@ settings = { path = "../settings" } text = { path = "../text" } workspace = { path = "../workspace" } postage.workspace = true +theme = { path = "../theme" } util = { path = "../util" } diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 967f17b794..0b41ee6dca 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -6,7 +6,6 @@ use gpui::{ View, ViewContext, ViewHandle, }; use menu::{Cancel, Confirm}; -use settings::Settings; use text::{Bias, Point}; use util::paths::FILE_ROW_COLUMN_DELIMITER; use workspace::{Modal, Workspace}; @@ -151,7 +150,7 @@ impl View for GoToLine { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = &cx.global::().theme.picker; + let theme = &theme::current(cx).picker; let label = format!( "{}{FILE_ROW_COLUMN_DELIMITER}{} of {} lines", diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 55afd2f572..4d33366a71 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -254,6 +254,7 @@ impl settings::Setting for AllLanguageSettings { fn json_schema( generator: &mut schemars::gen::SchemaGenerator, params: &settings::SettingsJsonSchemaParams, + _: &AppContext, ) -> schemars::schema::RootSchema { let mut root_schema = generator.root_schema_for::(); diff --git a/crates/language_selector/src/active_buffer_language.rs b/crates/language_selector/src/active_buffer_language.rs index 425f4c8dd7..2c78b89f31 100644 --- a/crates/language_selector/src/active_buffer_language.rs +++ b/crates/language_selector/src/active_buffer_language.rs @@ -4,7 +4,6 @@ use gpui::{ platform::{CursorStyle, MouseButton}, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, }; -use settings::Settings; use std::sync::Arc; use workspace::{item::ItemHandle, StatusItemView, Workspace}; @@ -55,7 +54,7 @@ impl View for ActiveBufferLanguage { }; MouseEventHandler::::new(0, cx, |state, cx| { - let theme = &cx.global::().theme.workspace.status_bar; + let theme = &theme::current(cx).workspace.status_bar; let style = theme.active_language.style_for(state, false); Label::new(active_language_text, style.text.clone()) .contained() diff --git a/crates/language_selector/src/language_selector.rs b/crates/language_selector/src/language_selector.rs index fd43111443..817901cd3a 100644 --- a/crates/language_selector/src/language_selector.rs +++ b/crates/language_selector/src/language_selector.rs @@ -8,7 +8,6 @@ use gpui::{actions, elements::*, AppContext, ModelHandle, MouseState, ViewContex use language::{Buffer, LanguageRegistry}; use picker::{Picker, PickerDelegate, PickerEvent}; use project::Project; -use settings::Settings; use std::sync::Arc; use util::ResultExt; use workspace::Workspace; @@ -179,8 +178,7 @@ impl PickerDelegate for LanguageSelectorDelegate { selected: bool, cx: &AppContext, ) -> AnyElement> { - let settings = cx.global::(); - let theme = &settings.theme; + let theme = theme::current(cx); let mat = &self.matches[ix]; let style = theme.picker.item.style_for(mouse_state, selected); let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name()); diff --git a/crates/lsp_log/src/lsp_log.rs b/crates/lsp_log/src/lsp_log.rs index 0efcd3afc8..db41c6ff4d 100644 --- a/crates/lsp_log/src/lsp_log.rs +++ b/crates/lsp_log/src/lsp_log.rs @@ -13,7 +13,6 @@ use gpui::{ }; use language::{Buffer, LanguageServerId, LanguageServerName}; use project::{Project, WorktreeId}; -use settings::Settings; use std::{borrow::Cow, sync::Arc}; use theme::{ui, Theme}; use workspace::{ @@ -304,7 +303,7 @@ impl View for LspLogToolbarItemView { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let Some(log_view) = self.log_view.as_ref() else { return Empty::new().into_any() }; let project = self.project.read(cx); let log_view = log_view.read(cx); diff --git a/crates/outline/Cargo.toml b/crates/outline/Cargo.toml index 91e5011b2a..95272b063e 100644 --- a/crates/outline/Cargo.toml +++ b/crates/outline/Cargo.toml @@ -16,7 +16,9 @@ language = { path = "../language" } picker = { path = "../picker" } settings = { path = "../settings" } text = { path = "../text" } +theme = { path = "../theme" } workspace = { path = "../workspace" } + ordered-float.workspace = true postage.workspace = true smol.workspace = true diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 6ecaf370e4..1e364f5fc8 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -10,7 +10,6 @@ use gpui::{ use language::Outline; use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate, PickerEvent}; -use settings::Settings; use std::{ cmp::{self, Reverse}, sync::Arc, @@ -34,7 +33,7 @@ pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext().theme.editor.syntax.as_ref())); + .outline(Some(theme::current(cx).editor.syntax.as_ref())); if let Some(outline) = outline { workspace.toggle_modal(cx, |_, cx| { cx.add_view(|cx| { @@ -204,9 +203,9 @@ impl PickerDelegate for OutlineViewDelegate { selected: bool, cx: &AppContext, ) -> AnyElement> { - let settings = cx.global::(); + let theme = theme::current(cx); + let style = theme.picker.item.style_for(mouse_state, selected); let string_match = &self.matches[ix]; - let style = settings.theme.picker.item.style_for(mouse_state, selected); let outline_item = &self.outline.items[string_match.candidate_id]; Text::new(outline_item.text.clone(), style.label.text.clone()) diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 01749ccf84..69f16e4949 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -57,7 +57,7 @@ impl View for Picker { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = (self.theme.lock())(&cx.global::().theme); + let theme = (self.theme.lock())(theme::current(cx).as_ref()); let query = self.query(cx); let match_count = self.delegate.match_count(); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 14359fa288..a487b23716 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -47,7 +47,7 @@ use project_settings::ProjectSettings; use rand::prelude::*; use search::SearchQuery; use serde::Serialize; -use settings::{Settings, SettingsStore}; +use settings::SettingsStore; use sha2::{Digest, Sha256}; use similar::{ChangeTag, TextDiff}; use std::{ @@ -610,12 +610,6 @@ impl Project { root_paths: impl IntoIterator, cx: &mut gpui::TestAppContext, ) -> ModelHandle { - if !cx.read(|cx| cx.has_global::()) { - cx.update(|cx| { - cx.set_global(Settings::test(cx)); - }); - } - let mut languages = LanguageRegistry::test(); languages.set_executor(cx.background()); let http_client = util::http::FakeHttpClient::with_404_response(); @@ -2137,7 +2131,7 @@ impl Project { let (mut settings_changed_tx, mut settings_changed_rx) = watch::channel(); let _ = postage::stream::Stream::try_recv(&mut settings_changed_rx); - let settings_observation = cx.observe_global::(move |_, _| { + let settings_observation = cx.observe_global::(move |_, _| { *settings_changed_tx.borrow_mut() = (); }); cx.spawn_weak(|this, mut cx| async move { diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 7325e69f3f..683ce8ad06 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -20,7 +20,6 @@ use project::{ repository::GitFileStatus, Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId, }; -use settings::Settings; use std::{ cmp::Ordering, collections::{hash_map, HashMap}, @@ -1113,7 +1112,7 @@ impl ProjectPanel { ComponentHost::new(FileName::new( details.filename.clone(), details.git_status, - FileName::style(style.text.clone(), &cx.global::().theme), + FileName::style(style.text.clone(), &theme::current(cx)), )) .contained() .with_margin_left(style.icon_spacing) @@ -1223,7 +1222,7 @@ impl ProjectPanel { let row_container_style = theme.dragged_entry.container; move |_, cx: &mut ViewContext| { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); Self::render_entry_visual_element( &details, None, @@ -1246,7 +1245,7 @@ impl View for ProjectPanel { fn render(&mut self, cx: &mut gpui::ViewContext) -> gpui::AnyElement { enum ProjectPanel {} - let theme = &cx.global::().theme.project_panel; + let theme = &theme::current(cx).project_panel; let mut container_style = theme.container; let padding = std::mem::take(&mut container_style.padding); let last_worktree_root_id = self.last_worktree_root_id; @@ -1265,7 +1264,7 @@ impl View for ProjectPanel { .sum(), cx, move |this, range, items, cx| { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let mut dragged_entry_destination = this.dragged_entry_destination.clone(); this.for_each_visible_entry(range, cx, |id, details, cx| { @@ -1302,8 +1301,7 @@ impl View for ProjectPanel { .with_child( MouseEventHandler::::new(2, cx, { let button_style = theme.open_project_button.clone(); - let context_menu_item_style = - cx.global::().theme.context_menu.item.clone(); + let context_menu_item_style = theme::current(cx).context_menu.item.clone(); move |state, cx| { let button_style = button_style.style_for(state, false).clone(); let context_menu_item = @@ -1952,6 +1950,7 @@ mod tests { cx.foreground().forbid_parking(); cx.update(|cx| { cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); language::init(cx); editor::init_settings(cx); workspace::init_settings(cx); diff --git a/crates/project_symbols/Cargo.toml b/crates/project_symbols/Cargo.toml index be2510c895..7e23e42b26 100644 --- a/crates/project_symbols/Cargo.toml +++ b/crates/project_symbols/Cargo.toml @@ -17,7 +17,9 @@ project = { path = "../project" } text = { path = "../text" } settings = { path = "../settings" } workspace = { path = "../workspace" } +theme = { path = "../theme" } util = { path = "../util" } + anyhow.workspace = true ordered-float.workspace = true postage.workspace = true @@ -30,4 +32,5 @@ gpui = { path = "../gpui", features = ["test-support"] } language = { path = "../language", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } project = { path = "../project", features = ["test-support"] } +theme = { path = "../theme", features = ["test-support"] } workspace = { path = "../workspace", features = ["test-support"] } diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 74edd45c21..992283df01 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -9,7 +9,6 @@ use gpui::{ use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate, PickerEvent}; use project::{Project, Symbol}; -use settings::Settings; use std::{borrow::Cow, cmp::Reverse, sync::Arc}; use util::ResultExt; use workspace::Workspace; @@ -195,12 +194,13 @@ impl PickerDelegate for ProjectSymbolsDelegate { selected: bool, cx: &AppContext, ) -> AnyElement> { - let string_match = &self.matches[ix]; - let settings = cx.global::(); - let style = &settings.theme.picker.item; + let theme = theme::current(cx); + let style = &theme.picker.item; let current_style = style.style_for(mouse_state, selected); + + let string_match = &self.matches[ix]; let symbol = &self.symbols[string_match.candidate_id]; - let syntax_runs = styled_runs_for_code_label(&symbol.label, &settings.theme.editor.syntax); + let syntax_runs = styled_runs_for_code_label(&symbol.label, &theme.editor.syntax); let mut path = symbol.path.path.to_string_lossy(); if self.show_worktree_root_name { @@ -371,8 +371,8 @@ mod tests { fn init_test(cx: &mut TestAppContext) { cx.foreground().forbid_parking(); cx.update(|cx| { - cx.set_global(Settings::test(cx)); cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); language::init(cx); Project::init_settings(cx); workspace::init_settings(cx); diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index 968ae8e9a4..d9e7546f34 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -18,6 +18,7 @@ picker = { path = "../picker" } settings = { path = "../settings" } text = { path = "../text" } util = { path = "../util"} +theme = { path = "../theme" } workspace = { path = "../workspace" } ordered-float.workspace = true diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 644e74d878..a1dc8982c7 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -10,7 +10,6 @@ use gpui::{ use highlighted_workspace_location::HighlightedWorkspaceLocation; use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate, PickerEvent}; -use settings::Settings; use std::sync::Arc; use workspace::{ notifications::simple_message_notification::MessageNotification, Workspace, WorkspaceLocation, @@ -173,9 +172,10 @@ impl PickerDelegate for RecentProjectsDelegate { selected: bool, cx: &gpui::AppContext, ) -> AnyElement> { - let settings = cx.global::(); + let theme = theme::current(cx); + let style = theme.picker.item.style_for(mouse_state, selected); + let string_match = &self.matches[ix]; - let style = settings.theme.picker.item.style_for(mouse_state, selected); let highlighted_location = HighlightedWorkspaceLocation::new( &string_match, diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 38f3894743..87a8b265fb 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -13,7 +13,6 @@ use gpui::{ }; use project::search::SearchQuery; use serde::Deserialize; -use settings::Settings; use std::{any::Any, sync::Arc}; use util::ResultExt; use workspace::{ @@ -93,7 +92,7 @@ impl View for BufferSearchBar { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let editor_container = if self.query_contains_error { theme.search.invalid_editor } else { @@ -324,16 +323,12 @@ impl BufferSearchBar { return None; } - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); let is_active = self.is_search_option_enabled(option); Some( MouseEventHandler::::new(option as usize, cx, |state, cx| { - let style = cx - .global::() - .theme - .search - .option_button - .style_for(state, is_active); + let theme = theme::current(cx); + let style = theme.search.option_button.style_for(state, is_active); Label::new(icon, style.text.clone()) .contained() .with_style(style.container) @@ -371,16 +366,12 @@ impl BufferSearchBar { tooltip = "Select Next Match"; } }; - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); enum NavButton {} MouseEventHandler::::new(direction as usize, cx, |state, cx| { - let style = cx - .global::() - .theme - .search - .option_button - .style_for(state, false); + let theme = theme::current(cx); + let style = theme.search.option_button.style_for(state, false); Label::new(icon, style.text.clone()) .contained() .with_style(style.container) @@ -408,7 +399,7 @@ impl BufferSearchBar { cx: &mut ViewContext, ) -> AnyElement { let tooltip = "Dismiss Buffer Search"; - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); enum CloseButton {} MouseEventHandler::::new(0, cx, |state, _| { diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 0d020da570..17f86c153c 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -17,7 +17,6 @@ use gpui::{ }; use menu::Confirm; use project::{search::SearchQuery, Project}; -use settings::Settings; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, @@ -195,7 +194,7 @@ impl View for ProjectSearchView { if model.match_ranges.is_empty() { enum Status {} - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let text = if self.query_editor.read(cx).text(cx).is_empty() { "" } else if model.pending_search.is_some() { @@ -903,16 +902,12 @@ impl ProjectSearchBar { tooltip = "Select Next Match"; } }; - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); enum NavButton {} MouseEventHandler::::new(direction as usize, cx, |state, cx| { - let style = &cx - .global::() - .theme - .search - .option_button - .style_for(state, false); + let theme = theme::current(cx); + let style = theme.search.option_button.style_for(state, false); Label::new(icon, style.text.clone()) .contained() .with_style(style.container) @@ -939,15 +934,11 @@ impl ProjectSearchBar { option: SearchOption, cx: &mut ViewContext, ) -> AnyElement { - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); let is_active = self.is_option_enabled(option, cx); MouseEventHandler::::new(option as usize, cx, |state, cx| { - let style = &cx - .global::() - .theme - .search - .option_button - .style_for(state, is_active); + let theme = theme::current(cx); + let style = theme.search.option_button.style_for(state, is_active); Label::new(icon, style.text.clone()) .contained() .with_style(style.container) @@ -992,7 +983,7 @@ impl View for ProjectSearchBar { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(search) = self.active_project_search.as_ref() { let search = search.read(cx); - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let query_container_style = if search.panels_with_errors.contains(&InputPanel::Query) { theme.search.invalid_editor } else { @@ -1154,6 +1145,7 @@ pub mod tests { use serde_json::json; use settings::SettingsStore; use std::sync::Arc; + use theme::ThemeSettings; #[gpui::test] async fn test_project_search(deterministic: Arc, cx: &mut TestAppContext) { @@ -1282,9 +1274,12 @@ pub mod tests { cx.set_global(SettingsStore::test(cx)); cx.set_global(ActiveSearches::default()); - let mut settings = Settings::test(cx); - settings.theme = Arc::new(theme); - cx.set_global(settings); + theme::init((), cx); + cx.update_global::(|store, _| { + let mut settings = store.get::(None).clone(); + settings.theme = Arc::new(theme); + store.override_global(settings) + }); language::init(cx); client::init_settings(cx); diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 6d2b4ded91..2cb6637ead 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -9,7 +9,7 @@ path = "src/settings.rs" doctest = false [features] -test-support = ["theme/test-support", "gpui/test-support", "fs/test-support"] +test-support = ["gpui/test-support", "fs/test-support"] [dependencies] assets = { path = "../assets" } @@ -17,12 +17,11 @@ collections = { path = "../collections" } gpui = { path = "../gpui" } sqlez = { path = "../sqlez" } fs = { path = "../fs" } -anyhow.workspace = true -futures.workspace = true -theme = { path = "../theme" } staff_mode = { path = "../staff_mode" } util = { path = "../util" } +anyhow.workspace = true +futures.workspace = true glob.workspace = true json_comments = "0.2" lazy_static.workspace = true @@ -39,7 +38,6 @@ tree-sitter-json = "*" [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } fs = { path = "../fs", features = ["test-support"] } -theme = { path = "../theme", features = ["test-support"] } pretty_assertions = "1.3.0" unindent.workspace = true diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index bd1104105f..e4784d61ac 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -3,23 +3,10 @@ mod keymap_file; mod settings_file; mod settings_store; -use anyhow::Result; -use gpui::{ - font_cache::{FamilyId, FontCache}, - fonts, AppContext, AssetSource, -}; -use schemars::{ - gen::SchemaGenerator, - schema::{InstanceType, Schema, SchemaObject}, - JsonSchema, -}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::{borrow::Cow, str, sync::Arc}; -use theme::{Theme, ThemeRegistry}; -use util::ResultExt as _; +use std::{borrow::Cow, str}; pub use font_size::{adjust_font_size_delta, font_size_for_setting}; +use gpui::AssetSource; pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; pub use settings_file::*; pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore}; @@ -27,193 +14,9 @@ pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore}; pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json"; pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json"; -#[derive(Clone)] -pub struct Settings { - pub buffer_font_family_name: String, - pub buffer_font_features: fonts::Features, - pub buffer_font_family: FamilyId, - pub buffer_font_size: f32, - pub theme: Arc, -} - -impl Setting for Settings { - const KEY: Option<&'static str> = None; - - type FileContent = SettingsFileContent; - - fn load( - defaults: &Self::FileContent, - user_values: &[&Self::FileContent], - cx: &AppContext, - ) -> Result { - let buffer_font_features = defaults.buffer_font_features.clone().unwrap(); - let themes = cx.global::>(); - - let mut this = Self { - buffer_font_family: cx - .font_cache() - .load_family( - &[defaults.buffer_font_family.as_ref().unwrap()], - &buffer_font_features, - ) - .unwrap(), - buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(), - buffer_font_features, - buffer_font_size: defaults.buffer_font_size.unwrap(), - theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), - }; - - for value in user_values.into_iter().copied().cloned() { - this.set_user_settings(value, themes.as_ref(), cx.font_cache()); - } - - Ok(this) - } - - fn json_schema( - generator: &mut SchemaGenerator, - params: &SettingsJsonSchemaParams, - ) -> schemars::schema::RootSchema { - let mut root_schema = generator.root_schema_for::(); - - // Create a schema for a theme name. - let theme_name_schema = SchemaObject { - instance_type: Some(InstanceType::String.into()), - enum_values: Some( - params - .theme_names - .iter() - .cloned() - .map(Value::String) - .collect(), - ), - ..Default::default() - }; - - root_schema - .definitions - .extend([("ThemeName".into(), theme_name_schema.into())]); - - root_schema - .schema - .object - .as_mut() - .unwrap() - .properties - .extend([( - "theme".to_owned(), - Schema::new_ref("#/definitions/ThemeName".into()), - )]); - - root_schema - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] -pub struct SettingsFileContent { - #[serde(default)] - pub buffer_font_family: Option, - #[serde(default)] - pub buffer_font_size: Option, - #[serde(default)] - pub buffer_font_features: Option, - #[serde(default)] - pub theme: Option, -} - -impl Settings { - pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> { - match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() { - Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()), - Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()), - } - } - - /// Fill out the settings corresponding to the default.json file, overrides will be set later - pub fn defaults( - assets: impl AssetSource, - font_cache: &FontCache, - themes: &ThemeRegistry, - ) -> Self { - let defaults: SettingsFileContent = settings_store::parse_json_with_comments( - str::from_utf8(assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap().as_ref()).unwrap(), - ) - .unwrap(); - - let buffer_font_features = defaults.buffer_font_features.unwrap(); - Self { - buffer_font_family: font_cache - .load_family( - &[defaults.buffer_font_family.as_ref().unwrap()], - &buffer_font_features, - ) - .unwrap(), - buffer_font_family_name: defaults.buffer_font_family.unwrap(), - buffer_font_features, - buffer_font_size: defaults.buffer_font_size.unwrap(), - theme: themes.get(&defaults.theme.unwrap()).unwrap(), - } - } - - // Fill out the overrride and etc. settings from the user's settings.json - fn set_user_settings( - &mut self, - data: SettingsFileContent, - theme_registry: &ThemeRegistry, - font_cache: &FontCache, - ) { - let mut family_changed = false; - if let Some(value) = data.buffer_font_family { - self.buffer_font_family_name = value; - family_changed = true; - } - if let Some(value) = data.buffer_font_features { - self.buffer_font_features = value; - family_changed = true; - } - if family_changed { - if let Some(id) = font_cache - .load_family(&[&self.buffer_font_family_name], &self.buffer_font_features) - .log_err() - { - self.buffer_font_family = id; - } - } - - if let Some(value) = &data.theme { - if let Some(theme) = theme_registry.get(value).log_err() { - self.theme = theme; - } - } - - merge(&mut self.buffer_font_size, data.buffer_font_size); - } - - #[cfg(any(test, feature = "test-support"))] - pub fn test(cx: &gpui::AppContext) -> Settings { - Settings { - buffer_font_family_name: "Monaco".to_string(), - buffer_font_features: Default::default(), - buffer_font_family: cx - .font_cache() - .load_family(&["Monaco"], &Default::default()) - .unwrap(), - buffer_font_size: 14., - theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), - } - } - - #[cfg(any(test, feature = "test-support"))] - pub fn test_async(cx: &mut gpui::TestAppContext) { - cx.update(|cx| { - let settings = Self::test(cx); - cx.set_global(settings); - }); - } -} - -fn merge(target: &mut T, value: Option) { - if let Some(value) = value { - *target = value; +pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> { + match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() { + Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()), + Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()), } } diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index cf1787d7c0..ed27416512 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -1,7 +1,4 @@ -use crate::{ - settings_store::parse_json_with_comments, settings_store::SettingsStore, Setting, Settings, - DEFAULT_SETTINGS_ASSET_PATH, -}; +use crate::{settings_store::SettingsStore, Setting, DEFAULT_SETTINGS_ASSET_PATH}; use anyhow::Result; use assets::Assets; use fs::Fs; @@ -34,16 +31,21 @@ pub fn default_settings() -> Cow<'static, str> { } } +#[cfg(any(test, feature = "test-support"))] +pub const EMPTY_THEME_NAME: &'static str = "empty-theme"; + #[cfg(any(test, feature = "test-support"))] pub fn test_settings() -> String { - let mut value = - parse_json_with_comments::(default_settings().as_ref()).unwrap(); + let mut value = crate::settings_store::parse_json_with_comments::( + default_settings().as_ref(), + ) + .unwrap(); util::merge_non_null_json_value_into( serde_json::json!({ "buffer_font_family": "Courier", "buffer_font_features": {}, "buffer_font_size": 14, - "theme": theme::EMPTY_THEME_NAME, + "theme": EMPTY_THEME_NAME, }), &mut value, ); @@ -85,10 +87,6 @@ pub fn handle_settings_file_changes( store .set_user_settings(&user_settings_content, cx) .log_err(); - - // TODO - remove the Settings global, use the SettingsStore instead. - store.register_setting::(cx); - cx.set_global(store.get::(None).clone()); }); cx.spawn(move |mut cx| async move { while let Some(user_settings_content) = user_settings_file_rx.next().await { @@ -97,9 +95,6 @@ pub fn handle_settings_file_changes( store .set_user_settings(&user_settings_content, cx) .log_err(); - - // TODO - remove the Settings global, use the SettingsStore instead. - cx.set_global(store.get::(None).clone()); }); }); } @@ -113,7 +108,7 @@ async fn load_settings(fs: &Arc) -> Result { Err(err) => { if let Some(e) = err.downcast_ref::() { if e.kind() == ErrorKind::NotFound { - return Ok(Settings::initial_user_settings_content(&Assets).to_string()); + return Ok(crate::initial_user_settings_content(&Assets).to_string()); } } return Err(err); diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index cdc7659189..dd81b05434 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -40,7 +40,11 @@ pub trait Setting: 'static { where Self: Sized; - fn json_schema(generator: &mut SchemaGenerator, _: &SettingsJsonSchemaParams) -> RootSchema { + fn json_schema( + generator: &mut SchemaGenerator, + _: &SettingsJsonSchemaParams, + _: &AppContext, + ) -> RootSchema { generator.root_schema_for::() } @@ -75,7 +79,7 @@ pub trait Setting: 'static { } pub struct SettingsJsonSchemaParams<'a> { - pub theme_names: &'a [String], + pub staff_mode: bool, pub language_names: &'a [String], } @@ -112,6 +116,7 @@ trait AnySettingValue { &self, generator: &mut SchemaGenerator, _: &SettingsJsonSchemaParams, + cx: &AppContext, ) -> RootSchema; } @@ -169,6 +174,16 @@ impl SettingsStore { .expect("no default value for setting type") } + /// Override the global value for a setting. + /// + /// The given value will be overwritten if the user settings file changes. + pub fn override_global(&mut self, value: T) { + self.setting_values + .get_mut(&TypeId::of::()) + .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::())) + .set_global_value(Box::new(value)) + } + /// Get the user's settings as a raw JSON value. /// /// This is only for debugging and reporting. For user-facing functionality, @@ -342,7 +357,11 @@ impl SettingsStore { Ok(()) } - pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams) -> serde_json::Value { + pub fn json_schema( + &self, + schema_params: &SettingsJsonSchemaParams, + cx: &AppContext, + ) -> serde_json::Value { use schemars::{ gen::SchemaSettings, schema::{Schema, SchemaObject}, @@ -355,7 +374,7 @@ impl SettingsStore { let mut combined_schema = RootSchema::default(); for setting_value in self.setting_values.values() { - let setting_schema = setting_value.json_schema(&mut generator, schema_params); + let setting_schema = setting_value.json_schema(&mut generator, schema_params, cx); combined_schema .definitions .extend(setting_schema.definitions); @@ -552,8 +571,9 @@ impl AnySettingValue for SettingValue { &self, generator: &mut SchemaGenerator, params: &SettingsJsonSchemaParams, + cx: &AppContext, ) -> RootSchema { - T::json_schema(generator, params) + T::json_schema(generator, params, cx) } } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index cc4580daa1..f918d91b6d 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -33,7 +33,6 @@ use mappings::mouse::{ use procinfo::LocalProcessInfo; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::Settings; use util::truncate_and_trailoff; use std::{ @@ -700,7 +699,7 @@ impl Terminal { match event { InternalEvent::ColorRequest(index, format) => { let color = term.colors()[*index].unwrap_or_else(|| { - let term_style = &cx.global::().theme.terminal; + let term_style = &theme::current(cx).terminal; to_alac_rgb(get_color_at_index(index, &term_style)) }); self.write_to_pty(format(color)) diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs index a92f7285b5..fcb5e7feb3 100644 --- a/crates/terminal_view/src/terminal_button.rs +++ b/crates/terminal_view/src/terminal_button.rs @@ -5,7 +5,6 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyElement, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle, }; -use settings::Settings; use std::any::TypeId; use workspace::{ dock::{Dock, FocusDock}, @@ -43,7 +42,7 @@ impl View for TerminalButton { let has_terminals = !project.local_terminal_handles().is_empty(); let terminal_count = project.local_terminal_handles().len() as i32; - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); Stack::new() .with_child( diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 93be64b612..8976a34210 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -16,7 +16,7 @@ use gpui::{ use itertools::Itertools; use language::CursorShape; use ordered_float::OrderedFloat; -use settings::{font_size_for_setting, Settings}; +use settings::font_size_for_setting; use terminal::{ alacritty_terminal::{ ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, @@ -27,7 +27,7 @@ use terminal::{ mappings::colors::convert_color, IndexedCell, Terminal, TerminalContent, TerminalSettings, TerminalSize, }; -use theme::TerminalStyle; +use theme::{TerminalStyle, ThemeSettings}; use util::ResultExt; use std::{fmt::Debug, ops::RangeInclusive}; @@ -522,7 +522,7 @@ impl Element for TerminalElement { view: &mut TerminalView, cx: &mut LayoutContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { - let settings = cx.global::(); + let settings = settings::get_setting::(None, cx); let terminal_settings = settings::get_setting::(None, cx); //Setup layout information diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 76bc9b4a38..944366e358 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -904,6 +904,7 @@ mod tests { cx: &mut TestAppContext, ) -> (ModelHandle, ViewHandle) { let params = cx.update(AppState::test); + cx.update(|cx| theme::init((), cx)); let project = Project::test(params.fs.clone(), [], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index 0cba8d8da2..b213cc9c1e 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -5,7 +5,11 @@ edition = "2021" publish = false [features] -test-support = ["gpui/test-support"] +test-support = [ + "gpui/test-support", + "fs/test-support", + "settings/test-support" +] [lib] path = "src/theme.rs" @@ -14,10 +18,19 @@ doctest = false [dependencies] gpui = { path = "../gpui" } fs = { path = "../fs" } +settings = { path = "../settings" } +util = { path = "../util" } + anyhow.workspace = true indexmap = "1.6.2" parking_lot.workspace = true +schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true toml.workspace = true + +[dev-dependencies] +gpui = { path = "../gpui", features = ["test-support"] } +fs = { path = "../fs", features = ["test-support"] } +settings = { path = "../settings", features = ["test-support"] } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 8760ea54ea..ae3bcb765d 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -1,19 +1,31 @@ mod theme_registry; +mod theme_settings; +pub mod ui; use gpui::{ color::Color, elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, TooltipStyle}, fonts::{HighlightStyle, TextStyle}, - platform, Border, MouseState, + platform, AppContext, AssetSource, Border, MouseState, }; use serde::{de::DeserializeOwned, Deserialize}; use serde_json::Value; use std::{collections::HashMap, sync::Arc}; use ui::{ButtonStyle, CheckboxStyle, IconStyle, ModalStyle, SvgStyle}; -pub mod ui; +pub use theme_registry::ThemeRegistry; +pub use theme_settings::ThemeSettings; -pub use theme_registry::*; +pub fn current(cx: &AppContext) -> Arc { + settings::get_setting::(None, cx) + .theme + .clone() +} + +pub fn init(source: impl AssetSource, cx: &mut AppContext) { + cx.set_global(ThemeRegistry::new(source, cx.font_cache().clone())); + settings::register_setting::(cx); +} #[derive(Deserialize, Default)] pub struct Theme { diff --git a/crates/theme/src/theme_registry.rs b/crates/theme/src/theme_registry.rs index 2bcdb4528c..12ccaf3d51 100644 --- a/crates/theme/src/theme_registry.rs +++ b/crates/theme/src/theme_registry.rs @@ -20,9 +20,6 @@ pub struct ThemeRegistry { next_theme_id: AtomicUsize, } -#[cfg(any(test, feature = "test-support"))] -pub const EMPTY_THEME_NAME: &'static str = "empty-theme"; - impl ThemeRegistry { pub fn new(source: impl AssetSource, font_cache: Arc) -> Arc { let this = Arc::new(Self { @@ -35,8 +32,13 @@ impl ThemeRegistry { #[cfg(any(test, feature = "test-support"))] this.themes.lock().insert( - EMPTY_THEME_NAME.to_string(), - gpui::fonts::with_font_cache(this.font_cache.clone(), || Arc::new(Theme::default())), + settings::EMPTY_THEME_NAME.to_string(), + gpui::fonts::with_font_cache(this.font_cache.clone(), || { + let mut theme = Theme::default(); + theme.meta.id = this.next_theme_id.fetch_add(1, SeqCst); + theme.meta.name = settings::EMPTY_THEME_NAME.into(); + Arc::new(theme) + }), ); this diff --git a/crates/theme/src/theme_settings.rs b/crates/theme/src/theme_settings.rs new file mode 100644 index 0000000000..85958e576d --- /dev/null +++ b/crates/theme/src/theme_settings.rs @@ -0,0 +1,136 @@ +use crate::{Theme, ThemeRegistry}; +use anyhow::Result; +use gpui::{font_cache::FamilyId, fonts, AppContext}; +use schemars::{ + gen::SchemaGenerator, + schema::{InstanceType, Schema, SchemaObject}, + JsonSchema, +}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use settings::SettingsJsonSchemaParams; +use std::sync::Arc; +use util::ResultExt as _; + +#[derive(Clone)] +pub struct ThemeSettings { + pub buffer_font_family_name: String, + pub buffer_font_features: fonts::Features, + pub buffer_font_family: FamilyId, + pub buffer_font_size: f32, + pub theme: Arc, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct ThemeSettingsContent { + #[serde(default)] + pub buffer_font_family: Option, + #[serde(default)] + pub buffer_font_size: Option, + #[serde(default)] + pub buffer_font_features: Option, + #[serde(default)] + pub theme: Option, +} + +impl settings::Setting for ThemeSettings { + const KEY: Option<&'static str> = None; + + type FileContent = ThemeSettingsContent; + + fn load( + defaults: &Self::FileContent, + user_values: &[&Self::FileContent], + cx: &AppContext, + ) -> Result { + let buffer_font_features = defaults.buffer_font_features.clone().unwrap(); + let themes = cx.global::>(); + + let mut this = Self { + buffer_font_family: cx + .font_cache() + .load_family( + &[defaults.buffer_font_family.as_ref().unwrap()], + &buffer_font_features, + ) + .unwrap(), + buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(), + buffer_font_features, + buffer_font_size: defaults.buffer_font_size.unwrap(), + theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), + }; + + for value in user_values.into_iter().copied().cloned() { + let font_cache = cx.font_cache(); + let mut family_changed = false; + if let Some(value) = value.buffer_font_family { + this.buffer_font_family_name = value; + family_changed = true; + } + if let Some(value) = value.buffer_font_features { + this.buffer_font_features = value; + family_changed = true; + } + if family_changed { + if let Some(id) = font_cache + .load_family(&[&this.buffer_font_family_name], &this.buffer_font_features) + .log_err() + { + this.buffer_font_family = id; + } + } + + if let Some(value) = &value.theme { + if let Some(theme) = themes.get(value).log_err() { + this.theme = theme; + } + } + + merge(&mut this.buffer_font_size, value.buffer_font_size); + } + + Ok(this) + } + + fn json_schema( + generator: &mut SchemaGenerator, + params: &SettingsJsonSchemaParams, + cx: &AppContext, + ) -> schemars::schema::RootSchema { + let mut root_schema = generator.root_schema_for::(); + let theme_names = cx + .global::>() + .list(params.staff_mode) + .map(|theme| Value::String(theme.name.clone())) + .collect(); + + let theme_name_schema = SchemaObject { + instance_type: Some(InstanceType::String.into()), + enum_values: Some(theme_names), + ..Default::default() + }; + + root_schema + .definitions + .extend([("ThemeName".into(), theme_name_schema.into())]); + + root_schema + .schema + .object + .as_mut() + .unwrap() + .properties + .extend([( + "theme".to_owned(), + Schema::new_ref("#/definitions/ThemeName".into()), + )]); + + root_schema + } +} + +fn merge(target: &mut T, value: Option) { + if let Some(value) = value { + *target = value; + } +} diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 27c5a9ef4e..a6c84d1d91 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -2,10 +2,10 @@ use fs::Fs; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use gpui::{actions, elements::*, AnyElement, AppContext, Element, MouseState, ViewContext}; use picker::{Picker, PickerDelegate, PickerEvent}; -use settings::{update_settings_file, Settings}; +use settings::{update_settings_file, SettingsStore}; use staff_mode::StaffMode; use std::sync::Arc; -use theme::{Theme, ThemeMeta, ThemeRegistry}; +use theme::{Theme, ThemeMeta, ThemeRegistry, ThemeSettings}; use util::ResultExt; use workspace::Workspace; @@ -18,17 +18,17 @@ pub fn init(cx: &mut AppContext) { pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { workspace.toggle_modal(cx, |workspace, cx| { - let themes = workspace.app_state().themes.clone(); let fs = workspace.app_state().fs.clone(); - cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(fs, themes, cx), cx)) + cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(fs, cx), cx)) }); } #[cfg(debug_assertions)] -pub fn reload(themes: Arc, cx: &mut AppContext) { - let current_theme_name = cx.global::().theme.meta.name.clone(); - themes.clear(); - match themes.get(¤t_theme_name) { +pub fn reload(cx: &mut AppContext) { + let current_theme_name = theme::current(cx).meta.name.clone(); + let registry = cx.global::>(); + registry.clear(); + match registry.get(¤t_theme_name) { Ok(theme) => { ThemeSelectorDelegate::set_theme(theme, cx); log::info!("reloaded theme {}", current_theme_name); @@ -43,7 +43,6 @@ pub type ThemeSelector = Picker; pub struct ThemeSelectorDelegate { fs: Arc, - registry: Arc, theme_data: Vec, matches: Vec, original_theme: Arc, @@ -52,18 +51,12 @@ pub struct ThemeSelectorDelegate { } impl ThemeSelectorDelegate { - fn new( - fs: Arc, - registry: Arc, - cx: &mut ViewContext, - ) -> Self { - let settings = cx.global::(); + fn new(fs: Arc, cx: &mut ViewContext) -> Self { + let original_theme = theme::current(cx).clone(); - let original_theme = settings.theme.clone(); - - let mut theme_names = registry - .list(**cx.default_global::()) - .collect::>(); + let staff_mode = **cx.default_global::(); + let registry = cx.global::>(); + let mut theme_names = registry.list(staff_mode).collect::>(); theme_names.sort_unstable_by(|a, b| a.is_light.cmp(&b.is_light).then(a.name.cmp(&b.name))); let matches = theme_names .iter() @@ -76,7 +69,6 @@ impl ThemeSelectorDelegate { .collect(); let mut this = Self { fs, - registry, theme_data: theme_names, matches, original_theme: original_theme.clone(), @@ -89,7 +81,8 @@ impl ThemeSelectorDelegate { fn show_selected_theme(&mut self, cx: &mut ViewContext) { if let Some(mat) = self.matches.get(self.selected_index) { - match self.registry.get(&mat.string) { + let registry = cx.global::>(); + match registry.get(&mat.string) { Ok(theme) => { Self::set_theme(theme, cx); } @@ -109,8 +102,10 @@ impl ThemeSelectorDelegate { } fn set_theme(theme: Arc, cx: &mut AppContext) { - cx.update_global::(|settings, cx| { - settings.theme = theme; + cx.update_global::(|store, cx| { + let mut theme_settings = store.get::(None).clone(); + theme_settings.theme = theme; + store.override_global(theme_settings); cx.refresh_windows(); }); } @@ -128,9 +123,9 @@ impl PickerDelegate for ThemeSelectorDelegate { fn confirm(&mut self, cx: &mut ViewContext) { self.selection_completed = true; - let theme_name = cx.global::().theme.meta.name.clone(); - update_settings_file::(self.fs.clone(), cx, |settings_content| { - settings_content.theme = Some(theme_name); + let theme_name = theme::current(cx).meta.name.clone(); + update_settings_file::(self.fs.clone(), cx, |settings| { + settings.theme = Some(theme_name); }); cx.emit(PickerEvent::Dismiss); @@ -212,11 +207,10 @@ impl PickerDelegate for ThemeSelectorDelegate { selected: bool, cx: &AppContext, ) -> AnyElement> { - let settings = cx.global::(); - let theme = &settings.theme; - let theme_match = &self.matches[ix]; + let theme = theme::current(cx); let style = theme.picker.item.style_for(mouse_state, selected); + let theme_match = &self.matches[ix]; Label::new(theme_match.string.clone(), style.label.clone()) .with_highlights(theme_match.positions.clone()) .contained() diff --git a/crates/theme_testbench/src/theme_testbench.rs b/crates/theme_testbench/src/theme_testbench.rs index 125bc523b7..52eca5f8ad 100644 --- a/crates/theme_testbench/src/theme_testbench.rs +++ b/crates/theme_testbench/src/theme_testbench.rs @@ -10,8 +10,7 @@ use gpui::{ WeakViewHandle, }; use project::Project; -use settings::Settings; -use theme::{ColorScheme, Layer, Style, StyleSet}; +use theme::{ColorScheme, Layer, Style, StyleSet, ThemeSettings}; use workspace::{item::Item, register_deserializable_item, Pane, Workspace}; actions!(theme, [DeployThemeTestbench]); @@ -220,7 +219,7 @@ impl ThemeTestbench { } fn render_label(text: String, style: &Style, cx: &mut ViewContext) -> Label { - let settings = cx.global::(); + let settings = settings::get_setting::(None, cx); let font_cache = cx.font_cache(); let family_id = settings.buffer_font_family; let font_size = settings::font_size_for_setting(settings.buffer_font_size, cx); @@ -252,7 +251,7 @@ impl View for ThemeTestbench { } fn render(&mut self, cx: &mut gpui::ViewContext) -> AnyElement { - let color_scheme = &cx.global::().theme.clone().color_scheme; + let color_scheme = &theme::current(cx).clone().color_scheme; Flex::row() .with_child( diff --git a/crates/welcome/src/base_keymap_picker.rs b/crates/welcome/src/base_keymap_picker.rs index 815de5e6ae..c9dfec9eb0 100644 --- a/crates/welcome/src/base_keymap_picker.rs +++ b/crates/welcome/src/base_keymap_picker.rs @@ -7,7 +7,7 @@ use gpui::{ }; use picker::{Picker, PickerDelegate, PickerEvent}; use project::Fs; -use settings::{update_settings_file, Settings}; +use settings::update_settings_file; use std::sync::Arc; use util::ResultExt; use workspace::Workspace; @@ -139,7 +139,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate { selected: bool, cx: &gpui::AppContext, ) -> gpui::AnyElement> { - let theme = &cx.global::().theme; + let theme = &theme::current(cx); let keymap_match = &self.matches[ix]; let style = theme.picker.item.style_for(mouse_state, selected); diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index ca04d5f6dc..29b013156d 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -8,7 +8,7 @@ use gpui::{ elements::{Flex, Label, ParentElement}, AnyElement, AppContext, Element, Entity, Subscription, View, ViewContext, WeakViewHandle, }; -use settings::{update_settings_file, Settings}; +use settings::{update_settings_file, SettingsStore}; use std::{borrow::Cow, sync::Arc}; use workspace::{ item::Item, open_new, sidebar::SidebarSide, AppState, PaneBackdrop, Welcome, Workspace, @@ -61,9 +61,7 @@ impl View for WelcomePage { fn render(&mut self, cx: &mut gpui::ViewContext) -> AnyElement { let self_handle = cx.handle(); - let settings = cx.global::(); - let theme = settings.theme.clone(); - + let theme = theme::current(cx); let width = theme.welcome.page_width; let telemetry_settings = *settings::get_setting::(None, cx); @@ -224,7 +222,7 @@ impl WelcomePage { pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { WelcomePage { workspace: workspace.weak_handle(), - _settings_subscription: cx.observe_global::(move |_, cx| cx.notify()), + _settings_subscription: cx.observe_global::(move |_, cx| cx.notify()), } } } @@ -260,7 +258,7 @@ impl Item for WelcomePage { ) -> Option { Some(WelcomePage { workspace: self.workspace.clone(), - _settings_subscription: cx.observe_global::(move |_, cx| cx.notify()), + _settings_subscription: cx.observe_global::(move |_, cx| cx.notify()), }) } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 9879aba5c6..beac87f99c 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -408,7 +408,6 @@ mod tests { use gpui::{AppContext, BorrowWindowContext, TestAppContext, ViewContext, WindowContext}; use project::{FakeFs, Project}; - use theme::ThemeRegistry; use super::*; use crate::{ @@ -468,7 +467,6 @@ mod tests { project.clone(), Arc::new(AppState { languages: project.read(cx).languages().clone(), - themes: ThemeRegistry::new((), cx.font_cache().clone()), client: project.read(cx).client(), user_store: project.read(cx).user_store(), fs: project.read(cx).fs().clone(), @@ -615,7 +613,6 @@ mod tests { project.clone(), Arc::new(AppState { languages: project.read(cx).languages().clone(), - themes: ThemeRegistry::new((), cx.font_cache().clone()), client: project.read(cx).client(), user_store: project.read(cx).user_store(), fs: project.read(cx).fs().clone(), diff --git a/crates/workspace/src/dock/toggle_dock_button.rs b/crates/workspace/src/dock/toggle_dock_button.rs index 1fda55b783..9ab7a8996b 100644 --- a/crates/workspace/src/dock/toggle_dock_button.rs +++ b/crates/workspace/src/dock/toggle_dock_button.rs @@ -6,7 +6,6 @@ use gpui::{ platform::MouseButton, AnyElement, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle, }; -use settings::Settings; pub struct ToggleDockButton { workspace: WeakViewHandle, @@ -43,7 +42,7 @@ impl View for ToggleDockButton { let dock_position = workspace.read(cx).dock.position; let dock_pane = workspace.read(cx).dock_pane().clone(); - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let button = MouseEventHandler::::new(0, cx, { let theme = theme.clone(); diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 7881603bbc..21b3be09d0 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -149,6 +149,8 @@ impl Workspace { } pub mod simple_message_notification { + use super::Notification; + use crate::Workspace; use gpui::{ actions, elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text}, @@ -158,13 +160,8 @@ pub mod simple_message_notification { }; use menu::Cancel; use serde::Deserialize; - use settings::Settings; use std::{borrow::Cow, sync::Arc}; - use crate::Workspace; - - use super::Notification; - actions!(message_notifications, [CancelMessageNotification]); #[derive(Clone, Default, Deserialize, PartialEq)] @@ -240,7 +237,7 @@ pub mod simple_message_notification { } fn render(&mut self, cx: &mut gpui::ViewContext) -> gpui::AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let theme = &theme.simple_message_notification; enum MessageNotificationTag {} diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index ca454d2c75..3717cfc200 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -30,7 +30,6 @@ use gpui::{ }; use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; -use settings::Settings; use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc}; use theme::Theme; use util::ResultExt; @@ -1297,7 +1296,7 @@ impl Pane { } fn render_tabs(&mut self, cx: &mut ViewContext) -> impl Element { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let pane = cx.handle().downgrade(); let autoscroll = if mem::take(&mut self.autoscroll) { @@ -1328,7 +1327,7 @@ impl Pane { let pane = pane.clone(); let detail = detail.clone(); - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let mut tooltip_theme = theme.tooltip.clone(); tooltip_theme.max_text_width = None; let tab_tooltip_text = item.tab_tooltip_text(cx).map(|a| a.to_string()); @@ -1406,7 +1405,7 @@ impl Pane { pane: pane.clone(), }, { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let detail = detail.clone(); move |dragged_item: &DraggedItem, cx: &mut ViewContext| { @@ -1699,7 +1698,7 @@ impl View for Pane { if let Some(active_item) = self.active_item() { Flex::column() .with_child({ - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); let mut stack = Stack::new(); @@ -1765,7 +1764,7 @@ impl View for Pane { .into_any() } else { enum EmptyPane {} - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); dragged_item_receiver::(0, 0, false, None, cx, |_, cx| { self.render_blank_pane(&theme, cx) @@ -1862,7 +1861,7 @@ fn render_tab_bar_button)>( Stack::new() .with_child( MouseEventHandler::::new(index, cx, |mouse_state, cx| { - let theme = &cx.global::().theme.workspace.tab_bar; + let theme = &theme::current(cx).workspace.tab_bar; let style = theme.pane_button.style_for(mouse_state, false); Svg::new(icon) .with_color(style.color) @@ -2024,7 +2023,7 @@ impl Element for PaneBackdrop { view: &mut V, cx: &mut ViewContext, ) -> Self::PaintState { - let background = cx.global::().theme.editor.background; + let background = theme::current(cx).editor.background; let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); @@ -2536,7 +2535,7 @@ mod tests { fn init_test(cx: &mut TestAppContext) { cx.update(|cx| { cx.set_global(SettingsStore::test(cx)); - cx.set_global(settings::Settings::test(cx)); + theme::init((), cx); crate::init_settings(cx); }); } diff --git a/crates/workspace/src/pane/dragged_item_receiver.rs b/crates/workspace/src/pane/dragged_item_receiver.rs index 961205b9ee..532e6bff5c 100644 --- a/crates/workspace/src/pane/dragged_item_receiver.rs +++ b/crates/workspace/src/pane/dragged_item_receiver.rs @@ -1,3 +1,5 @@ +use super::DraggedItem; +use crate::{Pane, SplitDirection, Workspace}; use drag_and_drop::DragAndDrop; use gpui::{ color::Color, @@ -8,11 +10,6 @@ use gpui::{ AppContext, Element, EventContext, MouseState, Quad, View, ViewContext, WeakViewHandle, }; use project::ProjectEntryId; -use settings::Settings; - -use crate::{Pane, SplitDirection, Workspace}; - -use super::DraggedItem; pub fn dragged_item_receiver( region_id: usize, @@ -225,8 +222,5 @@ fn drop_split_direction( } fn overlay_color(cx: &AppContext) -> Color { - cx.global::() - .theme - .workspace - .drop_target_overlay_color + theme::current(cx).workspace.drop_target_overlay_color } diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index 5cc54a6a7f..9a2e0bc5d2 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -12,7 +12,6 @@ use gpui::{ platform::MouseButton, AppContext, Entity, Task, View, ViewContext, }; -use settings::Settings; use smallvec::SmallVec; use std::{ borrow::Cow, @@ -88,7 +87,7 @@ impl View for SharedScreen { } }) .contained() - .with_style(cx.global::().theme.shared_screen) + .with_style(theme::current(cx).shared_screen) }) .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent()) .into_any() diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index 6463ab7d24..50148fa211 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -4,7 +4,6 @@ use gpui::{ AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use serde::Deserialize; -use settings::Settings; use std::rc::Rc; pub trait SidebarItem: View { @@ -192,7 +191,7 @@ impl View for Sidebar { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(active_item) = self.active_item() { enum ResizeHandleTag {} - let style = &cx.global::().theme.workspace.sidebar; + let style = &theme::current(cx).workspace.sidebar; ChildView::new(active_item.as_any(), cx) .contained() .with_style(style.container) @@ -231,7 +230,7 @@ impl View for SidebarButtons { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = &cx.global::().theme; + let theme = &theme::current(cx); let tooltip_style = theme.tooltip.clone(); let theme = &theme.workspace.status_bar.sidebar_buttons; let sidebar = self.sidebar.read(cx); diff --git a/crates/workspace/src/status_bar.rs b/crates/workspace/src/status_bar.rs index b4de6b3575..6fc1467566 100644 --- a/crates/workspace/src/status_bar.rs +++ b/crates/workspace/src/status_bar.rs @@ -11,7 +11,6 @@ use gpui::{ AnyElement, AnyViewHandle, Entity, LayoutContext, SceneBuilder, SizeConstraint, Subscription, View, ViewContext, ViewHandle, WindowContext, }; -use settings::Settings; pub trait StatusItemView: View { fn set_active_pane_item( @@ -47,7 +46,7 @@ impl View for StatusBar { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = &cx.global::().theme.workspace.status_bar; + let theme = &theme::current(cx).workspace.status_bar; StatusBarElement { left: Flex::row() diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index b2832aa1e8..30890ed5d2 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -3,7 +3,6 @@ use gpui::{ elements::*, platform::CursorStyle, platform::MouseButton, Action, AnyElement, AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; -use settings::Settings; pub trait ToolbarItemView: View { fn set_active_pane_item( @@ -68,7 +67,7 @@ impl View for Toolbar { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = &cx.global::().theme.workspace.toolbar; + let theme = &theme::current(cx).workspace.toolbar; let mut primary_left_items = Vec::new(); let mut primary_right_items = Vec::new(); @@ -131,7 +130,7 @@ impl View for Toolbar { let height = theme.height * primary_items_row_count as f32; let nav_button_height = theme.height; let button_style = theme.nav_button; - let tooltip_style = cx.global::().theme.tooltip.clone(); + let tooltip_style = theme::current(cx).tooltip.clone(); Flex::column() .with_child( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index d956db10b6..a94bf3ce1c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -76,12 +76,11 @@ pub use persistence::{ use postage::prelude::Stream; use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; -use settings::Settings; use shared_screen::SharedScreen; use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem}; use status_bar::StatusBar; pub use status_bar::StatusItemView; -use theme::{Theme, ThemeRegistry}; +use theme::Theme; pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; use util::{async_iife, paths, ResultExt}; pub use workspace_settings::{AutosaveSetting, DockAnchor, GitGutterSetting, WorkspaceSettings}; @@ -276,7 +275,7 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { cx.add_action( move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext| { create_and_open_local_file(&paths::SETTINGS, cx, || { - Settings::initial_user_settings_content(&Assets) + settings::initial_user_settings_content(&Assets) .as_ref() .into() }) @@ -361,7 +360,6 @@ pub fn register_deserializable_item(cx: &mut AppContext) { pub struct AppState { pub languages: Arc, - pub themes: Arc, pub client: Arc, pub user_store: ModelHandle, pub fs: Arc, @@ -379,7 +377,6 @@ impl AppState { if !cx.has_global::() { cx.set_global(SettingsStore::test(cx)); - cx.set_global(Settings::test(cx)); } let fs = fs::FakeFs::new(cx.background().clone()); @@ -387,14 +384,13 @@ impl AppState { let http_client = util::http::FakeHttpClient::with_404_response(); let client = Client::new(http_client.clone(), cx); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); - let themes = ThemeRegistry::new((), cx.font_cache().clone()); + theme::init((), cx); client::init(&client, cx); crate::init_settings(cx); Arc::new(Self { client, - themes, fs, languages, user_store, @@ -1992,7 +1988,7 @@ impl Workspace { enum DisconnectedOverlay {} Some( MouseEventHandler::::new(0, cx, |_, cx| { - let theme = &cx.global::().theme; + let theme = &theme::current(cx); Label::new( "Your connection to the remote project has been lost.", theme.workspace.disconnected_overlay.text.clone(), @@ -2630,7 +2626,6 @@ impl Workspace { pub fn test_new(project: ModelHandle, cx: &mut ViewContext) -> Self { let app_state = Arc::new(AppState { languages: project.read(cx).languages().clone(), - themes: ThemeRegistry::new((), cx.font_cache().clone()), client: project.read(cx).client(), user_store: project.read(cx).user_store(), fs: project.read(cx).fs().clone(), @@ -2776,7 +2771,7 @@ impl View for Workspace { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let theme = cx.global::().theme.clone(); + let theme = theme::current(cx).clone(); Stack::new() .with_child( Flex::column() @@ -3798,7 +3793,7 @@ mod tests { cx.foreground().forbid_parking(); cx.update(|cx| { cx.set_global(SettingsStore::test(cx)); - cx.set_global(Settings::test(cx)); + theme::init((), cx); language::init(cx); crate::init_settings(cx); }); diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 91b58f634b..1f2b359af1 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -3,7 +3,6 @@ pub use language::*; use node_runtime::NodeRuntime; use rust_embed::RustEmbed; use std::{borrow::Cow, str, sync::Arc}; -use theme::ThemeRegistry; mod c; mod elixir; @@ -32,11 +31,7 @@ mod yaml; #[exclude = "*.rs"] struct LanguageDir; -pub fn init( - languages: Arc, - themes: Arc, - node_runtime: Arc, -) { +pub fn init(languages: Arc, node_runtime: Arc) { fn adapter_arc(adapter: impl LspAdapter) -> Arc { Arc::new(adapter) } @@ -69,7 +64,6 @@ pub fn init( vec![adapter_arc(json::JsonLspAdapter::new( node_runtime.clone(), languages.clone(), - themes.clone(), ))], ), ("markdown", tree_sitter_markdown::language(), vec![]), diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 8ea07c626d..1fb1a5a941 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -16,7 +16,6 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use theme::ThemeRegistry; use util::http::HttpClient; use util::{paths, ResultExt}; @@ -30,20 +29,11 @@ fn server_binary_arguments(server_path: &Path) -> Vec { pub struct JsonLspAdapter { node: Arc, languages: Arc, - themes: Arc, } impl JsonLspAdapter { - pub fn new( - node: Arc, - languages: Arc, - themes: Arc, - ) -> Self { - JsonLspAdapter { - node, - languages, - themes, - } + pub fn new(node: Arc, languages: Arc) -> Self { + JsonLspAdapter { node, languages } } } @@ -128,18 +118,15 @@ impl LspAdapter for JsonLspAdapter { cx: &mut AppContext, ) -> Option> { let action_names = cx.all_action_names().collect::>(); - let theme_names = &self - .themes - .list(**cx.default_global::()) - .map(|meta| meta.name) - .collect::>(); + let staff_mode = cx.global::().0; let language_names = &self.languages.language_names(); - let settings_schema = cx - .global::() - .json_schema(&SettingsJsonSchemaParams { - theme_names, + let settings_schema = cx.global::().json_schema( + &SettingsJsonSchemaParams { language_names, - }); + staff_mode, + }, + cx, + ); Some( future::ready(serde_json::json!({ "json": { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index def5049633..18abb2e2c5 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -23,9 +23,7 @@ use node_runtime::NodeRuntime; use parking_lot::Mutex; use project::Fs; use serde::{Deserialize, Serialize}; -use settings::{ - default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore, -}; +use settings::{default_settings, handle_settings_file_changes, watch_config_file, SettingsStore}; use simplelog::ConfigBuilder; use smol::process::Command; use std::{ @@ -56,7 +54,6 @@ use welcome::{show_welcome_experience, FIRST_OPEN}; use fs::RealFs; #[cfg(debug_assertions)] use staff_mode::StaffMode; -use theme::ThemeRegistry; use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt}; use workspace::{ dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, @@ -84,7 +81,6 @@ fn main() { load_embedded_fonts(&app); let fs = Arc::new(RealFs); - let themes = ThemeRegistry::new(Assets, app.font_cache()); let user_settings_file_rx = watch_config_file(app.background(), fs.clone(), paths::SETTINGS.clone()); let user_keymap_file_rx = @@ -124,7 +120,6 @@ fn main() { app.run(move |cx| { cx.set_global(*RELEASE_CHANNEL); - cx.set_global(themes.clone()); #[cfg(debug_assertions)] cx.set_global(StaffMode(true)); @@ -148,11 +143,12 @@ fn main() { let languages = Arc::new(languages); let node_runtime = NodeRuntime::new(http.clone(), cx.background().to_owned()); - languages::init(languages.clone(), themes.clone(), node_runtime.clone()); + languages::init(languages.clone(), node_runtime.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); cx.set_global(client.clone()); + theme::init(Assets, cx); context_menu::init(cx); project::Project::init(&client, cx); client::init(&client, cx); @@ -171,13 +167,12 @@ fn main() { theme_testbench::init(cx); copilot::init(http.clone(), node_runtime, cx); - cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx)) - .detach(); + cx.spawn(|cx| watch_themes(fs.clone(), cx)).detach(); - languages.set_theme(cx.global::().theme.clone()); - cx.observe_global::({ + languages.set_theme(theme::current(cx).clone()); + cx.observe_global::({ let languages = languages.clone(); - move |cx| languages.set_theme(cx.global::().theme.clone()) + move |cx| languages.set_theme(theme::current(cx).clone()) }) .detach(); @@ -190,7 +185,6 @@ fn main() { let app_state = Arc::new(AppState { languages, - themes, client: client.clone(), user_store, fs, @@ -208,10 +202,13 @@ fn main() { journal::init(app_state.clone(), cx); language_selector::init(cx); theme_selector::init(cx); - zed::init(&app_state, cx); + activity_indicator::init(cx); + lsp_log::init(cx); + call::init(app_state.client.clone(), app_state.user_store.clone(), cx); collab_ui::init(&app_state, cx); feedback::init(cx); welcome::init(cx); + zed::init(&app_state, cx); cx.set_menus(menus::menus()); @@ -584,11 +581,7 @@ fn load_embedded_fonts(app: &App) { } #[cfg(debug_assertions)] -async fn watch_themes( - fs: Arc, - themes: Arc, - mut cx: AsyncAppContext, -) -> Option<()> { +async fn watch_themes(fs: Arc, mut cx: AsyncAppContext) -> Option<()> { let mut events = fs .watch("styles/src".as_ref(), Duration::from_millis(100)) .await; @@ -600,7 +593,7 @@ async fn watch_themes( .await .log_err()?; if output.status.success() { - cx.update(|cx| theme_selector::reload(themes.clone(), cx)) + cx.update(|cx| theme_selector::reload(cx)) } else { eprintln!( "build script failed {}", diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d6e4f26b67..5b271762ed 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -30,10 +30,11 @@ use search::{BufferSearchBar, ProjectSearchBar}; use serde::Deserialize; use serde_json::to_string_pretty; use settings::{ - adjust_font_size_delta, KeymapFileContent, Settings, SettingsStore, DEFAULT_SETTINGS_ASSET_PATH, + adjust_font_size_delta, KeymapFileContent, SettingsStore, DEFAULT_SETTINGS_ASSET_PATH, }; use std::{borrow::Cow, str, sync::Arc}; use terminal_view::terminal_button::TerminalButton; +use theme::ThemeSettings; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; use welcome::BaseKeymap; @@ -124,7 +125,7 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { }); cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| { adjust_font_size_delta(cx, |size, cx| { - if cx.global::().buffer_font_size + *size > MIN_FONT_SIZE { + if cx.global::().buffer_font_size + *size > MIN_FONT_SIZE { *size -= 1.0; } }) @@ -258,9 +259,6 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { } } }); - activity_indicator::init(cx); - lsp_log::init(cx); - call::init(app_state.client.clone(), app_state.user_store.clone(), cx); load_default_keymap(cx); } @@ -1910,7 +1908,7 @@ mod tests { cx.update(|cx| { cx.set_global(SettingsStore::test(cx)); - cx.set_global(ThemeRegistry::new(Assets, cx.font_cache().clone())); + theme::init(Assets, cx); welcome::init(cx); cx.add_global_action(|_: &A, _cx| {}); @@ -2038,15 +2036,25 @@ mod tests { ]) .unwrap(); let themes = ThemeRegistry::new(Assets, cx.font_cache().clone()); - let settings = Settings::defaults(Assets, cx.font_cache(), &themes); + let mut settings = SettingsStore::default(); + settings + .set_default_settings(&settings::default_settings(), cx) + .unwrap(); + cx.set_global(settings); + theme::init(Assets, cx); let mut has_default_theme = false; for theme_name in themes.list(false).map(|meta| meta.name) { let theme = themes.get(&theme_name).unwrap(); - if theme.meta.name == settings.theme.meta.name { + assert_eq!(theme.meta.name, theme_name); + if theme.meta.name + == settings::get_setting::(None, cx) + .theme + .meta + .name + { has_default_theme = true; } - assert_eq!(theme.meta.name, theme_name); } assert!(has_default_theme); } @@ -2056,10 +2064,9 @@ mod tests { let mut languages = LanguageRegistry::test(); languages.set_executor(cx.background().clone()); let languages = Arc::new(languages); - let themes = ThemeRegistry::new((), cx.font_cache().clone()); let http = FakeHttpClient::with_404_response(); let node_runtime = NodeRuntime::new(http, cx.background().to_owned()); - languages::init(languages.clone(), themes, node_runtime); + languages::init(languages.clone(), node_runtime); for name in languages.language_names() { languages.language_for_name(&name); } @@ -2073,6 +2080,7 @@ mod tests { let state = Arc::get_mut(&mut app_state).unwrap(); state.initialize_workspace = initialize_workspace; state.build_window_options = build_window_options; + theme::init((), cx); call::init(app_state.client.clone(), app_state.user_store.clone(), cx); workspace::init(app_state.clone(), cx); language::init(cx); From 2d5f03e14852891d9090c8728aba4753a48c0401 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 May 2023 15:05:20 -0700 Subject: [PATCH 125/168] Remove optional path argument when getting settings --- crates/auto_update/src/auto_update.rs | 6 +++--- crates/client/src/client.rs | 5 ++--- crates/client/src/telemetry.rs | 2 +- .../collab_ui/src/sharing_status_indicator.rs | 2 +- crates/copilot/src/copilot.rs | 3 +-- crates/copilot_button/src/copilot_button.rs | 13 ++++++------- crates/diagnostics/src/diagnostics.rs | 2 +- crates/editor/src/blink_manager.rs | 2 +- crates/editor/src/display_map.rs | 2 +- crates/editor/src/editor.rs | 16 ++++++++-------- crates/editor/src/element.rs | 19 +++++++++---------- crates/editor/src/hover_popover.rs | 2 +- crates/editor/src/multi_buffer.rs | 4 ++-- crates/journal/src/journal.rs | 2 +- crates/language/src/buffer.rs | 4 ++-- crates/language/src/language_settings.rs | 15 ++++----------- crates/project/src/project.rs | 9 ++++----- crates/project/src/terminals.rs | 2 +- crates/settings/src/settings_file.rs | 13 +++---------- crates/terminal/src/terminal.rs | 2 +- crates/terminal_view/src/terminal_element.rs | 4 ++-- crates/terminal_view/src/terminal_view.rs | 12 ++++++------ crates/theme/src/theme.rs | 4 +--- crates/theme_testbench/src/theme_testbench.rs | 2 +- crates/vim/src/vim.rs | 4 ++-- crates/welcome/src/base_keymap_picker.rs | 2 +- crates/welcome/src/welcome.rs | 2 +- crates/workspace/src/dock.rs | 5 ++--- crates/workspace/src/item.rs | 4 ++-- crates/workspace/src/pane.rs | 2 +- crates/workspace/src/pane_group.rs | 3 +-- crates/workspace/src/workspace.rs | 4 ++-- crates/zed/src/languages/yaml.rs | 2 +- crates/zed/src/main.rs | 6 +++--- crates/zed/src/zed.rs | 16 +++++----------- 35 files changed, 84 insertions(+), 113 deletions(-) diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 300340a225..7353c2597f 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -83,12 +83,12 @@ pub fn init(http_client: Arc, server_url: String, cx: &mut AppCo let auto_updater = cx.add_model(|cx| { let updater = AutoUpdater::new(version, http_client, server_url); - let mut update_subscription = settings::get_setting::(None, cx) + let mut update_subscription = settings::get::(cx) .0 .then(|| updater.start_polling(cx)); cx.observe_global::(move |updater, cx| { - if settings::get_setting::(None, cx).0 { + if settings::get::(cx).0 { if update_subscription.is_none() { update_subscription = Some(updater.start_polling(cx)) } @@ -281,7 +281,7 @@ impl AutoUpdater { let release_channel = cx .has_global::() .then(|| cx.global::().display_name()); - let telemetry = settings::get_setting::(None, cx).metrics; + let telemetry = settings::get::(cx).metrics; (installation_id, release_channel, telemetry) }); diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 041126271d..8950b51a45 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -781,7 +781,7 @@ impl Client { self.telemetry().report_mixpanel_event( "read credentials from keychain", Default::default(), - *settings::get_setting::(None, cx), + *settings::get::(cx), ); }); } @@ -1075,8 +1075,7 @@ impl Client { let telemetry = self.telemetry.clone(); let http = self.http.clone(); - let telemetry_settings = - cx.read(|cx| *settings::get_setting::(None, cx)); + let telemetry_settings = cx.read(|cx| *settings::get::(cx)); executor.clone().spawn(async move { // Generate a pair of asymmetric encryption keys. The public key will be used by the diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 075c2956ec..b3bdc72c91 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -247,7 +247,7 @@ impl Telemetry { is_staff: bool, cx: &AppContext, ) { - if !settings::get_setting::(None, cx).metrics { + if !settings::get::(cx).metrics { return; } diff --git a/crates/collab_ui/src/sharing_status_indicator.rs b/crates/collab_ui/src/sharing_status_indicator.rs index 8394036f39..3a1dde072f 100644 --- a/crates/collab_ui/src/sharing_status_indicator.rs +++ b/crates/collab_ui/src/sharing_status_indicator.rs @@ -16,7 +16,7 @@ pub fn init(cx: &mut AppContext) { if let Some(room) = call.read(cx).room() { if room.read(cx).is_screen_sharing() { if status_indicator.is_none() - && settings::get_setting::(None, cx).show_call_status_icon + && settings::get::(cx).show_call_status_icon { status_indicator = Some(cx.add_status_bar_item(|_| SharingStatusIndicator)); } diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index bb3727585b..de9104a684 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -318,7 +318,7 @@ impl Copilot { fn enable_or_disable_copilot(&mut self, cx: &mut ModelContext) { let http = self.http.clone(); let node_runtime = self.node_runtime.clone(); - if all_language_settings(None, cx).copilot_enabled(None, None) { + if all_language_settings(cx).copilot_enabled(None, None) { if matches!(self.server, CopilotServer::Disabled) { let start_task = cx .spawn({ @@ -786,7 +786,6 @@ impl Copilot { let uri = registered_buffer.uri.clone(); let position = position.to_point_utf16(buffer); let settings = language_settings( - None, buffer.language_at(position).map(|l| l.name()).as_deref(), cx, ); diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 560506a529..73cd8f6a1d 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -41,7 +41,7 @@ impl View for CopilotButton { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let all_language_settings = &all_language_settings(None, cx); + let all_language_settings = &all_language_settings(cx); if !all_language_settings.copilot.feature_enabled { return Empty::new().into_any(); } @@ -198,7 +198,7 @@ impl CopilotButton { if let Some(language) = self.language.clone() { let fs = fs.clone(); let language_enabled = - language_settings::language_settings(None, Some(language.as_ref()), cx) + language_settings::language_settings(Some(language.as_ref()), cx) .show_copilot_suggestions; menu_options.push(ContextMenuItem::handler( format!( @@ -210,7 +210,7 @@ impl CopilotButton { )); } - let settings = settings::get_setting::(None, cx); + let settings = settings::get::(cx); if let Some(path) = self.path.as_ref() { let path_enabled = settings.copilot_enabled_for_path(path); @@ -282,7 +282,7 @@ impl CopilotButton { let path = snapshot.file_at(suggestion_anchor).map(|file| file.path()); self.editor_enabled = Some( - all_language_settings(None, cx) + all_language_settings(cx) .copilot_enabled(language_name.as_deref(), path.map(|p| p.as_ref())), ); self.language = language_name; @@ -364,15 +364,14 @@ async fn configure_disabled_globs( } fn toggle_copilot_globally(fs: Arc, cx: &mut AppContext) { - let show_copilot_suggestions = all_language_settings(None, cx).copilot_enabled(None, None); + let show_copilot_suggestions = all_language_settings(cx).copilot_enabled(None, None); update_settings_file::(fs, cx, move |file| { file.defaults.show_copilot_suggestions = Some((!show_copilot_suggestions).into()) }); } fn toggle_copilot_for_language(language: Arc, fs: Arc, cx: &mut AppContext) { - let show_copilot_suggestions = - all_language_settings(None, cx).copilot_enabled(Some(&language), None); + let show_copilot_suggestions = all_language_settings(cx).copilot_enabled(Some(&language), None); update_settings_file::(fs, cx, move |file| { file.languages .entry(language) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 458ca51ce3..3385596f17 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -679,7 +679,7 @@ impl Item for ProjectDiagnosticsEditor { fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { let (message, highlights) = highlight_diagnostic_message(Vec::new(), &diagnostic.message); Arc::new(move |cx| { - let settings = settings::get_setting::(None, cx); + let settings = settings::get::(cx); let theme = &settings.theme.editor; let style = theme.diagnostic_header.clone(); let font_size = (style.text_scale_factor diff --git a/crates/editor/src/blink_manager.rs b/crates/editor/src/blink_manager.rs index 43d1ec6130..24ea4774aa 100644 --- a/crates/editor/src/blink_manager.rs +++ b/crates/editor/src/blink_manager.rs @@ -64,7 +64,7 @@ impl BlinkManager { } fn blink_cursors(&mut self, epoch: usize, cx: &mut ModelContext) { - if settings::get_setting::(None, cx).cursor_blink { + if settings::get::(cx).cursor_blink { if epoch == self.blink_epoch && self.enabled && !self.blinking_paused { self.visible = !self.visible; cx.notify(); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 4c36be682b..366e47ddc6 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -277,7 +277,7 @@ impl DisplayMap { .as_singleton() .and_then(|buffer| buffer.read(cx).language()) .map(|language| language.name()); - language_settings(None, language_name.as_deref(), cx).tab_size + language_settings(language_name.as_deref(), cx).tab_size } #[cfg(test)] diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 87c3b7afdc..9eed6c655e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1246,7 +1246,7 @@ impl Editor { ) -> Self { let editor_view_id = cx.view_id(); let display_map = cx.add_model(|cx| { - let settings = settings::get_setting::(None, cx); + let settings = settings::get::(cx); let style = build_style(settings, get_field_editor_theme.as_deref(), None, cx); DisplayMap::new( buffer.clone(), @@ -1427,7 +1427,7 @@ impl Editor { fn style(&self, cx: &AppContext) -> EditorStyle { build_style( - settings::get_setting::(None, cx), + settings::get::(cx), self.get_field_editor_theme.as_deref(), self.override_text_style.as_deref(), cx, @@ -2385,7 +2385,7 @@ impl Editor { } fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext) { - if !settings::get_setting::(None, cx).show_completions_on_input { + if !settings::get::(cx).show_completions_on_input { return; } @@ -3156,7 +3156,7 @@ impl Editor { let language_name = snapshot .language_at(location) .map(|language| language.name()); - let settings = all_language_settings(None, cx); + let settings = all_language_settings(cx); settings.copilot_enabled(language_name.as_deref(), path) } @@ -6902,7 +6902,7 @@ impl Editor { .map(|a| a.to_string()); let telemetry = project.read(cx).client().telemetry().clone(); - let telemetry_settings = *settings::get_setting::(None, cx); + let telemetry_settings = *settings::get::(cx); let event = ClickhouseEvent::Copilot { suggestion_id, @@ -6937,8 +6937,8 @@ impl Editor { .untyped_user_settings() .get("vim_mode") == Some(&serde_json::Value::Bool(true)); - let telemetry_settings = *settings::get_setting::(None, cx); - let copilot_enabled = all_language_settings(None, cx).copilot_enabled(None, None); + let telemetry_settings = *settings::get::(cx); + let copilot_enabled = all_language_settings(cx).copilot_enabled(None, None); let copilot_enabled_for_language = self .buffer .read(cx) @@ -7616,7 +7616,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend } Arc::new(move |cx: &mut BlockContext| { - let settings = settings::get_setting::(None, cx); + let settings = settings::get::(cx); let theme = &settings.theme.editor; let style = diagnostic_style(diagnostic.severity, is_valid, theme); let font_size = (style.text_scale_factor diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index dcce3c80f0..0a17fc8baf 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -550,7 +550,7 @@ impl EditorElement { let scroll_top = scroll_position.y() * line_height; let show_gutter = matches!( - settings::get_setting::(None, cx) + settings::get::(cx) .git .git_gutter .unwrap_or_default(), @@ -2060,15 +2060,14 @@ impl Element for EditorElement { )); } - let show_scrollbars = - match settings::get_setting::(None, cx).show_scrollbars { - ShowScrollbars::Auto => { - snapshot.has_scrollbar_info() || editor.scroll_manager.scrollbars_visible() - } - ShowScrollbars::System => editor.scroll_manager.scrollbars_visible(), - ShowScrollbars::Always => true, - ShowScrollbars::Never => false, - }; + let show_scrollbars = match settings::get::(cx).show_scrollbars { + ShowScrollbars::Auto => { + snapshot.has_scrollbar_info() || editor.scroll_manager.scrollbars_visible() + } + ShowScrollbars::System => editor.scroll_manager.scrollbars_visible(), + ShowScrollbars::Always => true, + ShowScrollbars::Never => false, + }; let include_root = editor .project diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 24f09f1082..9192dc75e1 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -37,7 +37,7 @@ pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext) { /// The internal hover action dispatches between `show_hover` or `hide_hover` /// depending on whether a point to hover over is provided. pub fn hover_at(editor: &mut Editor, point: Option, cx: &mut ViewContext) { - if settings::get_setting::(None, cx).hover_popover_enabled { + if settings::get::(cx).hover_popover_enabled { if let Some(point) = point { show_hover(editor, point, false, cx); } else { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 99df8ce796..eb69e8e7c1 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1380,7 +1380,7 @@ impl MultiBuffer { cx: &'a AppContext, ) -> &'a LanguageSettings { let language = self.language_at(point, cx); - language_settings(None, language.map(|l| l.name()).as_deref(), cx) + language_settings(language.map(|l| l.name()).as_deref(), cx) } pub fn for_each_buffer(&self, mut f: impl FnMut(&ModelHandle)) { @@ -2782,7 +2782,7 @@ impl MultiBufferSnapshot { ) -> &'a LanguageSettings { self.point_to_buffer_offset(point) .map(|(buffer, offset)| buffer.settings_at(offset, cx)) - .unwrap_or_else(|| language_settings(None, None, cx)) + .unwrap_or_else(|| language_settings(None, cx)) } pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option { diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 34af4ed0e4..74868e2f91 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -53,7 +53,7 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { } pub fn new_journal_entry(app_state: Arc, cx: &mut AppContext) { - let settings = settings::get_setting::(None, cx); + let settings = settings::get::(cx); let journal_dir = match journal_dir(settings.path.as_ref().unwrap()) { Some(journal_dir) => journal_dir, None => { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index fd1430b1e4..aee646091a 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1827,7 +1827,7 @@ impl BufferSnapshot { pub fn language_indent_size_at(&self, position: T, cx: &AppContext) -> IndentSize { let language_name = self.language_at(position).map(|language| language.name()); - let settings = language_settings(None, language_name.as_deref(), cx); + let settings = language_settings(language_name.as_deref(), cx); if settings.hard_tabs { IndentSize::tab() } else { @@ -2152,7 +2152,7 @@ impl BufferSnapshot { cx: &'a AppContext, ) -> &'a LanguageSettings { let language = self.language_at(position); - language_settings(None, language.map(|l| l.name()).as_deref(), cx) + language_settings(language.map(|l| l.name()).as_deref(), cx) } pub fn language_scope_at(&self, position: D) -> Option { diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 4d33366a71..cd932ce791 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -12,19 +12,12 @@ pub fn init(cx: &mut AppContext) { settings::register_setting::(cx); } -pub fn language_settings<'a>( - path: Option<&Path>, - language: Option<&str>, - cx: &'a AppContext, -) -> &'a LanguageSettings { - settings::get_setting::(path, cx).language(language) +pub fn language_settings<'a>(language: Option<&str>, cx: &'a AppContext) -> &'a LanguageSettings { + settings::get::(cx).language(language) } -pub fn all_language_settings<'a>( - path: Option<&Path>, - cx: &'a AppContext, -) -> &'a AllLanguageSettings { - settings::get_setting::(path, cx) +pub fn all_language_settings<'a>(cx: &'a AppContext) -> &'a AllLanguageSettings { + settings::get::(cx) } #[derive(Debug, Clone)] diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a487b23716..cdfe35f268 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -631,7 +631,7 @@ impl Project { } fn on_settings_changed(&mut self, cx: &mut ModelContext) { - let settings = all_language_settings(None, cx); + let settings = all_language_settings(cx); let mut language_servers_to_start = Vec::new(); for buffer in self.opened_buffers.values() { @@ -2208,7 +2208,7 @@ impl Project { language: Arc, cx: &mut ModelContext, ) { - if !language_settings(None, Some(&language.name()), cx).enable_language_server { + if !language_settings(Some(&language.name()), cx).enable_language_server { return; } @@ -2229,7 +2229,7 @@ impl Project { None => continue, }; - let lsp = settings::get_setting::(None, cx) + let lsp = settings::get::(cx) .lsp .get(&adapter.name.0); let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); @@ -3259,8 +3259,7 @@ impl Project { for (buffer, buffer_abs_path, language_server) in &buffers_with_paths_and_servers { let settings = buffer.read_with(&cx, |buffer, cx| { let language_name = buffer.language().map(|language| language.name()); - language_settings(buffer_abs_path.as_deref(), language_name.as_deref(), cx) - .clone() + language_settings(language_name.as_deref(), cx).clone() }); let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save; diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 26f2215bea..7bd9ce8aec 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -19,7 +19,7 @@ impl Project { "creating terminals as a guest is not supported yet" )); } else { - let settings = settings::get_setting::(None, cx); + let settings = settings::get::(cx); let terminal = TerminalBuilder::new( working_directory.clone(), diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index ed27416512..2f71d5b9af 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -4,14 +4,7 @@ use assets::Assets; use fs::Fs; use futures::{channel::mpsc, StreamExt}; use gpui::{executor::Background, AppContext, AssetSource}; -use std::{ - borrow::Cow, - io::ErrorKind, - path::{Path, PathBuf}, - str, - sync::Arc, - time::Duration, -}; +use std::{borrow::Cow, io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration}; use util::{paths, ResultExt}; pub fn register_setting(cx: &mut AppContext) { @@ -20,8 +13,8 @@ pub fn register_setting(cx: &mut AppContext) { }); } -pub fn get_setting<'a, T: Setting>(path: Option<&Path>, cx: &'a AppContext) -> &'a T { - cx.global::().get(path) +pub fn get<'a, T: Setting>(cx: &'a AppContext) -> &'a T { + cx.global::().get(None) } pub fn default_settings() -> Cow<'static, str> { diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index f918d91b6d..412e9fb1da 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -1149,7 +1149,7 @@ impl Terminal { } pub fn mouse_up(&mut self, e: &MouseUp, origin: Vector2F, cx: &mut ModelContext) { - let setting = settings::get_setting::(None, cx); + let setting = settings::get::(cx); let position = e.position.sub(origin); if self.mouse_mode(e.shift) { diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 8976a34210..6137f8eff6 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -522,8 +522,8 @@ impl Element for TerminalElement { view: &mut TerminalView, cx: &mut LayoutContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { - let settings = settings::get_setting::(None, cx); - let terminal_settings = settings::get_setting::(None, cx); + let settings = settings::get::(cx); + let terminal_settings = settings::get::(cx); //Setup layout information let terminal_theme = settings.theme.terminal.clone(); //TODO: Try to minimize this clone. diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 944366e358..0a7a69bf73 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -103,7 +103,7 @@ impl TerminalView { _: &workspace::NewTerminal, cx: &mut ViewContext, ) { - let strategy = settings::get_setting::(None, cx); + let strategy = settings::get::(cx); let working_directory = get_working_directory(workspace, cx, strategy.working_directory.clone()); @@ -217,7 +217,7 @@ impl TerminalView { self.terminal.update(cx, |term, cx| { term.try_keystroke( &Keystroke::parse("ctrl-cmd-space").unwrap(), - settings::get_setting::(None, cx).option_as_meta, + settings::get::(cx).option_as_meta, ) }); } @@ -243,7 +243,7 @@ impl TerminalView { return true; } - match settings::get_setting::(None, cx).blinking { + match settings::get::(cx).blinking { //If the user requested to never blink, don't blink it. TerminalBlink::Off => true, //If the terminal is controlling it, check terminal mode @@ -336,7 +336,7 @@ impl TerminalView { self.terminal.update(cx, |term, cx| { term.try_keystroke( &keystroke, - settings::get_setting::(None, cx).option_as_meta, + settings::get::(cx).option_as_meta, ); }); } @@ -399,7 +399,7 @@ impl View for TerminalView { self.terminal.update(cx, |term, cx| { term.try_keystroke( &event.keystroke, - settings::get_setting::(None, cx).option_as_meta, + settings::get::(cx).option_as_meta, ) }) } @@ -601,7 +601,7 @@ impl Item for TerminalView { .flatten() .or_else(|| { cx.read(|cx| { - let strategy = settings::get_setting::(None, cx) + let strategy = settings::get::(cx) .working_directory .clone(); workspace diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index ae3bcb765d..aa8e37814f 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -17,9 +17,7 @@ pub use theme_registry::ThemeRegistry; pub use theme_settings::ThemeSettings; pub fn current(cx: &AppContext) -> Arc { - settings::get_setting::(None, cx) - .theme - .clone() + settings::get::(cx).theme.clone() } pub fn init(source: impl AssetSource, cx: &mut AppContext) { diff --git a/crates/theme_testbench/src/theme_testbench.rs b/crates/theme_testbench/src/theme_testbench.rs index 52eca5f8ad..d0f50da07c 100644 --- a/crates/theme_testbench/src/theme_testbench.rs +++ b/crates/theme_testbench/src/theme_testbench.rs @@ -219,7 +219,7 @@ impl ThemeTestbench { } fn render_label(text: String, style: &Style, cx: &mut ViewContext) -> Label { - let settings = settings::get_setting::(None, cx); + let settings = settings::get::(cx); let font_cache = cx.font_cache(); let family_id = settings.buffer_font_family; let font_size = settings::font_size_for_setting(settings.buffer_font_size, cx); diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 23a28b3544..efb339eed7 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -95,11 +95,11 @@ pub fn init(cx: &mut AppContext) { filter.filtered_namespaces.insert("vim"); }); cx.update_default_global(|vim: &mut Vim, cx: &mut AppContext| { - vim.set_enabled(settings::get_setting::(None, cx).0, cx) + vim.set_enabled(settings::get::(cx).0, cx) }); cx.observe_global::(|cx| { cx.update_default_global(|vim: &mut Vim, cx: &mut AppContext| { - vim.set_enabled(settings::get_setting::(None, cx).0, cx) + vim.set_enabled(settings::get::(cx).0, cx) }); }) .detach(); diff --git a/crates/welcome/src/base_keymap_picker.rs b/crates/welcome/src/base_keymap_picker.rs index c9dfec9eb0..e44b391d84 100644 --- a/crates/welcome/src/base_keymap_picker.rs +++ b/crates/welcome/src/base_keymap_picker.rs @@ -40,7 +40,7 @@ pub struct BaseKeymapSelectorDelegate { impl BaseKeymapSelectorDelegate { fn new(fs: Arc, cx: &mut ViewContext) -> Self { - let base = settings::get_setting::(None, cx); + let base = settings::get::(cx); let selected_index = BaseKeymap::OPTIONS .iter() .position(|(_, value)| value == base) diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 29b013156d..89b4e42cbb 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -64,7 +64,7 @@ impl View for WelcomePage { let theme = theme::current(cx); let width = theme.welcome.page_width; - let telemetry_settings = *settings::get_setting::(None, cx); + let telemetry_settings = *settings::get::(cx); enum Metrics {} enum Diagnostics {} diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index beac87f99c..d1ec80de95 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -172,9 +172,8 @@ impl Dock { background_actions: BackgroundActions, cx: &mut ViewContext, ) -> Self { - let position = DockPosition::Hidden( - settings::get_setting::(None, cx).default_dock_anchor, - ); + let position = + DockPosition::Hidden(settings::get::(cx).default_dock_anchor); let workspace = cx.weak_handle(); let pane = cx.add_view(|cx| Pane::new(workspace, Some(position.anchor()), background_actions, cx)); diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index e8c10c6cd2..16905849a9 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -450,7 +450,7 @@ impl ItemHandle for ViewHandle { } ItemEvent::Edit => { - let settings = settings::get_setting::(None, cx); + let settings = settings::get::(cx); let debounce_delay = settings.git.gutter_debounce; if let AutosaveSetting::AfterDelay { milliseconds } = @@ -501,7 +501,7 @@ impl ItemHandle for ViewHandle { cx.observe_focus(self, move |workspace, item, focused, cx| { if !focused - && settings::get_setting::(None, cx).autosave + && settings::get::(cx).autosave == AutosaveSetting::OnFocusChange { Pane::autosave_item(&item, workspace.project.clone(), cx) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 3717cfc200..200f83700b 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1024,7 +1024,7 @@ impl Pane { } else if is_dirty && (can_save || is_singleton) { let will_autosave = cx.read(|cx| { matches!( - settings::get_setting::(None, cx).autosave, + settings::get::(cx).autosave, AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange ) && Self::can_autosave_item(&*item, cx) }); diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index edc7c617d0..6e7580a103 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -379,8 +379,7 @@ impl PaneAxis { .with_children(self.members.iter().enumerate().map(|(ix, member)| { let mut flex = 1.0; if member.contains(active_pane) { - flex = settings::get_setting::(None, cx) - .active_pane_magnification; + flex = settings::get::(cx).active_pane_magnification; } let mut member = member.render( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a94bf3ce1c..282daf8d8f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2359,7 +2359,7 @@ impl Workspace { item.workspace_deactivated(cx); } if matches!( - settings::get_setting::(None, cx).autosave, + settings::get::(cx).autosave, AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange ) { for item in pane.items() { @@ -3140,7 +3140,7 @@ pub fn join_remote_project( } pub fn restart(_: &Restart, cx: &mut AppContext) { - let should_confirm = settings::get_setting::(None, cx).confirm_quit; + let should_confirm = settings::get::(cx).confirm_quit; cx.spawn(|mut cx| async move { let mut workspaces = cx .window_ids() diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index b62168775c..bd5f2b4c02 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -107,7 +107,7 @@ impl LspAdapter for YamlLspAdapter { "keyOrdering": false }, "[yaml]": { - "editor.tabSize": language_settings(None, Some("YAML"), cx).tab_size, + "editor.tabSize": language_settings(Some("YAML"), cx).tab_size, } })) .boxed(), diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 18abb2e2c5..299e77efce 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -180,7 +180,7 @@ fn main() { client.telemetry().report_mixpanel_event( "start app", Default::default(), - *settings::get_setting::(None, cx), + *settings::get::(cx), ); let app_state = Arc::new(AppState { @@ -441,7 +441,7 @@ fn init_panic_hook(app_version: String) { } fn upload_previous_panics(http: Arc, cx: &mut AppContext) { - let telemetry_settings = *settings::get_setting::(None, cx); + let telemetry_settings = *settings::get::(cx); cx.background() .spawn({ @@ -800,7 +800,7 @@ pub fn dock_default_item_factory( workspace: &mut Workspace, cx: &mut ViewContext, ) -> Option> { - let strategy = settings::get_setting::(None, cx) + let strategy = settings::get::(cx) .working_directory .clone(); let working_directory = get_working_directory(workspace, cx, strategy); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 5b271762ed..8f241639ea 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -368,7 +368,7 @@ pub fn build_window_options( } fn quit(_: &Quit, cx: &mut gpui::AppContext) { - let should_confirm = settings::get_setting::(None, cx).confirm_quit; + let should_confirm = settings::get::(cx).confirm_quit; cx.spawn(|mut cx| async move { let mut workspaces = cx .window_ids() @@ -484,7 +484,7 @@ pub fn load_default_keymap(cx: &mut AppContext) { KeymapFileContent::load_asset(path, cx).unwrap(); } - if let Some(asset_path) = settings::get_setting::(None, cx).asset_path() { + if let Some(asset_path) = settings::get::(cx).asset_path() { KeymapFileContent::load_asset(asset_path, cx).unwrap(); } } @@ -503,12 +503,11 @@ pub fn handle_keymap_file_changes( keymap_content.clone().add_to_cx(cx).log_err(); }); - let mut old_base_keymap = - cx.read(|cx| *settings::get_setting::(None, cx)); + let mut old_base_keymap = cx.read(|cx| *settings::get::(cx)); drop(settings_subscription); settings_subscription = Some(cx.update(|cx| { cx.observe_global::(move |cx| { - let new_base_keymap = *settings::get_setting::(None, cx); + let new_base_keymap = *settings::get::(cx); if new_base_keymap != old_base_keymap { old_base_keymap = new_base_keymap.clone(); @@ -2047,12 +2046,7 @@ mod tests { for theme_name in themes.list(false).map(|meta| meta.name) { let theme = themes.get(&theme_name).unwrap(); assert_eq!(theme.meta.name, theme_name); - if theme.meta.name - == settings::get_setting::(None, cx) - .theme - .meta - .name - { + if theme.meta.name == settings::get::(cx).theme.meta.name { has_default_theme = true; } } From 258723566f721cfad53e7e260f74cd4d94a0c1c7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 May 2023 15:06:11 -0700 Subject: [PATCH 126/168] Rename settings::register_setting -> settings::register --- crates/auto_update/src/auto_update.rs | 2 +- crates/client/src/client.rs | 2 +- crates/editor/src/editor.rs | 2 +- crates/journal/src/journal.rs | 2 +- crates/language/src/language_settings.rs | 2 +- crates/project/src/project.rs | 2 +- crates/settings/src/settings_file.rs | 2 +- crates/terminal/src/terminal.rs | 2 +- crates/theme/src/theme.rs | 2 +- crates/vim/src/vim.rs | 2 +- crates/welcome/src/welcome.rs | 2 +- crates/workspace/src/workspace.rs | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 7353c2597f..822886b580 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -77,7 +77,7 @@ impl Setting for AutoUpdateSetting { } pub fn init(http_client: Arc, server_url: String, cx: &mut AppContext) { - settings::register_setting::(cx); + settings::register::(cx); if let Some(version) = (*ZED_APP_VERSION).or_else(|| cx.platform().app_version().ok()) { let auto_updater = cx.add_model(|cx| { diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 8950b51a45..311d9a2b88 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -71,7 +71,7 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5); actions!(client, [SignIn, SignOut]); pub fn init_settings(cx: &mut AppContext) { - settings::register_setting::(cx); + settings::register::(cx); } pub fn init(client: &Arc, cx: &mut AppContext) { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9eed6c655e..ee4f19b0a1 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -290,7 +290,7 @@ pub enum Direction { } pub fn init_settings(cx: &mut AppContext) { - settings::register_setting::(cx); + settings::register::(cx); } pub fn init(cx: &mut AppContext) { diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 74868e2f91..99fe997dc5 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -47,7 +47,7 @@ impl settings::Setting for JournalSettings { } pub fn init(app_state: Arc, cx: &mut AppContext) { - settings::register_setting::(cx); + settings::register::(cx); cx.add_global_action(move |_: &NewJournalEntry, cx| new_journal_entry(app_state.clone(), cx)); } diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index cd932ce791..b47982819a 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use std::{num::NonZeroU32, path::Path, sync::Arc}; pub fn init(cx: &mut AppContext) { - settings::register_setting::(cx); + settings::register::(cx); } pub fn language_settings<'a>(language: Option<&str>, cx: &'a AppContext) -> &'a LanguageSettings { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index cdfe35f268..13809622f9 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -390,7 +390,7 @@ impl FormatTrigger { impl Project { pub fn init_settings(cx: &mut AppContext) { - settings::register_setting::(cx); + settings::register::(cx); } pub fn init(client: &Arc, cx: &mut AppContext) { diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 2f71d5b9af..79e0d1eb31 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -7,7 +7,7 @@ use gpui::{executor::Background, AppContext, AssetSource}; use std::{borrow::Cow, io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration}; use util::{paths, ResultExt}; -pub fn register_setting(cx: &mut AppContext) { +pub fn register(cx: &mut AppContext) { cx.update_global::(|store, cx| { store.register_setting::(cx); }); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 412e9fb1da..728f2e348f 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -116,7 +116,7 @@ impl EventListener for ZedListener { } pub fn init(cx: &mut AppContext) { - settings::register_setting::(cx); + settings::register::(cx); } #[derive(Deserialize)] diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index aa8e37814f..d014179a07 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -22,7 +22,7 @@ pub fn current(cx: &AppContext) -> Arc { pub fn init(source: impl AssetSource, cx: &mut AppContext) { cx.set_global(ThemeRegistry::new(source, cx.font_cache().clone())); - settings::register_setting::(cx); + settings::register::(cx); } #[derive(Deserialize, Default)] diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index efb339eed7..d10ec5e824 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -42,7 +42,7 @@ actions!(vim, [Tab, Enter]); impl_actions!(vim, [Number, SwitchMode, PushOperator]); pub fn init(cx: &mut AppContext) { - settings::register_setting::(cx); + settings::register::(cx); editor_events::init(cx); normal::init(cx); diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 89b4e42cbb..000c2fd8d9 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -20,7 +20,7 @@ pub use base_keymap_setting::BaseKeymap; pub const FIRST_OPEN: &str = "first_open"; pub fn init(cx: &mut AppContext) { - settings::register_setting::(cx); + settings::register::(cx); cx.add_action(|workspace: &mut Workspace, _: &Welcome, cx| { let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx)); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 282daf8d8f..8ca6358f9a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -185,7 +185,7 @@ pub type WorkspaceId = i64; impl_actions!(workspace, [ActivatePane]); pub fn init_settings(cx: &mut AppContext) { - settings::register_setting::(cx); + settings::register::(cx); } pub fn init(app_state: Arc, cx: &mut AppContext) { From 42eca3048f58222ad00ddc9fc9ff354b622f6908 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 May 2023 15:56:32 -0700 Subject: [PATCH 127/168] Move font size adjustment code to the theme crate --- crates/diagnostics/src/diagnostics.rs | 4 +- crates/editor/src/editor.rs | 6 +-- crates/settings/src/font_size.rs | 19 -------- crates/settings/src/settings.rs | 2 - crates/terminal/src/terminal.rs | 9 +++- crates/terminal_view/src/terminal_element.rs | 10 ++-- crates/theme/src/theme.rs | 4 +- crates/theme/src/theme_settings.rs | 48 ++++++++++++++++++- crates/theme_testbench/src/theme_testbench.rs | 2 +- crates/zed/src/zed.rs | 21 ++------ 10 files changed, 69 insertions(+), 56 deletions(-) delete mode 100644 crates/settings/src/font_size.rs diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 3385596f17..a202a6082c 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -682,9 +682,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { let settings = settings::get::(cx); let theme = &settings.theme.editor; let style = theme.diagnostic_header.clone(); - let font_size = (style.text_scale_factor - * settings::font_size_for_setting(settings.buffer_font_size, cx)) - .round(); + let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round(); let icon_width = cx.em_width * style.icon_width_factor; let icon = if diagnostic.severity == DiagnosticSeverity::ERROR { Svg::new("icons/circle_x_mark_12.svg") diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ee4f19b0a1..0fb7a10a16 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7446,7 +7446,7 @@ fn build_style( let font_id = font_cache .select_font(font_family_id, &font_properties) .unwrap(); - let font_size = settings::font_size_for_setting(settings.buffer_font_size, cx); + let font_size = settings.buffer_font_size(cx); EditorStyle { text: TextStyle { color: settings.theme.editor.text_color, @@ -7619,9 +7619,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend let settings = settings::get::(cx); let theme = &settings.theme.editor; let style = diagnostic_style(diagnostic.severity, is_valid, theme); - let font_size = (style.text_scale_factor - * settings::font_size_for_setting(settings.buffer_font_size, cx)) - .round(); + let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round(); Flex::column() .with_children(highlighted_lines.iter().map(|(line, highlights)| { Label::new( diff --git a/crates/settings/src/font_size.rs b/crates/settings/src/font_size.rs deleted file mode 100644 index 79abfeb550..0000000000 --- a/crates/settings/src/font_size.rs +++ /dev/null @@ -1,19 +0,0 @@ -use gpui::AppContext; - -#[derive(Default)] -pub struct FontSizeDelta(pub f32); - -pub fn adjust_font_size_delta(cx: &mut AppContext, f: fn(&mut f32, cx: &mut AppContext)) { - cx.update_default_global::(|size, cx| { - f(&mut size.0, cx); - }); - cx.refresh_windows(); -} - -pub fn font_size_for_setting(size: f32, cx: &AppContext) -> f32 { - if cx.has_global::() { - size + cx.global::().0 - } else { - size - } -} diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index e4784d61ac..4ab504a96f 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -1,11 +1,9 @@ -mod font_size; mod keymap_file; mod settings_file; mod settings_store; use std::{borrow::Cow, str}; -pub use font_size::{adjust_font_size_delta, font_size_for_setting}; use gpui::AssetSource; pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; pub use settings_file::*; diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 728f2e348f..98d85d00e1 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -123,7 +123,7 @@ pub fn init(cx: &mut AppContext) { pub struct TerminalSettings { pub shell: Shell, pub working_directory: WorkingDirectory, - pub font_size: Option, + font_size: Option, pub font_family: Option, pub line_height: TerminalLineHeight, pub font_features: Option, @@ -149,6 +149,13 @@ pub struct TerminalSettingsContent { pub copy_on_select: Option, } +impl TerminalSettings { + pub fn font_size(&self, cx: &AppContext) -> Option { + self.font_size + .map(|size| theme::adjusted_font_size(size, cx)) + } +} + impl settings::Setting for TerminalSettings { const KEY: Option<&'static str> = Some("terminal"); diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 6137f8eff6..18c85db980 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -16,7 +16,6 @@ use gpui::{ use itertools::Itertools; use language::CursorShape; use ordered_float::OrderedFloat; -use settings::font_size_for_setting; use terminal::{ alacritty_terminal::{ ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, @@ -531,12 +530,9 @@ impl Element for TerminalElement { let tooltip_style = settings.theme.tooltip.clone(); let font_cache = cx.font_cache(); - let font_size = font_size_for_setting( - terminal_settings - .font_size - .unwrap_or(settings.buffer_font_size), - cx, - ); + let font_size = terminal_settings + .font_size(cx) + .unwrap_or(settings.buffer_font_size(cx)); let font_family_name = terminal_settings .font_family .as_ref() diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index d014179a07..7a94f45656 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -13,8 +13,8 @@ use serde_json::Value; use std::{collections::HashMap, sync::Arc}; use ui::{ButtonStyle, CheckboxStyle, IconStyle, ModalStyle, SvgStyle}; -pub use theme_registry::ThemeRegistry; -pub use theme_settings::ThemeSettings; +pub use theme_registry::*; +pub use theme_settings::*; pub fn current(cx: &AppContext) -> Arc { settings::get::(cx).theme.clone() diff --git a/crates/theme/src/theme_settings.rs b/crates/theme/src/theme_settings.rs index 85958e576d..9c8f1e3553 100644 --- a/crates/theme/src/theme_settings.rs +++ b/crates/theme/src/theme_settings.rs @@ -12,15 +12,19 @@ use settings::SettingsJsonSchemaParams; use std::sync::Arc; use util::ResultExt as _; +const MIN_FONT_SIZE: f32 = 6.0; + #[derive(Clone)] pub struct ThemeSettings { pub buffer_font_family_name: String, pub buffer_font_features: fonts::Features, pub buffer_font_family: FamilyId, - pub buffer_font_size: f32, + buffer_font_size: f32, pub theme: Arc, } +pub struct AdjustedBufferFontSize(pub f32); + #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct ThemeSettingsContent { #[serde(default)] @@ -33,6 +37,48 @@ pub struct ThemeSettingsContent { pub theme: Option, } +impl ThemeSettings { + pub fn buffer_font_size(&self, cx: &AppContext) -> f32 { + if cx.has_global::() { + cx.global::().0 + } else { + self.buffer_font_size + } + .max(MIN_FONT_SIZE) + } +} + +pub fn adjusted_font_size(size: f32, cx: &AppContext) -> f32 { + if cx.has_global::() { + let buffer_font_size = settings::get::(cx).buffer_font_size; + let delta = cx.global::().0 - buffer_font_size; + size + delta + } else { + size + } + .max(MIN_FONT_SIZE) +} + +pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut f32)) { + if !cx.has_global::() { + let buffer_font_size = settings::get::(cx).buffer_font_size; + cx.set_global(AdjustedBufferFontSize(buffer_font_size)); + } + + cx.update_global::(|delta, cx| { + f(&mut delta.0); + delta.0 = delta + .0 + .max(MIN_FONT_SIZE - settings::get::(cx).buffer_font_size); + }); + cx.refresh_windows(); +} + +pub fn reset_font_size(cx: &mut AppContext) { + cx.remove_global::(); + cx.refresh_windows(); +} + impl settings::Setting for ThemeSettings { const KEY: Option<&'static str> = None; diff --git a/crates/theme_testbench/src/theme_testbench.rs b/crates/theme_testbench/src/theme_testbench.rs index d0f50da07c..258249b599 100644 --- a/crates/theme_testbench/src/theme_testbench.rs +++ b/crates/theme_testbench/src/theme_testbench.rs @@ -222,7 +222,7 @@ impl ThemeTestbench { let settings = settings::get::(cx); let font_cache = cx.font_cache(); let family_id = settings.buffer_font_family; - let font_size = settings::font_size_for_setting(settings.buffer_font_size, cx); + let font_size = settings.buffer_font_size(cx); let font_id = font_cache .select_font(family_id, &Default::default()) .unwrap(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 8f241639ea..376ecfc06b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -29,12 +29,9 @@ use project_panel::ProjectPanel; use search::{BufferSearchBar, ProjectSearchBar}; use serde::Deserialize; use serde_json::to_string_pretty; -use settings::{ - adjust_font_size_delta, KeymapFileContent, SettingsStore, DEFAULT_SETTINGS_ASSET_PATH, -}; +use settings::{KeymapFileContent, SettingsStore, DEFAULT_SETTINGS_ASSET_PATH}; use std::{borrow::Cow, str, sync::Arc}; use terminal_view::terminal_button::TerminalButton; -use theme::ThemeSettings; use util::{channel::ReleaseChannel, paths, ResultExt}; use uuid::Uuid; use welcome::BaseKeymap; @@ -76,8 +73,6 @@ actions!( ] ); -const MIN_FONT_SIZE: f32 = 6.0; - pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { cx.add_action(about); cx.add_global_action(|_: &Hide, cx: &mut gpui::AppContext| { @@ -121,18 +116,12 @@ pub fn init(app_state: &Arc, cx: &mut gpui::AppContext) { cx.add_global_action(quit); cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| { - adjust_font_size_delta(cx, |size, _| *size += 1.0) + theme::adjust_font_size(cx, |size| *size += 1.0) }); cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| { - adjust_font_size_delta(cx, |size, cx| { - if cx.global::().buffer_font_size + *size > MIN_FONT_SIZE { - *size -= 1.0; - } - }) - }); - cx.add_global_action(move |_: &ResetBufferFontSize, cx| { - adjust_font_size_delta(cx, |size, _| *size = 0.0) + theme::adjust_font_size(cx, |size| *size -= 1.0) }); + cx.add_global_action(move |_: &ResetBufferFontSize, cx| theme::reset_font_size(cx)); cx.add_global_action(move |_: &install_cli::Install, cx| { cx.spawn(|cx| async move { install_cli::install_cli(&cx) @@ -639,7 +628,7 @@ mod tests { collections::HashSet, path::{Path, PathBuf}, }; - use theme::ThemeRegistry; + use theme::{ThemeRegistry, ThemeSettings}; use util::http::FakeHttpClient; use workspace::{ item::{Item, ItemHandle}, From 667f476f7f2254fa94cbaca391c610897b839228 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 May 2023 16:02:04 -0700 Subject: [PATCH 128/168] Remove unused watched_json file --- crates/settings/src/watched_json.rs | 126 ---------------------------- 1 file changed, 126 deletions(-) delete mode 100644 crates/settings/src/watched_json.rs diff --git a/crates/settings/src/watched_json.rs b/crates/settings/src/watched_json.rs deleted file mode 100644 index 16be82fa35..0000000000 --- a/crates/settings/src/watched_json.rs +++ /dev/null @@ -1,126 +0,0 @@ -use fs::Fs; -use futures::StreamExt; -use gpui::{executor, AppContext}; -use postage::sink::Sink as _; -use postage::{prelude::Stream, watch}; -use serde::Deserialize; - -use std::{path::Path, sync::Arc, time::Duration}; -use theme::ThemeRegistry; -use util::ResultExt; - -use crate::{parse_json_with_comments, KeymapFileContent, Settings, SettingsFileContent}; - -#[derive(Clone)] -pub struct WatchedJsonFile(pub watch::Receiver); - -impl WatchedJsonFile -where - T: 'static + for<'de> Deserialize<'de> + Clone + Default + Send + Sync, -{ - pub async fn new( - fs: Arc, - executor: &executor::Background, - path: impl Into>, - ) -> Self { - let path = path.into(); - let settings = Self::load(fs.clone(), &path).await.unwrap_or_default(); - let mut events = fs.watch(&path, Duration::from_millis(500)).await; - let (mut tx, rx) = watch::channel_with(settings); - executor - .spawn(async move { - while events.next().await.is_some() { - if let Some(settings) = Self::load(fs.clone(), &path).await { - if tx.send(settings).await.is_err() { - break; - } - } - } - }) - .detach(); - Self(rx) - } - - ///Loads the given watched JSON file. In the special case that the file is - ///empty (ignoring whitespace) or is not a file, this will return T::default() - async fn load(fs: Arc, path: &Path) -> Option { - if !fs.is_file(path).await { - return Some(T::default()); - } - - fs.load(path).await.log_err().and_then(|data| { - if data.trim().is_empty() { - Some(T::default()) - } else { - parse_json_with_comments(&data).log_err() - } - }) - } - - pub fn current(&self) -> T { - self.0.borrow().clone() - } -} - -pub fn watch_files( - defaults: Settings, - settings_file: WatchedJsonFile, - theme_registry: Arc, - keymap_file: WatchedJsonFile, - cx: &mut AppContext, -) { - watch_settings_file(defaults, settings_file, theme_registry, cx); - watch_keymap_file(keymap_file, cx); -} - -pub(crate) fn watch_settings_file( - defaults: Settings, - mut file: WatchedJsonFile, - theme_registry: Arc, - cx: &mut AppContext, -) { - settings_updated(&defaults, file.0.borrow().clone(), &theme_registry, cx); - cx.spawn(|mut cx| async move { - while let Some(content) = file.0.recv().await { - cx.update(|cx| settings_updated(&defaults, content, &theme_registry, cx)); - } - }) - .detach(); -} - -fn keymap_updated(content: KeymapFileContent, cx: &mut AppContext) { - cx.clear_bindings(); - KeymapFileContent::load_defaults(cx); - content.add_to_cx(cx).log_err(); -} - -fn settings_updated( - defaults: &Settings, - content: SettingsFileContent, - theme_registry: &Arc, - cx: &mut AppContext, -) { - let mut settings = defaults.clone(); - settings.set_user_settings(content, theme_registry, cx.font_cache()); - cx.set_global(settings); - cx.refresh_windows(); -} - -fn watch_keymap_file(mut file: WatchedJsonFile, cx: &mut AppContext) { - cx.spawn(|mut cx| async move { - let mut settings_subscription = None; - while let Some(content) = file.0.recv().await { - cx.update(|cx| { - let old_base_keymap = cx.global::().base_keymap; - keymap_updated(content.clone(), cx); - settings_subscription = Some(cx.observe_global::(move |cx| { - let settings = cx.global::(); - if settings.base_keymap != old_base_keymap { - keymap_updated(content.clone(), cx); - } - })); - }); - } - }) - .detach(); -} From 823e15d85a5dbc1605c83d5bf5958364c0477f21 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 May 2023 16:02:21 -0700 Subject: [PATCH 129/168] Refresh windows when settings file changes --- crates/settings/src/settings.rs | 3 +-- crates/settings/src/settings_file.rs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 4ab504a96f..840797c6ad 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -2,12 +2,11 @@ mod keymap_file; mod settings_file; mod settings_store; -use std::{borrow::Cow, str}; - use gpui::AssetSource; pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; pub use settings_file::*; pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore}; +use std::{borrow::Cow, str}; pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json"; pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json"; diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 79e0d1eb31..30848713d9 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -89,6 +89,7 @@ pub fn handle_settings_file_changes( .set_user_settings(&user_settings_content, cx) .log_err(); }); + cx.refresh_windows(); }); } }) From a56793c2140ed864569d141f54a5c7e5d17d245c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 May 2023 16:14:05 -0700 Subject: [PATCH 130/168] Clear buffer font size adjustments when buffer font size setting changes --- crates/theme/src/theme.rs | 11 +++++++++++ crates/theme/src/theme_settings.rs | 8 +++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 7a94f45656..eb404cdaad 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -10,6 +10,7 @@ use gpui::{ }; use serde::{de::DeserializeOwned, Deserialize}; use serde_json::Value; +use settings::SettingsStore; use std::{collections::HashMap, sync::Arc}; use ui::{ButtonStyle, CheckboxStyle, IconStyle, ModalStyle, SvgStyle}; @@ -23,6 +24,16 @@ pub fn current(cx: &AppContext) -> Arc { pub fn init(source: impl AssetSource, cx: &mut AppContext) { cx.set_global(ThemeRegistry::new(source, cx.font_cache().clone())); settings::register::(cx); + + let mut prev_buffer_font_size = settings::get::(cx).buffer_font_size; + cx.observe_global::(move |cx| { + let buffer_font_size = settings::get::(cx).buffer_font_size; + if buffer_font_size != prev_buffer_font_size { + prev_buffer_font_size = buffer_font_size; + reset_font_size(cx); + } + }) + .detach(); } #[derive(Deserialize, Default)] diff --git a/crates/theme/src/theme_settings.rs b/crates/theme/src/theme_settings.rs index 9c8f1e3553..f86d3fd8dd 100644 --- a/crates/theme/src/theme_settings.rs +++ b/crates/theme/src/theme_settings.rs @@ -19,7 +19,7 @@ pub struct ThemeSettings { pub buffer_font_family_name: String, pub buffer_font_features: fonts::Features, pub buffer_font_family: FamilyId, - buffer_font_size: f32, + pub(crate) buffer_font_size: f32, pub theme: Arc, } @@ -75,8 +75,10 @@ pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut f32)) { } pub fn reset_font_size(cx: &mut AppContext) { - cx.remove_global::(); - cx.refresh_windows(); + if cx.has_global::() { + cx.remove_global::(); + cx.refresh_windows(); + } } impl settings::Setting for ThemeSettings { From a9b107f155ead0f414c865808026e12c307eb348 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 May 2023 16:36:06 -0700 Subject: [PATCH 131/168] Fix release-mode stub for watch_themes --- crates/zed/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 299e77efce..3b289392c5 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -607,7 +607,6 @@ async fn watch_themes(fs: Arc, mut cx: AsyncAppContext) -> Option<()> { #[cfg(not(debug_assertions))] async fn watch_themes( _fs: Arc, - _themes: Arc, _cx: AsyncAppContext, ) -> Option<()> { None From 5c437e2098a7c02aeec7e0e10cabf120a8bc1dbb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 May 2023 16:53:48 -0700 Subject: [PATCH 132/168] Fix crashes when running zed bundled --- crates/zed/src/languages/json.rs | 2 +- crates/zed/src/main.rs | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 1fb1a5a941..406d54cc03 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -118,7 +118,7 @@ impl LspAdapter for JsonLspAdapter { cx: &mut AppContext, ) -> Option> { let action_names = cx.all_action_names().collect::>(); - let staff_mode = cx.global::().0; + let staff_mode = cx.default_global::().0; let language_names = &self.languages.language_names(); let settings_schema = cx.global::().json_schema( &SettingsJsonSchemaParams { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 3b289392c5..2f359240bc 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -132,10 +132,6 @@ fn main() { handle_settings_file_changes(user_settings_file_rx, cx); handle_keymap_file_changes(user_keymap_file_rx, cx); - if !stdout_is_a_pty() { - upload_previous_panics(http.clone(), cx); - } - let client = client::Client::new(http.clone(), cx); let mut languages = LanguageRegistry::new(login_shell_env_loaded); languages.set_executor(cx.background().clone()); @@ -194,7 +190,7 @@ fn main() { background_actions, }); cx.set_global(Arc::downgrade(&app_state)); - auto_update::init(http, client::ZED_SERVER_URL.clone(), cx); + auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx); workspace::init(app_state.clone(), cx); recent_projects::init(cx); @@ -222,6 +218,8 @@ fn main() { workspace::open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx); } } else { + upload_previous_panics(http.clone(), cx); + // TODO Development mode that forces the CLI mode usually runs Zed binary as is instead // of an *app, hence gets no specific callbacks run. Emulate them here, if needed. if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some() @@ -605,10 +603,7 @@ async fn watch_themes(fs: Arc, mut cx: AsyncAppContext) -> Option<()> { } #[cfg(not(debug_assertions))] -async fn watch_themes( - _fs: Arc, - _cx: AsyncAppContext, -) -> Option<()> { +async fn watch_themes(_fs: Arc, _cx: AsyncAppContext) -> Option<()> { None } From 5ab4eab369e49157434ad865bf3cb5ebb95d0c1b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 18 May 2023 13:49:55 +0300 Subject: [PATCH 133/168] Unite dmg build jobs --- .github/workflows/build_dmg.yml | 54 --------------------------------- .github/workflows/ci.yml | 8 ++--- 2 files changed, 4 insertions(+), 58 deletions(-) delete mode 100644 .github/workflows/build_dmg.yml diff --git a/.github/workflows/build_dmg.yml b/.github/workflows/build_dmg.yml deleted file mode 100644 index 989914e5e8..0000000000 --- a/.github/workflows/build_dmg.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Build Zed.dmg - -on: - push: - branches: - - main - - "v[0-9]+.[0-9]+.x" - pull_request: - -defaults: - run: - shell: bash -euxo pipefail {0} - -concurrency: - # Allow only one workflow per any non-`main` branch. - group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }} - cancel-in-progress: true - -env: - RUST_BACKTRACE: 1 - COPT: '-Werror' - -jobs: - build-dmg: - if: github.ref_name == 'main' || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') - runs-on: - - self-hosted - - test - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - clean: false - submodules: 'recursive' - - - name: Install Rust - run: | - rustup set profile minimal - rustup update stable - - - name: Install node - uses: actions/setup-node@v3 - with: - node-version: 18 - - - name: Build dmg bundle - run: ./script/bundle - - - name: Upload the build artifact - uses: actions/upload-artifact@v3 - with: - name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg - path: ./target/release/Zed.dmg diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4176bd9b9..2cd717d5c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,7 @@ jobs: runs-on: - self-hosted - bundle - if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }} + if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }} needs: tests env: MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} @@ -148,11 +148,11 @@ jobs: - name: Create app bundle run: script/bundle - - name: Upload app bundle to workflow run if main branch + - name: Upload app bundle to workflow run if main branch or specifi label uses: actions/upload-artifact@v2 - if: ${{ github.ref == 'refs/heads/main' }} + if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }} with: - name: Zed.dmg + name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg path: target/release/Zed.dmg - uses: softprops/action-gh-release@v1 From 71ad7e7612b537f6e8ffb27e9c47c7c5318a909a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 May 2023 09:01:38 -0700 Subject: [PATCH 134/168] Define empty theme for tests regardless of cargo features Co-authored-by: Kirill --- crates/file_finder/Cargo.toml | 1 + crates/settings/src/settings_file.rs | 1 - crates/theme/src/theme_registry.rs | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/file_finder/Cargo.toml b/crates/file_finder/Cargo.toml index 0349d26408..cae3fa25ca 100644 --- a/crates/file_finder/Cargo.toml +++ b/crates/file_finder/Cargo.toml @@ -26,6 +26,7 @@ postage.workspace = true gpui = { path = "../gpui", features = ["test-support"] } language = { path = "../language", features = ["test-support"] } workspace = { path = "../workspace", features = ["test-support"] } +theme = { path = "../theme", features = ["test-support"] } serde_json.workspace = true ctor.workspace = true diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 30848713d9..cca2909da2 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -24,7 +24,6 @@ pub fn default_settings() -> Cow<'static, str> { } } -#[cfg(any(test, feature = "test-support"))] pub const EMPTY_THEME_NAME: &'static str = "empty-theme"; #[cfg(any(test, feature = "test-support"))] diff --git a/crates/theme/src/theme_registry.rs b/crates/theme/src/theme_registry.rs index 12ccaf3d51..8565bc3b56 100644 --- a/crates/theme/src/theme_registry.rs +++ b/crates/theme/src/theme_registry.rs @@ -30,7 +30,6 @@ impl ThemeRegistry { font_cache, }); - #[cfg(any(test, feature = "test-support"))] this.themes.lock().insert( settings::EMPTY_THEME_NAME.to_string(), gpui::fonts::with_font_cache(this.font_cache.clone(), || { From 5ab1ae1521be69c704afbf39e8ad6fce0abf7698 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 18 May 2023 11:59:59 -0400 Subject: [PATCH 135/168] Avoid panic in `get_injections` --- crates/language/src/syntax_map.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 88de8e690a..ce1196e946 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -1114,6 +1114,8 @@ fn get_injections( let mut query_cursor = QueryCursorHandle::new(); let mut prev_match = None; + // Ensure that a `ParseStep` is created for every combined injection language, even + // if there currently no matches for that injection. combined_injection_ranges.clear(); for pattern in &config.patterns { if let (Some(language_name), true) = (pattern.language.as_ref(), pattern.combined) { @@ -1174,8 +1176,8 @@ fn get_injections( if let Some(language) = language { if combined { combined_injection_ranges - .get_mut(&language.clone()) - .unwrap() + .entry(language.clone()) + .or_default() .extend(content_ranges); } else { queue.push(ParseStep { From 711d2c6fe771e9ba7094fa68a2c4a1eb35cfa2c5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 13 May 2023 00:44:21 +0300 Subject: [PATCH 136/168] Maintain recently opened files history --- crates/file_finder/src/file_finder.rs | 104 +++++++++++++++++++++++++- crates/project/src/project.rs | 50 ++++++++++++- 2 files changed, 151 insertions(+), 3 deletions(-) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index b318f1d167..311882bd8e 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -239,6 +239,13 @@ impl PickerDelegate for FileFinderDelegate { if raw_query.is_empty() { self.latest_search_id = post_inc(&mut self.search_count); self.matches.clear(); + self.matches = self + .project + .read(cx) + .search_panel_state() + .recent_selections() + .cloned() + .collect(); cx.notify(); Task::ready(()) } else { @@ -261,11 +268,14 @@ impl PickerDelegate for FileFinderDelegate { fn confirm(&mut self, cx: &mut ViewContext) { if let Some(m) = self.matches.get(self.selected_index()) { if let Some(workspace) = self.workspace.upgrade(cx) { + self.project.update(cx, |project, _cx| { + project.update_search_panel_state().add_selection(m.clone()) + }); + let project_path = ProjectPath { worktree_id: WorktreeId::from_usize(m.worktree_id), path: m.path.clone(), }; - let open_task = workspace.update(cx, |workspace, cx| { workspace.open_path(project_path.clone(), None, true, cx) }); @@ -301,7 +311,6 @@ impl PickerDelegate for FileFinderDelegate { .log_err(); } } - workspace .update(&mut cx, |workspace, cx| workspace.dismiss_modal(cx)) .log_err(); @@ -904,6 +913,97 @@ mod tests { }); } + #[gpui::test] + async fn test_query_history(cx: &mut gpui::TestAppContext) { + let app_state = init_test(cx); + + app_state + .fs + .as_fake() + .insert_tree( + "/src", + json!({ + "test": { + "first.rs": "// First Rust file", + "second.rs": "// Second Rust file", + "third.rs": "// Third Rust file", + } + }), + ) + .await; + + let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; + let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + cx.dispatch_action(window_id, Toggle); + let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); + + finder + .update(cx, |finder, cx| { + finder.delegate_mut().update_matches("fir".to_string(), cx) + }) + .await; + cx.dispatch_action(window_id, SelectNext); + cx.dispatch_action(window_id, Confirm); + + cx.dispatch_action(window_id, Toggle); + let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); + finder + .update(cx, |finder, cx| { + finder.delegate_mut().update_matches("sec".to_string(), cx) + }) + .await; + cx.dispatch_action(window_id, SelectNext); + cx.dispatch_action(window_id, Confirm); + + finder.read_with(cx, |finder, cx| { + let recent_query_paths = finder + .delegate() + .project + .read(cx) + .search_panel_state() + .recent_selections() + .map(|query| query.path.to_path_buf()) + .collect::>(); + assert_eq!( + vec![ + Path::new("test/second.rs").to_path_buf(), + Path::new("test/first.rs").to_path_buf(), + ], + recent_query_paths, + "Two finder queries should produce only two recent queries. Second query should be more recent (first)" + ) + }); + + cx.dispatch_action(window_id, Toggle); + let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); + finder + .update(cx, |finder, cx| { + finder.delegate_mut().update_matches("fir".to_string(), cx) + }) + .await; + cx.dispatch_action(window_id, SelectNext); + cx.dispatch_action(window_id, Confirm); + + finder.read_with(cx, |finder, cx| { + let recent_query_paths = finder + .delegate() + .project + .read(cx) + .search_panel_state() + .recent_selections() + .map(|query| query.path.to_path_buf()) + .collect::>(); + assert_eq!( + vec![ + Path::new("test/first.rs").to_path_buf(), + Path::new("test/second.rs").to_path_buf(), + ], + recent_query_paths, + "Three finder queries on two different files should produce only two recent queries. First query should be more recent (first), since got queried again" + ) + }); + } + fn init_test(cx: &mut TestAppContext) -> Arc { cx.foreground().forbid_parking(); cx.update(|cx| { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 13809622f9..f19ef44644 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -12,13 +12,14 @@ mod project_tests; use anyhow::{anyhow, Context, Result}; use client::{proto, Client, TypedEnvelope, UserStore}; use clock::ReplicaId; -use collections::{hash_map, BTreeMap, HashMap, HashSet}; +use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque}; use copilot::Copilot; use futures::{ channel::mpsc::{self, UnboundedReceiver}, future::{try_join_all, Shared}, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt, }; +use fuzzy::PathMatch; use gpui::{ AnyModelHandle, AppContext, AsyncAppContext, BorrowAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle, @@ -135,6 +136,7 @@ pub struct Project { _maintain_workspace_config: Task<()>, terminals: Terminals, copilot_enabled: bool, + search_panel_state: SearchPanelState, } struct LspBufferSnapshot { @@ -388,6 +390,42 @@ impl FormatTrigger { } } +const MAX_RECENT_SELECTIONS: usize = 20; + +#[derive(Debug, Default)] +pub struct SearchPanelState { + recent_selections: VecDeque, +} + +impl SearchPanelState { + pub fn recent_selections(&self) -> impl Iterator { + self.recent_selections.iter().rev() + } + + pub fn add_selection(&mut self, mut new_selection: PathMatch) { + let old_len = self.recent_selections.len(); + + // remove `new_selection` element, if it's in the list + self.recent_selections.retain(|old_selection| { + old_selection.worktree_id != new_selection.worktree_id + || old_selection.path != new_selection.path + }); + // if `new_selection` was not present and we're adding a new element, + // ensure we do not exceed max allowed elements + if self.recent_selections.len() == old_len { + if self.recent_selections.len() >= MAX_RECENT_SELECTIONS { + self.recent_selections.pop_front(); + } + } + + // do not highlight query matches in the selection + new_selection.positions.clear(); + // always re-add the element even if it exists to the back + // this way, it gets to the top as the most recently selected element + self.recent_selections.push_back(new_selection); + } +} + impl Project { pub fn init_settings(cx: &mut AppContext) { settings::register::(cx); @@ -487,6 +525,7 @@ impl Project { local_handles: Vec::new(), }, copilot_enabled: Copilot::global(cx).is_some(), + search_panel_state: SearchPanelState::default(), } }) } @@ -577,6 +616,7 @@ impl Project { local_handles: Vec::new(), }, copilot_enabled: Copilot::global(cx).is_some(), + search_panel_state: SearchPanelState::default(), }; for worktree in worktrees { let _ = this.add_worktree(&worktree, cx); @@ -6435,6 +6475,14 @@ impl Project { }) } + pub fn search_panel_state(&self) -> &SearchPanelState { + &self.search_panel_state + } + + pub fn update_search_panel_state(&mut self) -> &mut SearchPanelState { + &mut self.search_panel_state + } + fn primary_language_servers_for_buffer( &self, buffer: &Buffer, From 201d513c5019bb901e580262fae9c15505b4eda6 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 17 May 2023 17:35:06 +0300 Subject: [PATCH 137/168] Show navigation history in the file finder modal co-authored-by: Max --- Cargo.lock | 1 + crates/file_finder/src/file_finder.rs | 157 ++++++++------------------ crates/project/src/project.rs | 50 +------- crates/project/src/worktree.rs | 2 +- crates/workspace/Cargo.toml | 1 + crates/workspace/src/dock.rs | 13 ++- crates/workspace/src/pane.rs | 51 ++++++++- crates/workspace/src/workspace.rs | 52 ++++++++- 8 files changed, 162 insertions(+), 165 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce727a9c6c..be8a8a74bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8676,6 +8676,7 @@ dependencies = [ "gpui", "indoc", "install_cli", + "itertools", "language", "lazy_static", "log", diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 311882bd8e..94b48d9e45 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -25,10 +25,11 @@ pub struct FileFinderDelegate { latest_search_id: usize, latest_search_did_cancel: bool, latest_search_query: Option>, - relative_to: Option>, + currently_opened_path: Option, matches: Vec, selected: Option<(usize, Arc)>, cancel_flag: Arc, + history_items: Vec, } actions!(file_finder, [Toggle]); @@ -38,17 +39,26 @@ pub fn init(cx: &mut AppContext) { FileFinder::init(cx); } +const MAX_RECENT_SELECTIONS: usize = 20; + fn toggle_file_finder(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { workspace.toggle_modal(cx, |workspace, cx| { - let relative_to = workspace + let history_items = workspace.recent_navigation_history(Some(MAX_RECENT_SELECTIONS), cx); + let currently_opened_path = workspace .active_item(cx) - .and_then(|item| item.project_path(cx)) - .map(|project_path| project_path.path.clone()); + .and_then(|item| item.project_path(cx)); + let project = workspace.project().clone(); let workspace = cx.handle().downgrade(); let finder = cx.add_view(|cx| { Picker::new( - FileFinderDelegate::new(workspace, project, relative_to, cx), + FileFinderDelegate::new( + workspace, + project, + currently_opened_path, + history_items, + cx, + ), cx, ) }); @@ -106,7 +116,8 @@ impl FileFinderDelegate { pub fn new( workspace: WeakViewHandle, project: ModelHandle, - relative_to: Option>, + currently_opened_path: Option, + history_items: Vec, cx: &mut ViewContext, ) -> Self { cx.observe(&project, |picker, _, cx| { @@ -120,10 +131,11 @@ impl FileFinderDelegate { latest_search_id: 0, latest_search_did_cancel: false, latest_search_query: None, - relative_to, + currently_opened_path, matches: Vec::new(), selected: None, cancel_flag: Arc::new(AtomicBool::new(false)), + history_items, } } @@ -132,7 +144,10 @@ impl FileFinderDelegate { query: PathLikeWithPosition, cx: &mut ViewContext, ) -> Task<()> { - let relative_to = self.relative_to.clone(); + let relative_to = self + .currently_opened_path + .as_ref() + .map(|project_path| Arc::clone(&project_path.path)); let worktrees = self .project .read(cx) @@ -239,12 +254,22 @@ impl PickerDelegate for FileFinderDelegate { if raw_query.is_empty() { self.latest_search_id = post_inc(&mut self.search_count); self.matches.clear(); + self.matches = self - .project - .read(cx) - .search_panel_state() - .recent_selections() - .cloned() + .currently_opened_path + .iter() // if exists, bubble the currently opened path to the top + .chain(self.history_items.iter().filter(|history_item| { + Some(*history_item) != self.currently_opened_path.as_ref() + })) + .enumerate() + .map(|(i, history_item)| PathMatch { + score: i as f64, + positions: Vec::new(), + worktree_id: history_item.worktree_id.0, + path: Arc::clone(&history_item.path), + path_prefix: "".into(), + distance_to_relative_ancestor: usize::MAX, + }) .collect(); cx.notify(); Task::ready(()) @@ -268,10 +293,6 @@ impl PickerDelegate for FileFinderDelegate { fn confirm(&mut self, cx: &mut ViewContext) { if let Some(m) = self.matches.get(self.selected_index()) { if let Some(workspace) = self.workspace.upgrade(cx) { - self.project.update(cx, |project, _cx| { - project.update_search_panel_state().add_selection(m.clone()) - }); - let project_path = ProjectPath { worktree_id: WorktreeId::from_usize(m.worktree_id), path: m.path.clone(), @@ -613,6 +634,7 @@ mod tests { workspace.downgrade(), workspace.read(cx).project().clone(), None, + Vec::new(), cx, ), cx, @@ -697,6 +719,7 @@ mod tests { workspace.downgrade(), workspace.read(cx).project().clone(), None, + Vec::new(), cx, ), cx, @@ -732,6 +755,7 @@ mod tests { workspace.downgrade(), workspace.read(cx).project().clone(), None, + Vec::new(), cx, ), cx, @@ -797,6 +821,7 @@ mod tests { workspace.downgrade(), workspace.read(cx).project().clone(), None, + Vec::new(), cx, ), cx, @@ -846,13 +871,17 @@ mod tests { // When workspace has an active item, sort items which are closer to that item // first when they have the same name. In this case, b.txt is closer to dir2's a.txt // so that one should be sorted earlier - let b_path = Some(Arc::from(Path::new("/root/dir2/b.txt"))); + let b_path = Some(ProjectPath { + worktree_id: WorktreeId(workspace.id()), + path: Arc::from(Path::new("/root/dir2/b.txt")), + }); let (_, finder) = cx.add_window(|cx| { Picker::new( FileFinderDelegate::new( workspace.downgrade(), workspace.read(cx).project().clone(), b_path, + Vec::new(), cx, ), cx, @@ -897,6 +926,7 @@ mod tests { workspace.downgrade(), workspace.read(cx).project().clone(), None, + Vec::new(), cx, ), cx, @@ -913,97 +943,6 @@ mod tests { }); } - #[gpui::test] - async fn test_query_history(cx: &mut gpui::TestAppContext) { - let app_state = init_test(cx); - - app_state - .fs - .as_fake() - .insert_tree( - "/src", - json!({ - "test": { - "first.rs": "// First Rust file", - "second.rs": "// Second Rust file", - "third.rs": "// Third Rust file", - } - }), - ) - .await; - - let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); - cx.dispatch_action(window_id, Toggle); - let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); - - finder - .update(cx, |finder, cx| { - finder.delegate_mut().update_matches("fir".to_string(), cx) - }) - .await; - cx.dispatch_action(window_id, SelectNext); - cx.dispatch_action(window_id, Confirm); - - cx.dispatch_action(window_id, Toggle); - let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); - finder - .update(cx, |finder, cx| { - finder.delegate_mut().update_matches("sec".to_string(), cx) - }) - .await; - cx.dispatch_action(window_id, SelectNext); - cx.dispatch_action(window_id, Confirm); - - finder.read_with(cx, |finder, cx| { - let recent_query_paths = finder - .delegate() - .project - .read(cx) - .search_panel_state() - .recent_selections() - .map(|query| query.path.to_path_buf()) - .collect::>(); - assert_eq!( - vec![ - Path::new("test/second.rs").to_path_buf(), - Path::new("test/first.rs").to_path_buf(), - ], - recent_query_paths, - "Two finder queries should produce only two recent queries. Second query should be more recent (first)" - ) - }); - - cx.dispatch_action(window_id, Toggle); - let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); - finder - .update(cx, |finder, cx| { - finder.delegate_mut().update_matches("fir".to_string(), cx) - }) - .await; - cx.dispatch_action(window_id, SelectNext); - cx.dispatch_action(window_id, Confirm); - - finder.read_with(cx, |finder, cx| { - let recent_query_paths = finder - .delegate() - .project - .read(cx) - .search_panel_state() - .recent_selections() - .map(|query| query.path.to_path_buf()) - .collect::>(); - assert_eq!( - vec![ - Path::new("test/first.rs").to_path_buf(), - Path::new("test/second.rs").to_path_buf(), - ], - recent_query_paths, - "Three finder queries on two different files should produce only two recent queries. First query should be more recent (first), since got queried again" - ) - }); - } - fn init_test(cx: &mut TestAppContext) -> Arc { cx.foreground().forbid_parking(); cx.update(|cx| { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f19ef44644..13809622f9 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -12,14 +12,13 @@ mod project_tests; use anyhow::{anyhow, Context, Result}; use client::{proto, Client, TypedEnvelope, UserStore}; use clock::ReplicaId; -use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque}; +use collections::{hash_map, BTreeMap, HashMap, HashSet}; use copilot::Copilot; use futures::{ channel::mpsc::{self, UnboundedReceiver}, future::{try_join_all, Shared}, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt, }; -use fuzzy::PathMatch; use gpui::{ AnyModelHandle, AppContext, AsyncAppContext, BorrowAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle, @@ -136,7 +135,6 @@ pub struct Project { _maintain_workspace_config: Task<()>, terminals: Terminals, copilot_enabled: bool, - search_panel_state: SearchPanelState, } struct LspBufferSnapshot { @@ -390,42 +388,6 @@ impl FormatTrigger { } } -const MAX_RECENT_SELECTIONS: usize = 20; - -#[derive(Debug, Default)] -pub struct SearchPanelState { - recent_selections: VecDeque, -} - -impl SearchPanelState { - pub fn recent_selections(&self) -> impl Iterator { - self.recent_selections.iter().rev() - } - - pub fn add_selection(&mut self, mut new_selection: PathMatch) { - let old_len = self.recent_selections.len(); - - // remove `new_selection` element, if it's in the list - self.recent_selections.retain(|old_selection| { - old_selection.worktree_id != new_selection.worktree_id - || old_selection.path != new_selection.path - }); - // if `new_selection` was not present and we're adding a new element, - // ensure we do not exceed max allowed elements - if self.recent_selections.len() == old_len { - if self.recent_selections.len() >= MAX_RECENT_SELECTIONS { - self.recent_selections.pop_front(); - } - } - - // do not highlight query matches in the selection - new_selection.positions.clear(); - // always re-add the element even if it exists to the back - // this way, it gets to the top as the most recently selected element - self.recent_selections.push_back(new_selection); - } -} - impl Project { pub fn init_settings(cx: &mut AppContext) { settings::register::(cx); @@ -525,7 +487,6 @@ impl Project { local_handles: Vec::new(), }, copilot_enabled: Copilot::global(cx).is_some(), - search_panel_state: SearchPanelState::default(), } }) } @@ -616,7 +577,6 @@ impl Project { local_handles: Vec::new(), }, copilot_enabled: Copilot::global(cx).is_some(), - search_panel_state: SearchPanelState::default(), }; for worktree in worktrees { let _ = this.add_worktree(&worktree, cx); @@ -6475,14 +6435,6 @@ impl Project { }) } - pub fn search_panel_state(&self) -> &SearchPanelState { - &self.search_panel_state - } - - pub fn update_search_panel_state(&mut self) -> &mut SearchPanelState { - &mut self.search_panel_state - } - fn primary_language_servers_for_buffer( &self, buffer: &Buffer, diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 403d893425..550a27ea9f 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -58,7 +58,7 @@ use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet}; use util::{paths::HOME, ResultExt, TakeUntilExt, TryFutureExt}; #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] -pub struct WorktreeId(usize); +pub struct WorktreeId(pub usize); pub enum Worktree { Local(LocalWorktree), diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 26797e8d6c..33e5e7aefe 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -38,6 +38,7 @@ theme = { path = "../theme" } util = { path = "../util" } async-recursion = "1.0.0" +itertools = "0.10" bincode = "1.2.1" anyhow.workspace = true futures.workspace = true diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index d1ec80de95..beec6a0515 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -12,6 +12,7 @@ use gpui::{ platform::{CursorStyle, MouseButton}, AnyElement, AppContext, Border, Element, SizeConstraint, ViewContext, ViewHandle, }; +use std::sync::{atomic::AtomicUsize, Arc}; use theme::Theme; pub use toggle_dock_button::ToggleDockButton; @@ -170,13 +171,21 @@ impl Dock { pub fn new( default_item_factory: DockDefaultItemFactory, background_actions: BackgroundActions, + pane_history_timestamp: Arc, cx: &mut ViewContext, ) -> Self { let position = DockPosition::Hidden(settings::get::(cx).default_dock_anchor); let workspace = cx.weak_handle(); - let pane = - cx.add_view(|cx| Pane::new(workspace, Some(position.anchor()), background_actions, cx)); + let pane = cx.add_view(|cx| { + Pane::new( + workspace, + Some(position.anchor()), + background_actions, + pane_history_timestamp, + cx, + ) + }); pane.update(cx, |pane, cx| { pane.set_active(false, cx); }); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 200f83700b..368afcd16c 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -30,7 +30,17 @@ use gpui::{ }; use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; -use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc}; +use std::{ + any::Any, + cell::RefCell, + cmp, mem, + path::Path, + rc::Rc, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, +}; use theme::Theme; use util::ResultExt; @@ -159,6 +169,8 @@ pub struct ItemNavHistory { item: Rc, } +pub struct PaneNavHistory(Rc>); + struct NavHistory { mode: NavigationMode, backward_stack: VecDeque, @@ -166,6 +178,7 @@ struct NavHistory { closed_stack: VecDeque, paths_by_item: HashMap, pane: WeakViewHandle, + next_timestamp: Arc, } #[derive(Copy, Clone)] @@ -187,6 +200,7 @@ impl Default for NavigationMode { pub struct NavigationEntry { pub item: Rc, pub data: Option>, + pub timestamp: usize, } struct DraggedItem { @@ -226,6 +240,7 @@ impl Pane { workspace: WeakViewHandle, docked: Option, background_actions: BackgroundActions, + next_timestamp: Arc, cx: &mut ViewContext, ) -> Self { let pane_view_id = cx.view_id(); @@ -249,6 +264,7 @@ impl Pane { closed_stack: Default::default(), paths_by_item: Default::default(), pane: handle.clone(), + next_timestamp, })), toolbar: cx.add_view(|_| Toolbar::new(handle)), tab_bar_context_menu: TabBarContextMenu { @@ -292,6 +308,10 @@ impl Pane { } } + pub fn nav_history(&self) -> PaneNavHistory { + PaneNavHistory(self.nav_history.clone()) + } + pub fn go_back( workspace: &mut Workspace, pane: Option>, @@ -1942,6 +1962,7 @@ impl NavHistory { self.backward_stack.push_back(NavigationEntry { item, data: data.map(|data| Box::new(data) as Box), + timestamp: self.next_timestamp.fetch_add(1, Ordering::SeqCst), }); self.forward_stack.clear(); } @@ -1952,6 +1973,7 @@ impl NavHistory { self.forward_stack.push_back(NavigationEntry { item, data: data.map(|data| Box::new(data) as Box), + timestamp: self.next_timestamp.fetch_add(1, Ordering::SeqCst), }); } NavigationMode::GoingForward => { @@ -1961,6 +1983,7 @@ impl NavHistory { self.backward_stack.push_back(NavigationEntry { item, data: data.map(|data| Box::new(data) as Box), + timestamp: self.next_timestamp.fetch_add(1, Ordering::SeqCst), }); } NavigationMode::ClosingItem => { @@ -1970,6 +1993,7 @@ impl NavHistory { self.closed_stack.push_back(NavigationEntry { item, data: data.map(|data| Box::new(data) as Box), + timestamp: self.next_timestamp.fetch_add(1, Ordering::SeqCst), }); } } @@ -1985,6 +2009,31 @@ impl NavHistory { } } +impl PaneNavHistory { + pub fn for_each_entry( + &self, + cx: &AppContext, + mut f: impl FnMut(&NavigationEntry, ProjectPath), + ) { + let borrowed_history = self.0.borrow(); + borrowed_history + .forward_stack + .iter() + .chain(borrowed_history.backward_stack.iter()) + .chain(borrowed_history.closed_stack.iter()) + .for_each(|entry| { + if let Some(path) = borrowed_history.paths_by_item.get(&entry.item.id()) { + f(entry, path.clone()); + } else if let Some(item) = entry.item.upgrade(cx) { + let path = item.project_path(cx); + if let Some(path) = path { + f(entry, path); + } + } + }) + } +} + pub struct PaneBackdrop { child_view: usize, child: AnyElement, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 8ca6358f9a..28ad294798 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -47,6 +47,7 @@ use gpui::{ WindowContext, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; +use itertools::Itertools; use language::{LanguageRegistry, Rope}; use std::{ any::TypeId, @@ -55,7 +56,7 @@ use std::{ future::Future, path::{Path, PathBuf}, str, - sync::Arc, + sync::{atomic::AtomicUsize, Arc}, time::Duration, }; @@ -481,6 +482,7 @@ pub struct Workspace { _window_subscriptions: [Subscription; 3], _apply_leader_updates: Task>, _observe_current_user: Task>, + pane_history_timestamp: Arc, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -542,15 +544,24 @@ impl Workspace { .detach(); let weak_handle = cx.weak_handle(); + let pane_history_timestamp = Arc::new(AtomicUsize::new(0)); - let center_pane = cx - .add_view(|cx| Pane::new(weak_handle.clone(), None, app_state.background_actions, cx)); + let center_pane = cx.add_view(|cx| { + Pane::new( + weak_handle.clone(), + None, + app_state.background_actions, + pane_history_timestamp.clone(), + cx, + ) + }); cx.subscribe(¢er_pane, Self::handle_pane_event).detach(); cx.focus(¢er_pane); cx.emit(Event::PaneAdded(center_pane.clone())); let dock = Dock::new( app_state.dock_default_item_factory, app_state.background_actions, + pane_history_timestamp.clone(), cx, ); let dock_pane = dock.pane().clone(); @@ -665,6 +676,7 @@ impl Workspace { _apply_leader_updates, leader_updates_tx, _window_subscriptions: subscriptions, + pane_history_timestamp, }; this.project_remote_id_changed(project.read(cx).remote_id(), cx); cx.defer(|this, cx| this.update_window_title(cx)); @@ -825,6 +837,39 @@ impl Workspace { &self.project } + pub fn recent_navigation_history( + &self, + limit: Option, + cx: &AppContext, + ) -> Vec { + let mut history: HashMap = HashMap::default(); + for pane in &self.panes { + let pane = pane.read(cx); + pane.nav_history() + .for_each_entry(cx, |entry, project_path| { + let timestamp = entry.timestamp; + match history.entry(project_path) { + hash_map::Entry::Occupied(mut entry) => { + if ×tamp > entry.get() { + entry.insert(timestamp); + } + } + hash_map::Entry::Vacant(entry) => { + entry.insert(timestamp); + } + } + }); + } + + history + .into_iter() + .sorted_by_key(|(_, timestamp)| *timestamp) + .map(|(project_path, _)| project_path) + .rev() + .take(limit.unwrap_or(usize::MAX)) + .collect() + } + pub fn client(&self) -> &Client { &self.app_state.client } @@ -1386,6 +1431,7 @@ impl Workspace { self.weak_handle(), None, self.app_state.background_actions, + self.pane_history_timestamp.clone(), cx, ) }); From 2ec994dfcd77980d2ec62ff8aea5697f0ed070e5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 18 May 2023 16:33:43 +0300 Subject: [PATCH 138/168] Add a unit test --- crates/file_finder/src/file_finder.rs | 250 +++++++++++++++++++++++++- 1 file changed, 246 insertions(+), 4 deletions(-) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 94b48d9e45..8ba58819a3 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -373,14 +373,14 @@ impl PickerDelegate for FileFinderDelegate { #[cfg(test)] mod tests { - use std::time::Duration; + use std::{assert_eq, collections::HashMap, time::Duration}; use super::*; use editor::Editor; - use gpui::TestAppContext; + use gpui::{TestAppContext, ViewHandle}; use menu::{Confirm, SelectNext}; use serde_json::json; - use workspace::{AppState, Workspace}; + use workspace::{AppState, Pane, Workspace}; #[ctor::ctor] fn init_logger() { @@ -867,12 +867,17 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let worktree_id = cx.read(|cx| { + let worktrees = workspace.read(cx).worktrees(cx).collect::>(); + assert_eq!(worktrees.len(), 1); + WorktreeId(worktrees[0].id()) + }); // When workspace has an active item, sort items which are closer to that item // first when they have the same name. In this case, b.txt is closer to dir2's a.txt // so that one should be sorted earlier let b_path = Some(ProjectPath { - worktree_id: WorktreeId(workspace.id()), + worktree_id, path: Arc::from(Path::new("/root/dir2/b.txt")), }); let (_, finder) = cx.add_window(|cx| { @@ -943,6 +948,243 @@ mod tests { }); } + #[gpui::test] + async fn test_query_history( + deterministic: Arc, + cx: &mut gpui::TestAppContext, + ) { + let app_state = init_test(cx); + + app_state + .fs + .as_fake() + .insert_tree( + "/src", + json!({ + "test": { + "first.rs": "// First Rust file", + "second.rs": "// Second Rust file", + "third.rs": "// Third Rust file", + } + }), + ) + .await; + + let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await; + let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + let worktree_id = cx.read(|cx| { + let worktrees = workspace.read(cx).worktrees(cx).collect::>(); + assert_eq!(worktrees.len(), 1); + WorktreeId(worktrees[0].id()) + }); + + // Open and close panels, getting their history items afterwards. + // Ensure history items get populated with opened items, and items are kept in a certain order. + // The history lags one opened buffer behind, since it's updated in the search panel only on its reopen. + // + // TODO: without closing, the opened items do not propagate their history changes for some reason + // it does work in real app though, only tests do not propagate. + + let initial_history = open_close_queried_buffer( + "fir", + 1, + "first.rs", + window_id, + &workspace, + &deterministic, + cx, + ) + .await; + assert!( + initial_history.is_empty(), + "Should have no history before opening any files" + ); + + let history_after_first = open_close_queried_buffer( + "sec", + 1, + "second.rs", + window_id, + &workspace, + &deterministic, + cx, + ) + .await; + assert_eq!( + history_after_first, + vec![ProjectPath { + worktree_id, + path: Arc::from(Path::new("test/first.rs")), + }], + "Should show 1st opened item in the history when opening the 2nd item" + ); + + let history_after_second = open_close_queried_buffer( + "thi", + 1, + "third.rs", + window_id, + &workspace, + &deterministic, + cx, + ) + .await; + assert_eq!( + history_after_second, + vec![ + ProjectPath { + worktree_id, + path: Arc::from(Path::new("test/second.rs")), + }, + ProjectPath { + worktree_id, + path: Arc::from(Path::new("test/first.rs")), + }, + ], + "Should show 1st and 2nd opened items in the history when opening the 3rd item. \ +2nd item should be the first in the history, as the last opened." + ); + + let history_after_third = open_close_queried_buffer( + "sec", + 1, + "second.rs", + window_id, + &workspace, + &deterministic, + cx, + ) + .await; + assert_eq!( + history_after_third, + vec![ + ProjectPath { + worktree_id, + path: Arc::from(Path::new("test/third.rs")), + }, + ProjectPath { + worktree_id, + path: Arc::from(Path::new("test/second.rs")), + }, + ProjectPath { + worktree_id, + path: Arc::from(Path::new("test/first.rs")), + }, + ], + "Should show 1st, 2nd and 3rd opened items in the history when opening the 2nd item again. \ +3rd item should be the first in the history, as the last opened." + ); + + let history_after_second_again = open_close_queried_buffer( + "thi", + 1, + "third.rs", + window_id, + &workspace, + &deterministic, + cx, + ) + .await; + assert_eq!( + history_after_second_again, + vec![ + ProjectPath { + worktree_id, + path: Arc::from(Path::new("test/second.rs")), + }, + ProjectPath { + worktree_id, + path: Arc::from(Path::new("test/third.rs")), + }, + ProjectPath { + worktree_id, + path: Arc::from(Path::new("test/first.rs")), + }, + ], + "Should show 1st, 2nd and 3rd opened items in the history when opening the 3rd item again. \ +2nd item, as the last opened, 3rd item should go next as it was opened right before." + ); + } + + async fn open_close_queried_buffer( + input: &str, + expected_matches: usize, + expected_editor_title: &str, + window_id: usize, + workspace: &ViewHandle, + deterministic: &gpui::executor::Deterministic, + cx: &mut gpui::TestAppContext, + ) -> Vec { + cx.dispatch_action(window_id, Toggle); + let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); + finder + .update(cx, |finder, cx| { + finder.delegate_mut().update_matches(input.to_string(), cx) + }) + .await; + let history_items = finder.read_with(cx, |finder, _| { + assert_eq!( + finder.delegate().matches.len(), + expected_matches, + "Unexpected number of matches found for query {input}" + ); + finder.delegate().history_items.clone() + }); + + let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); + cx.dispatch_action(window_id, SelectNext); + cx.dispatch_action(window_id, Confirm); + deterministic.run_until_parked(); + active_pane + .condition(cx, |pane, _| pane.active_item().is_some()) + .await; + cx.read(|cx| { + let active_item = active_pane.read(cx).active_item().unwrap(); + let active_editor_title = active_item + .as_any() + .downcast_ref::() + .unwrap() + .read(cx) + .title(cx); + assert_eq!( + expected_editor_title, active_editor_title, + "Unexpected editor title for query {input}" + ); + }); + + let mut original_items = HashMap::new(); + cx.read(|cx| { + for pane in workspace.read(cx).panes() { + let pane_id = pane.id(); + let pane = pane.read(cx); + let insertion_result = original_items.insert(pane_id, pane.items().count()); + assert!(insertion_result.is_none(), "Pane id {pane_id} collision"); + } + }); + workspace.update(cx, |workspace, cx| { + Pane::close_active_item(workspace, &workspace::CloseActiveItem, cx); + }); + deterministic.run_until_parked(); + cx.read(|cx| { + for pane in workspace.read(cx).panes() { + let pane_id = pane.id(); + let pane = pane.read(cx); + match original_items.remove(&pane_id) { + Some(original_items) => { + assert_eq!( + pane.items().count(), + original_items.saturating_sub(1), + "Pane id {pane_id} should have item closed" + ); + } + None => panic!("Pane id {pane_id} not found in original items"), + } + } + }); + + history_items + } + fn init_test(cx: &mut TestAppContext) -> Arc { cx.foreground().forbid_parking(); cx.update(|cx| { From c71b59b248d5d6aa2cf8ad89b2c23699073eabc0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 May 2023 12:24:07 -0700 Subject: [PATCH 139/168] Log how long it takes to handle each RPC message --- crates/collab/src/rpc.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 001f3462d0..ac86f8c171 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -51,7 +51,7 @@ use std::{ atomic::{AtomicBool, Ordering::SeqCst}, Arc, }, - time::Duration, + time::{Duration, Instant}, }; use tokio::sync::{watch, Semaphore}; use tower::ServiceBuilder; @@ -397,10 +397,16 @@ impl Server { "message received" ); }); + let start_time = Instant::now(); let future = (handler)(*envelope, session); async move { - if let Err(error) = future.await { - tracing::error!(%error, "error handling message"); + let result = future.await; + let duration_ms = start_time.elapsed().as_micros() as f64 / 1000.0; + match result { + Err(error) => { + tracing::error!(%error, ?duration_ms, "error handling message") + } + Ok(()) => tracing::info!(?duration_ms, "finished handling message"), } } .instrument(span) From 55b241a4f6b42704fa1b3387b7fc8a242159627d Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 17 May 2023 13:36:43 -0400 Subject: [PATCH 140/168] collab 0.12.2 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce727a9c6c..326c9f3e81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1230,7 +1230,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.12.1" +version = "0.12.2" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index ba49373641..542a6e97bf 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.12.1" +version = "0.12.2" publish = false [[bin]] From 2a11a898275ca0143514eddc331fb32cfe6a1cf9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 May 2023 12:27:08 -0700 Subject: [PATCH 141/168] collab 0.12.3 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 326c9f3e81..7bd4e30021 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1230,7 +1230,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.12.2" +version = "0.12.3" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 542a6e97bf..4544d411d0 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.12.2" +version = "0.12.3" publish = false [[bin]] From 95a7d69bce670e61391287214a45e431c4420daa Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 18 May 2023 14:29:18 -0700 Subject: [PATCH 142/168] Fixed an imprecise join in rejoin room --- crates/collab/src/db.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 453aa82b53..95886709b3 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -1514,6 +1514,7 @@ impl Database { let mut db_entries = worktree_entry::Entity::find() .filter( Condition::all() + .add(worktree_entry::Column::ProjectId.eq(project.id)) .add(worktree_entry::Column::WorktreeId.eq(worktree.id)) .add(entry_filter), ) @@ -1553,6 +1554,7 @@ impl Database { let mut db_repositories = worktree_repository::Entity::find() .filter( Condition::all() + .add(worktree_repository::Column::ProjectId.eq(project.id)) .add(worktree_repository::Column::WorktreeId.eq(worktree.id)) .add(repository_entry_filter), ) @@ -1590,6 +1592,7 @@ impl Database { worktree_repository_statuses::Entity::find() .filter( Condition::all() + .add(worktree_repository_statuses::Column::ProjectId.eq(project.id)) .add( worktree_repository_statuses::Column::WorktreeId .eq(worktree.id), From 99fcf237985752512507f85028ca6a4a7bd57f90 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 18 May 2023 14:31:08 -0700 Subject: [PATCH 143/168] fmt --- crates/collab/src/db.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 95886709b3..fd28fb9101 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -1592,7 +1592,10 @@ impl Database { worktree_repository_statuses::Entity::find() .filter( Condition::all() - .add(worktree_repository_statuses::Column::ProjectId.eq(project.id)) + .add( + worktree_repository_statuses::Column::ProjectId + .eq(project.id), + ) .add( worktree_repository_statuses::Column::WorktreeId .eq(worktree.id), From 58f704abcb8463cf6f3933d41273b5cebb137214 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 May 2023 14:46:23 -0700 Subject: [PATCH 144/168] Avoid unnecessary code action requests when applying leader updates to an editor --- crates/editor/src/editor_tests.rs | 7 ++++++ crates/editor/src/multi_buffer.rs | 40 +++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index b61c2a780f..9a21429301 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5517,10 +5517,12 @@ async fn test_following(cx: &mut gpui::TestAppContext) { }); let is_still_following = Rc::new(RefCell::new(true)); + let follower_edit_event_count = Rc::new(RefCell::new(0)); let pending_update = Rc::new(RefCell::new(None)); follower.update(cx, { let update = pending_update.clone(); let is_still_following = is_still_following.clone(); + let follower_edit_event_count = follower_edit_event_count.clone(); |_, cx| { cx.subscribe(&leader, move |_, leader, event, cx| { leader @@ -5533,6 +5535,9 @@ async fn test_following(cx: &mut gpui::TestAppContext) { if Editor::should_unfollow_on_event(event, cx) { *is_still_following.borrow_mut() = false; } + if let Event::BufferEdited = event { + *follower_edit_event_count.borrow_mut() += 1; + } }) .detach(); } @@ -5552,6 +5557,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) { assert_eq!(follower.selections.ranges(cx), vec![1..1]); }); assert_eq!(*is_still_following.borrow(), true); + assert_eq!(*follower_edit_event_count.borrow(), 0); // Update the scroll position only leader.update(cx, |leader, cx| { @@ -5568,6 +5574,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) { vec2f(1.5, 3.5) ); assert_eq!(*is_still_following.borrow(), true); + assert_eq!(*follower_edit_event_count.borrow(), 0); // Update the selections and scroll position. The follower's scroll position is updated // via autoscroll, not via the leader's exact scroll position. diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index eb69e8e7c1..f3e8fd7440 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1167,6 +1167,9 @@ impl MultiBuffer { ) { self.sync(cx); let ids = excerpt_ids.into_iter().collect::>(); + if ids.is_empty() { + return; + } let mut buffers = self.buffers.borrow_mut(); let mut snapshot = self.snapshot.borrow_mut(); @@ -4100,19 +4103,25 @@ mod tests { let leader_multibuffer = cx.add_model(|_| MultiBuffer::new(0)); let follower_multibuffer = cx.add_model(|_| MultiBuffer::new(0)); + let follower_edit_event_count = Rc::new(RefCell::new(0)); follower_multibuffer.update(cx, |_, cx| { - cx.subscribe(&leader_multibuffer, |follower, _, event, cx| { - match event.clone() { + let follower_edit_event_count = follower_edit_event_count.clone(); + cx.subscribe( + &leader_multibuffer, + move |follower, _, event, cx| match event.clone() { Event::ExcerptsAdded { buffer, predecessor, excerpts, } => follower.insert_excerpts_with_ids_after(predecessor, buffer, excerpts, cx), Event::ExcerptsRemoved { ids } => follower.remove_excerpts(ids, cx), + Event::Edited => { + *follower_edit_event_count.borrow_mut() += 1; + } _ => {} - } - }) + }, + ) .detach(); }); @@ -4151,6 +4160,7 @@ mod tests { leader_multibuffer.read(cx).snapshot(cx).text(), follower_multibuffer.read(cx).snapshot(cx).text(), ); + assert_eq!(*follower_edit_event_count.borrow(), 2); leader_multibuffer.update(cx, |leader, cx| { let excerpt_ids = leader.excerpt_ids(); @@ -4160,6 +4170,27 @@ mod tests { leader_multibuffer.read(cx).snapshot(cx).text(), follower_multibuffer.read(cx).snapshot(cx).text(), ); + assert_eq!(*follower_edit_event_count.borrow(), 3); + + // Removing an empty set of excerpts is a noop. + leader_multibuffer.update(cx, |leader, cx| { + leader.remove_excerpts([], cx); + }); + assert_eq!( + leader_multibuffer.read(cx).snapshot(cx).text(), + follower_multibuffer.read(cx).snapshot(cx).text(), + ); + assert_eq!(*follower_edit_event_count.borrow(), 3); + + // Adding an empty set of excerpts is a noop. + leader_multibuffer.update(cx, |leader, cx| { + leader.push_excerpts::(buffer_2.clone(), [], cx); + }); + assert_eq!( + leader_multibuffer.read(cx).snapshot(cx).text(), + follower_multibuffer.read(cx).snapshot(cx).text(), + ); + assert_eq!(*follower_edit_event_count.borrow(), 3); leader_multibuffer.update(cx, |leader, cx| { leader.clear(cx); @@ -4168,6 +4199,7 @@ mod tests { leader_multibuffer.read(cx).snapshot(cx).text(), follower_multibuffer.read(cx).snapshot(cx).text(), ); + assert_eq!(*follower_edit_event_count.borrow(), 4); } #[gpui::test] From 34b0d6200f66d3cf3eddd02d73f81e429f2c6514 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 May 2023 14:58:57 -0700 Subject: [PATCH 145/168] collab 0.12.4 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 51bd47b66c..d339ac3256 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1230,7 +1230,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.12.3" +version = "0.12.4" dependencies = [ "anyhow", "async-tungstenite", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 4544d411d0..f2202618f4 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.12.3" +version = "0.12.4" publish = false [[bin]] From 4bda5c4d696b8d6abeb2e8ccd85e3bf38e0b74d5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 May 2023 17:08:51 -0700 Subject: [PATCH 146/168] Optimize LSP watched file reporting in 2 simple ways * Convert globs to relative paths in advance. This avoids needing to convert every changed path to an absolute path before performing glob matching. * Avoid duplicate reporting for language servers with multiple worktrees. --- crates/project/src/lsp_glob_set.rs | 8 +++++ crates/project/src/project.rs | 54 +++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/crates/project/src/lsp_glob_set.rs b/crates/project/src/lsp_glob_set.rs index daac344a0a..d4d661b6a1 100644 --- a/crates/project/src/lsp_glob_set.rs +++ b/crates/project/src/lsp_glob_set.rs @@ -73,6 +73,14 @@ impl LspGlobSet { } } +impl std::fmt::Debug for LspGlobSet { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_set() + .entries(self.patterns.iter().map(|p| p.as_str())) + .finish() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 13809622f9..23b4984e55 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -226,7 +226,7 @@ pub enum LanguageServerState { language: Arc, adapter: Arc, server: Arc, - watched_paths: LspGlobSet, + watched_paths: HashMap, simulate_disk_based_diagnostics_completion: Option>, }, } @@ -2869,7 +2869,25 @@ impl Project { { watched_paths.clear(); for watcher in params.watchers { - watched_paths.add_pattern(&watcher.glob_pattern).log_err(); + for worktree in &self.worktrees { + if let Some(worktree) = worktree.upgrade(cx) { + let worktree = worktree.read(cx); + if let Some(abs_path) = worktree.abs_path().to_str() { + if let Some(suffix) = watcher + .glob_pattern + .strip_prefix(abs_path) + .and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR)) + { + watched_paths + .entry(worktree.id()) + .or_default() + .add_pattern(suffix) + .log_err(); + break; + } + } + } + } } cx.notify(); } @@ -4708,24 +4726,34 @@ impl Project { cx: &mut ModelContext, ) { let worktree_id = worktree_handle.read(cx).id(); + let mut language_server_ids = self + .language_server_ids + .iter() + .filter_map(|((server_worktree_id, _), server_id)| { + (*server_worktree_id == worktree_id).then_some(*server_id) + }) + .collect::>(); + language_server_ids.sort(); + language_server_ids.dedup(); + let abs_path = worktree_handle.read(cx).abs_path(); - for ((server_worktree_id, _), server_id) in &self.language_server_ids { - if *server_worktree_id == worktree_id { - if let Some(server) = self.language_servers.get(server_id) { - if let LanguageServerState::Running { - server, - watched_paths, - .. - } = server - { + for server_id in &language_server_ids { + if let Some(server) = self.language_servers.get(server_id) { + if let LanguageServerState::Running { + server, + watched_paths, + .. + } = server + { + if let Some(watched_paths) = watched_paths.get(&worktree_id) { let params = lsp::DidChangeWatchedFilesParams { changes: changes .iter() .filter_map(|((path, _), change)| { - let path = abs_path.join(path); if watched_paths.matches(&path) { Some(lsp::FileEvent { - uri: lsp::Url::from_file_path(path).unwrap(), + uri: lsp::Url::from_file_path(abs_path.join(path)) + .unwrap(), typ: match change { PathChange::Added => lsp::FileChangeType::CREATED, PathChange::Removed => lsp::FileChangeType::DELETED, From 3984cc6d39996eb3fa1b8c9a6f35f4930e2356cf Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 19 May 2023 12:37:36 +0300 Subject: [PATCH 147/168] Properly handle WorktreeId --- crates/file_finder/src/file_finder.rs | 6 +++--- crates/project/src/worktree.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 8ba58819a3..5270b694b2 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -265,7 +265,7 @@ impl PickerDelegate for FileFinderDelegate { .map(|(i, history_item)| PathMatch { score: i as f64, positions: Vec::new(), - worktree_id: history_item.worktree_id.0, + worktree_id: history_item.worktree_id.to_usize(), path: Arc::clone(&history_item.path), path_prefix: "".into(), distance_to_relative_ancestor: usize::MAX, @@ -870,7 +870,7 @@ mod tests { let worktree_id = cx.read(|cx| { let worktrees = workspace.read(cx).worktrees(cx).collect::>(); assert_eq!(worktrees.len(), 1); - WorktreeId(worktrees[0].id()) + WorktreeId::from_usize(worktrees[0].id()) }); // When workspace has an active item, sort items which are closer to that item @@ -975,7 +975,7 @@ mod tests { let worktree_id = cx.read(|cx| { let worktrees = workspace.read(cx).worktrees(cx).collect::>(); assert_eq!(worktrees.len(), 1); - WorktreeId(worktrees[0].id()) + WorktreeId::from_usize(worktrees[0].id()) }); // Open and close panels, getting their history items afterwards. diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 550a27ea9f..403d893425 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -58,7 +58,7 @@ use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet}; use util::{paths::HOME, ResultExt, TakeUntilExt, TryFutureExt}; #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] -pub struct WorktreeId(pub usize); +pub struct WorktreeId(usize); pub enum Worktree { Local(LocalWorktree), From 583b15badc0d7a5561061d32a95e8a4472c3c4cf Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 19 May 2023 18:04:12 +0300 Subject: [PATCH 148/168] When the file is deleted via project panel, close it in editors --- crates/gpui/src/app/test_app_context.rs | 2 +- crates/project/src/project.rs | 7 + crates/project_panel/src/project_panel.rs | 180 ++++++++++++++++++++++ crates/workspace/src/pane.rs | 28 ++++ crates/workspace/src/workspace.rs | 6 + 5 files changed, 222 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/app/test_app_context.rs b/crates/gpui/src/app/test_app_context.rs index 4af436a7b8..e956c4ca0d 100644 --- a/crates/gpui/src/app/test_app_context.rs +++ b/crates/gpui/src/app/test_app_context.rs @@ -270,7 +270,7 @@ impl TestAppContext { .borrow_mut() .pop_front() .expect("prompt was not called"); - let _ = done_tx.try_send(answer); + done_tx.try_send(answer).ok(); } pub fn has_pending_prompt(&self, window_id: usize) -> bool { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 13809622f9..106dd4d817 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -213,6 +213,7 @@ pub enum Event { RemoteIdChanged(Option), DisconnectedFromHost, Closed, + DeletedEntry(ProjectEntryId), CollaboratorUpdated { old_peer_id: proto::PeerId, new_peer_id: proto::PeerId, @@ -977,6 +978,9 @@ impl Project { cx: &mut ModelContext, ) -> Option>> { let worktree = self.worktree_for_entry(entry_id, cx)?; + + cx.emit(Event::DeletedEntry(entry_id)); + if self.is_local() { worktree.update(cx, |worktree, cx| { worktree.as_local_mut().unwrap().delete_entry(entry_id, cx) @@ -5146,6 +5150,9 @@ impl Project { mut cx: AsyncAppContext, ) -> Result { let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id); + + this.update(&mut cx, |_, cx| cx.emit(Event::DeletedEntry(entry_id))); + let worktree = this.read_with(&cx, |this, cx| { this.worktree_for_entry(entry_id, cx) .ok_or_else(|| anyhow!("worktree not found")) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 683ce8ad06..8606649398 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1378,6 +1378,7 @@ mod tests { use serde_json::json; use settings::SettingsStore; use std::{collections::HashSet, path::Path}; + use workspace::{pane, AppState}; #[gpui::test] async fn test_visible_list(cx: &mut gpui::TestAppContext) { @@ -1853,6 +1854,95 @@ mod tests { ); } + #[gpui::test] + async fn test_remove_opened_file(cx: &mut gpui::TestAppContext) { + init_test_with_editor(cx); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/src", + json!({ + "test": { + "first.rs": "// First Rust file", + "second.rs": "// Second Rust file", + "third.rs": "// Third Rust file", + } + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await; + let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)); + + toggle_expand_dir(&panel, "src/test", cx); + select_path(&panel, "src/test/first.rs", cx); + panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx)); + cx.foreground().run_until_parked(); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &[ + "v src", + " v test", + " first.rs <== selected", + " second.rs", + " third.rs" + ] + ); + ensure_single_file_is_opened(window_id, &workspace, "test/first.rs", cx); + + submit_deletion(window_id, &panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &[ + "v src", + " v test", + " second.rs", + " third.rs" + ], + "Project panel should have no deleted file, no other file is selected in it" + ); + ensure_no_open_items_and_panes(window_id, &workspace, cx); + + select_path(&panel, "src/test/second.rs", cx); + panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx)); + cx.foreground().run_until_parked(); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &[ + "v src", + " v test", + " second.rs <== selected", + " third.rs" + ] + ); + ensure_single_file_is_opened(window_id, &workspace, "test/second.rs", cx); + + cx.update_window(window_id, |cx| { + let active_items = workspace + .read(cx) + .panes() + .iter() + .filter_map(|pane| pane.read(cx).active_item()) + .collect::>(); + assert_eq!(active_items.len(), 1); + let open_editor = active_items + .into_iter() + .next() + .unwrap() + .downcast::() + .expect("Open item should be an editor"); + open_editor.update(cx, |editor, cx| editor.set_text("Another text!", cx)); + }); + submit_deletion(window_id, &panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..10, cx), + &["v src", " v test", " third.rs"], + "Project panel should have no deleted file, with one last file remaining" + ); + ensure_no_open_items_and_panes(window_id, &workspace, cx); + } + fn toggle_expand_dir( panel: &ViewHandle, path: impl AsRef, @@ -1956,4 +2046,94 @@ mod tests { workspace::init_settings(cx); }); } + + fn init_test_with_editor(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + cx.update(|cx| { + let app_state = AppState::test(cx); + theme::init((), cx); + language::init(cx); + editor::init(cx); + pane::init(cx); + workspace::init(app_state.clone(), cx); + }); + } + + fn ensure_single_file_is_opened( + window_id: usize, + workspace: &ViewHandle, + expected_path: &str, + cx: &mut TestAppContext, + ) { + cx.read_window(window_id, |cx| { + let workspace = workspace.read(cx); + let worktrees = workspace.worktrees(cx).collect::>(); + assert_eq!(worktrees.len(), 1); + let worktree_id = WorktreeId::from_usize(worktrees[0].id()); + + let open_project_paths = workspace + .panes() + .iter() + .filter_map(|pane| pane.read(cx).active_item()?.project_path(cx)) + .collect::>(); + assert_eq!( + open_project_paths, + vec![ProjectPath { + worktree_id, + path: Arc::from(Path::new(expected_path)) + }], + "Should have opened file, selected in project panel" + ); + }); + } + + fn submit_deletion( + window_id: usize, + panel: &ViewHandle, + cx: &mut TestAppContext, + ) { + assert!( + !cx.has_pending_prompt(window_id), + "Should have no prompts before the deletion" + ); + panel.update(cx, |panel, cx| { + panel + .delete(&Delete, cx) + .expect("Deletion start") + .detach_and_log_err(cx); + }); + assert!( + cx.has_pending_prompt(window_id), + "Should have a prompt after the deletion" + ); + cx.simulate_prompt_answer(window_id, 0); + assert!( + !cx.has_pending_prompt(window_id), + "Should have no prompts after prompt was replied to" + ); + cx.foreground().run_until_parked(); + } + + fn ensure_no_open_items_and_panes( + window_id: usize, + workspace: &ViewHandle, + cx: &mut TestAppContext, + ) { + assert!( + !cx.has_pending_prompt(window_id), + "Should have no prompts after deletion operation closes the file" + ); + cx.read_window(window_id, |cx| { + let open_project_paths = workspace + .read(cx) + .panes() + .iter() + .filter_map(|pane| pane.read(cx).active_item()?.project_path(cx)) + .collect::>(); + assert!( + open_project_paths.is_empty(), + "Deleted file's buffer should be closed, but got open files: {open_project_paths:?}" + ); + }); + } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 368afcd16c..c54f8d393b 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1305,6 +1305,25 @@ impl Pane { &self.toolbar } + pub fn delete_item( + &mut self, + entry_id: ProjectEntryId, + cx: &mut ViewContext, + ) -> Option<()> { + let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| { + if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { + Some((i, item.id())) + } else { + None + } + })?; + + self.remove_item(item_index_to_delete, false, cx); + self.nav_history.borrow_mut().remove_item(item_id); + + Some(()) + } + fn update_toolbar(&mut self, cx: &mut ViewContext) { let active_item = self .items @@ -2007,6 +2026,15 @@ impl NavHistory { }); } } + + fn remove_item(&mut self, item_id: usize) { + self.paths_by_item.remove(&item_id); + self.backward_stack + .retain(|entry| entry.item.id() != item_id); + self.forward_stack + .retain(|entry| entry.item.id() != item_id); + self.closed_stack.retain(|entry| entry.item.id() != item_id); + } } impl PaneNavHistory { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 28ad294798..11703aba6e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -537,6 +537,12 @@ impl Workspace { cx.remove_window(); } + project::Event::DeletedEntry(entry_id) => { + for pane in this.panes.iter() { + pane.update(cx, |pane, cx| pane.delete_item(*entry_id, cx)); + } + } + _ => {} } cx.notify() From 459cc9c9597ef31911330dc8130526590ebad7c4 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 May 2023 09:13:31 -0700 Subject: [PATCH 149/168] Optimize matching of multiple file-watch globs using the globset crate --- Cargo.lock | 4 +- Cargo.toml | 1 + crates/project/Cargo.toml | 2 +- crates/project/src/lsp_glob_set.rs | 129 ---------------------------- crates/project/src/project.rs | 34 +++++--- crates/project/src/project_tests.rs | 55 ++++++------ crates/project/src/search.rs | 70 ++++++--------- crates/search/Cargo.toml | 2 +- crates/search/src/project_search.rs | 74 ++++++++-------- 9 files changed, 119 insertions(+), 252 deletions(-) delete mode 100644 crates/project/src/lsp_glob_set.rs diff --git a/Cargo.lock b/Cargo.lock index d339ac3256..7129cc8b81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4851,7 +4851,7 @@ dependencies = [ "fuzzy", "git", "git2", - "glob", + "globset", "gpui", "ignore", "itertools", @@ -5949,7 +5949,7 @@ dependencies = [ "collections", "editor", "futures 0.3.28", - "glob", + "globset", "gpui", "language", "log", diff --git a/Cargo.toml b/Cargo.toml index f14e1c7355..c31624b5fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,6 +77,7 @@ async-trait = { version = "0.1" } ctor = { version = "0.1" } env_logger = { version = "0.9" } futures = { version = "0.3" } +globset = { version = "0.4" } glob = { version = "0.3.1" } lazy_static = { version = "1.4.0" } log = { version = "0.4.16", features = ["kv_unstable_serde"] } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 190f1d96a8..d6578c87ba 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -42,7 +42,7 @@ anyhow.workspace = true async-trait.workspace = true backtrace = "0.3" futures.workspace = true -glob.workspace = true +globset.workspace = true ignore = "0.4" lazy_static.workspace = true log.workspace = true diff --git a/crates/project/src/lsp_glob_set.rs b/crates/project/src/lsp_glob_set.rs deleted file mode 100644 index d4d661b6a1..0000000000 --- a/crates/project/src/lsp_glob_set.rs +++ /dev/null @@ -1,129 +0,0 @@ -use anyhow::{anyhow, Result}; -use std::path::Path; - -#[derive(Default)] -pub struct LspGlobSet { - patterns: Vec, -} - -impl LspGlobSet { - pub fn clear(&mut self) { - self.patterns.clear(); - } - - /// Add a pattern to the glob set. - /// - /// LSP's glob syntax supports bash-style brace expansion. For example, - /// the pattern '*.{js,ts}' would match all JavaScript or TypeScript files. - /// This is not a part of the standard libc glob syntax, and isn't supported - /// by the `glob` crate. So we pre-process the glob patterns, producing a - /// separate glob `Pattern` object for each part of a brace expansion. - pub fn add_pattern(&mut self, pattern: &str) -> Result<()> { - // Find all of the ranges of `pattern` that contain matched curly braces. - let mut expansion_ranges = Vec::new(); - let mut expansion_start_ix = None; - for (ix, c) in pattern.match_indices(|c| ['{', '}'].contains(&c)) { - match c { - "{" => { - if expansion_start_ix.is_some() { - return Err(anyhow!("nested braces in glob patterns aren't supported")); - } - expansion_start_ix = Some(ix); - } - "}" => { - if let Some(start_ix) = expansion_start_ix { - expansion_ranges.push(start_ix..ix + 1); - } - expansion_start_ix = None; - } - _ => {} - } - } - - // Starting with a single pattern, process each brace expansion by cloning - // the pattern once per element of the expansion. - let mut unexpanded_patterns = vec![]; - let mut expanded_patterns = vec![pattern.to_string()]; - - for outer_range in expansion_ranges.into_iter().rev() { - let inner_range = (outer_range.start + 1)..(outer_range.end - 1); - std::mem::swap(&mut unexpanded_patterns, &mut expanded_patterns); - for unexpanded_pattern in unexpanded_patterns.drain(..) { - for part in unexpanded_pattern[inner_range.clone()].split(',') { - let mut expanded_pattern = unexpanded_pattern.clone(); - expanded_pattern.replace_range(outer_range.clone(), part); - expanded_patterns.push(expanded_pattern); - } - } - } - - // Parse the final glob patterns and add them to the set. - for pattern in expanded_patterns { - let pattern = glob::Pattern::new(&pattern)?; - self.patterns.push(pattern); - } - - Ok(()) - } - - pub fn matches(&self, path: &Path) -> bool { - self.patterns - .iter() - .any(|pattern| pattern.matches_path(path)) - } -} - -impl std::fmt::Debug for LspGlobSet { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_set() - .entries(self.patterns.iter().map(|p| p.as_str())) - .finish() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_glob_set() { - let mut watch = LspGlobSet::default(); - watch.add_pattern("/a/**/*.rs").unwrap(); - watch.add_pattern("/a/**/Cargo.toml").unwrap(); - - assert!(watch.matches("/a/b.rs".as_ref())); - assert!(watch.matches("/a/b/c.rs".as_ref())); - - assert!(!watch.matches("/b/c.rs".as_ref())); - assert!(!watch.matches("/a/b.ts".as_ref())); - } - - #[test] - fn test_brace_expansion() { - let mut watch = LspGlobSet::default(); - watch.add_pattern("/a/*.{ts,js,tsx}").unwrap(); - - assert!(watch.matches("/a/one.js".as_ref())); - assert!(watch.matches("/a/two.ts".as_ref())); - assert!(watch.matches("/a/three.tsx".as_ref())); - - assert!(!watch.matches("/a/one.j".as_ref())); - assert!(!watch.matches("/a/two.s".as_ref())); - assert!(!watch.matches("/a/three.t".as_ref())); - assert!(!watch.matches("/a/four.t".as_ref())); - assert!(!watch.matches("/a/five.xt".as_ref())); - } - - #[test] - fn test_multiple_brace_expansion() { - let mut watch = LspGlobSet::default(); - watch.add_pattern("/a/{one,two,three}.{b*c,d*e}").unwrap(); - - assert!(watch.matches("/a/one.bic".as_ref())); - assert!(watch.matches("/a/two.dole".as_ref())); - assert!(watch.matches("/a/three.deeee".as_ref())); - - assert!(!watch.matches("/a/four.bic".as_ref())); - assert!(!watch.matches("/a/one.be".as_ref())); - } -} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 23b4984e55..4b759c4240 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1,6 +1,5 @@ mod ignore; mod lsp_command; -mod lsp_glob_set; mod project_settings; pub mod search; pub mod terminals; @@ -19,6 +18,7 @@ use futures::{ future::{try_join_all, Shared}, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt, }; +use globset::{Glob, GlobSet, GlobSetBuilder}; use gpui::{ AnyModelHandle, AppContext, AsyncAppContext, BorrowAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle, @@ -41,7 +41,6 @@ use lsp::{ DocumentHighlightKind, LanguageServer, LanguageServerId, }; use lsp_command::*; -use lsp_glob_set::LspGlobSet; use postage::watch; use project_settings::ProjectSettings; use rand::prelude::*; @@ -226,7 +225,7 @@ pub enum LanguageServerState { language: Arc, adapter: Arc, server: Arc, - watched_paths: HashMap, + watched_paths: HashMap, simulate_disk_based_diagnostics_completion: Option>, }, } @@ -2867,8 +2866,10 @@ impl Project { if let Some(LanguageServerState::Running { watched_paths, .. }) = self.language_servers.get_mut(&language_server_id) { - watched_paths.clear(); + eprintln!("change watch"); + let mut builders = HashMap::default(); for watcher in params.watchers { + eprintln!(" {}", watcher.glob_pattern); for worktree in &self.worktrees { if let Some(worktree) = worktree.upgrade(cx) { let worktree = worktree.read(cx); @@ -2878,17 +2879,26 @@ impl Project { .strip_prefix(abs_path) .and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR)) { - watched_paths - .entry(worktree.id()) - .or_default() - .add_pattern(suffix) - .log_err(); + if let Some(glob) = Glob::new(suffix).log_err() { + builders + .entry(worktree.id()) + .or_insert_with(|| GlobSetBuilder::new()) + .add(glob); + } break; } } } } } + + watched_paths.clear(); + for (worktree_id, builder) in builders { + if let Ok(globset) = builder.build() { + watched_paths.insert(worktree_id, globset); + } + } + cx.notify(); } } @@ -4725,6 +4735,10 @@ impl Project { changes: &HashMap<(Arc, ProjectEntryId), PathChange>, cx: &mut ModelContext, ) { + if changes.is_empty() { + return; + } + let worktree_id = worktree_handle.read(cx).id(); let mut language_server_ids = self .language_server_ids @@ -4750,7 +4764,7 @@ impl Project { changes: changes .iter() .filter_map(|((path, _), change)| { - if watched_paths.matches(&path) { + if watched_paths.is_match(&path) { Some(lsp::FileEvent { uri: lsp::Url::from_file_path(abs_path.join(path)) .unwrap(), diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index e7b1a84924..69bcea8ce0 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1,6 +1,7 @@ use crate::{worktree::WorktreeHandle, Event, *}; use fs::{FakeFs, LineEnding, RealFs}; use futures::{future, StreamExt}; +use globset::Glob; use gpui::{executor::Deterministic, test::subscribe, AppContext}; use language::{ language_settings::{AllLanguageSettings, LanguageSettingsContent}, @@ -505,7 +506,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon register_options: serde_json::to_value( lsp::DidChangeWatchedFilesRegistrationOptions { watchers: vec![lsp::FileSystemWatcher { - glob_pattern: "*.{rs,c}".to_string(), + glob_pattern: "/the-root/*.{rs,c}".to_string(), kind: None, }], }, @@ -3393,7 +3394,7 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) { search_query, false, true, - vec![glob::Pattern::new("*.odd").unwrap()], + vec![Glob::new("*.odd").unwrap().compile_matcher()], Vec::new() ), cx @@ -3411,7 +3412,7 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) { search_query, false, true, - vec![glob::Pattern::new("*.rs").unwrap()], + vec![Glob::new("*.rs").unwrap().compile_matcher()], Vec::new() ), cx @@ -3433,8 +3434,8 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) { false, true, vec![ - glob::Pattern::new("*.ts").unwrap(), - glob::Pattern::new("*.odd").unwrap(), + Glob::new("*.ts").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher(), ], Vec::new() ), @@ -3457,9 +3458,9 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) { false, true, vec![ - glob::Pattern::new("*.rs").unwrap(), - glob::Pattern::new("*.ts").unwrap(), - glob::Pattern::new("*.odd").unwrap(), + Glob::new("*.rs").unwrap().compile_matcher(), + Glob::new("*.ts").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher(), ], Vec::new() ), @@ -3504,7 +3505,7 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) { false, true, Vec::new(), - vec![glob::Pattern::new("*.odd").unwrap()], + vec![Glob::new("*.odd").unwrap().compile_matcher()], ), cx ) @@ -3527,7 +3528,7 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) { false, true, Vec::new(), - vec![glob::Pattern::new("*.rs").unwrap()], + vec![Glob::new("*.rs").unwrap().compile_matcher()], ), cx ) @@ -3549,8 +3550,8 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) { true, Vec::new(), vec![ - glob::Pattern::new("*.ts").unwrap(), - glob::Pattern::new("*.odd").unwrap(), + Glob::new("*.ts").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher(), ], ), cx @@ -3573,9 +3574,9 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) { true, Vec::new(), vec![ - glob::Pattern::new("*.rs").unwrap(), - glob::Pattern::new("*.ts").unwrap(), - glob::Pattern::new("*.odd").unwrap(), + Glob::new("*.rs").unwrap().compile_matcher(), + Glob::new("*.ts").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher(), ], ), cx @@ -3612,8 +3613,8 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex search_query, false, true, - vec![glob::Pattern::new("*.odd").unwrap()], - vec![glob::Pattern::new("*.odd").unwrap()], + vec![Glob::new("*.odd").unwrap().compile_matcher()], + vec![Glob::new("*.odd").unwrap().compile_matcher()], ), cx ) @@ -3630,8 +3631,8 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex search_query, false, true, - vec![glob::Pattern::new("*.ts").unwrap()], - vec![glob::Pattern::new("*.ts").unwrap()], + vec![Glob::new("*.ts").unwrap().compile_matcher()], + vec![Glob::new("*.ts").unwrap().compile_matcher()], ), cx ) @@ -3649,12 +3650,12 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex false, true, vec![ - glob::Pattern::new("*.ts").unwrap(), - glob::Pattern::new("*.odd").unwrap() + Glob::new("*.ts").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher() ], vec![ - glob::Pattern::new("*.ts").unwrap(), - glob::Pattern::new("*.odd").unwrap() + Glob::new("*.ts").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher() ], ), cx @@ -3673,12 +3674,12 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex false, true, vec![ - glob::Pattern::new("*.ts").unwrap(), - glob::Pattern::new("*.odd").unwrap() + Glob::new("*.ts").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher() ], vec![ - glob::Pattern::new("*.rs").unwrap(), - glob::Pattern::new("*.odd").unwrap() + Glob::new("*.rs").unwrap().compile_matcher(), + Glob::new("*.odd").unwrap().compile_matcher() ], ), cx diff --git a/crates/project/src/search.rs b/crates/project/src/search.rs index ed139c97d3..4b4126fef2 100644 --- a/crates/project/src/search.rs +++ b/crates/project/src/search.rs @@ -1,6 +1,7 @@ use aho_corasick::{AhoCorasick, AhoCorasickBuilder}; use anyhow::Result; use client::proto; +use globset::{Glob, GlobMatcher}; use itertools::Itertools; use language::{char_kind, Rope}; use regex::{Regex, RegexBuilder}; @@ -19,8 +20,8 @@ pub enum SearchQuery { query: Arc, whole_word: bool, case_sensitive: bool, - files_to_include: Vec, - files_to_exclude: Vec, + files_to_include: Vec, + files_to_exclude: Vec, }, Regex { regex: Regex, @@ -28,8 +29,8 @@ pub enum SearchQuery { multiline: bool, whole_word: bool, case_sensitive: bool, - files_to_include: Vec, - files_to_exclude: Vec, + files_to_include: Vec, + files_to_exclude: Vec, }, } @@ -38,8 +39,8 @@ impl SearchQuery { query: impl ToString, whole_word: bool, case_sensitive: bool, - files_to_include: Vec, - files_to_exclude: Vec, + files_to_include: Vec, + files_to_exclude: Vec, ) -> Self { let query = query.to_string(); let search = AhoCorasickBuilder::new() @@ -60,8 +61,8 @@ impl SearchQuery { query: impl ToString, whole_word: bool, case_sensitive: bool, - files_to_include: Vec, - files_to_exclude: Vec, + files_to_include: Vec, + files_to_exclude: Vec, ) -> Result { let mut query = query.to_string(); let initial_query = Arc::from(query.as_str()); @@ -95,40 +96,16 @@ impl SearchQuery { message.query, message.whole_word, message.case_sensitive, - message - .files_to_include - .split(',') - .map(str::trim) - .filter(|glob_str| !glob_str.is_empty()) - .map(|glob_str| glob::Pattern::new(glob_str)) - .collect::>()?, - message - .files_to_exclude - .split(',') - .map(str::trim) - .filter(|glob_str| !glob_str.is_empty()) - .map(|glob_str| glob::Pattern::new(glob_str)) - .collect::>()?, + deserialize_globs(&message.files_to_include)?, + deserialize_globs(&message.files_to_exclude)?, ) } else { Ok(Self::text( message.query, message.whole_word, message.case_sensitive, - message - .files_to_include - .split(',') - .map(str::trim) - .filter(|glob_str| !glob_str.is_empty()) - .map(|glob_str| glob::Pattern::new(glob_str)) - .collect::>()?, - message - .files_to_exclude - .split(',') - .map(str::trim) - .filter(|glob_str| !glob_str.is_empty()) - .map(|glob_str| glob::Pattern::new(glob_str)) - .collect::>()?, + deserialize_globs(&message.files_to_include)?, + deserialize_globs(&message.files_to_exclude)?, )) } } @@ -143,12 +120,12 @@ impl SearchQuery { files_to_include: self .files_to_include() .iter() - .map(ToString::to_string) + .map(|g| g.glob().to_string()) .join(","), files_to_exclude: self .files_to_exclude() .iter() - .map(ToString::to_string) + .map(|g| g.glob().to_string()) .join(","), } } @@ -289,7 +266,7 @@ impl SearchQuery { matches!(self, Self::Regex { .. }) } - pub fn files_to_include(&self) -> &[glob::Pattern] { + pub fn files_to_include(&self) -> &[GlobMatcher] { match self { Self::Text { files_to_include, .. @@ -300,7 +277,7 @@ impl SearchQuery { } } - pub fn files_to_exclude(&self) -> &[glob::Pattern] { + pub fn files_to_exclude(&self) -> &[GlobMatcher] { match self { Self::Text { files_to_exclude, .. @@ -317,14 +294,23 @@ impl SearchQuery { !self .files_to_exclude() .iter() - .any(|exclude_glob| exclude_glob.matches_path(file_path)) + .any(|exclude_glob| exclude_glob.is_match(file_path)) && (self.files_to_include().is_empty() || self .files_to_include() .iter() - .any(|include_glob| include_glob.matches_path(file_path))) + .any(|include_glob| include_glob.is_match(file_path))) } None => self.files_to_include().is_empty(), } } } + +fn deserialize_globs(glob_set: &str) -> Result> { + glob_set + .split(',') + .map(str::trim) + .filter(|glob_str| !glob_str.is_empty()) + .map(|glob_str| Ok(Glob::new(glob_str)?.compile_matcher())) + .collect() +} diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 14e658e8f8..7ef388f7c0 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -27,7 +27,7 @@ serde.workspace = true serde_derive.workspace = true smallvec.workspace = true smol.workspace = true -glob.workspace = true +globset.workspace = true [dev-dependencies] client = { path = "../client", features = ["test-support"] } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 17f86c153c..d96d77eb00 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -2,12 +2,14 @@ use crate::{ SearchOption, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex, ToggleWholeWord, }; +use anyhow::Result; use collections::HashMap; use editor::{ items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN, }; use futures::StreamExt; +use globset::{Glob, GlobMatcher}; use gpui::{ actions, elements::*, @@ -571,46 +573,30 @@ impl ProjectSearchView { fn build_search_query(&mut self, cx: &mut ViewContext) -> Option { let text = self.query_editor.read(cx).text(cx); - let included_files = match self - .included_files_editor - .read(cx) - .text(cx) - .split(',') - .map(str::trim) - .filter(|glob_str| !glob_str.is_empty()) - .map(|glob_str| glob::Pattern::new(glob_str)) - .collect::>() - { - Ok(included_files) => { - self.panels_with_errors.remove(&InputPanel::Include); - included_files - } - Err(_e) => { - self.panels_with_errors.insert(InputPanel::Include); - cx.notify(); - return None; - } - }; - let excluded_files = match self - .excluded_files_editor - .read(cx) - .text(cx) - .split(',') - .map(str::trim) - .filter(|glob_str| !glob_str.is_empty()) - .map(|glob_str| glob::Pattern::new(glob_str)) - .collect::>() - { - Ok(excluded_files) => { - self.panels_with_errors.remove(&InputPanel::Exclude); - excluded_files - } - Err(_e) => { - self.panels_with_errors.insert(InputPanel::Exclude); - cx.notify(); - return None; - } - }; + let included_files = + match Self::load_glob_set(&self.included_files_editor.read(cx).text(cx)) { + Ok(included_files) => { + self.panels_with_errors.remove(&InputPanel::Include); + included_files + } + Err(_e) => { + self.panels_with_errors.insert(InputPanel::Include); + cx.notify(); + return None; + } + }; + let excluded_files = + match Self::load_glob_set(&self.excluded_files_editor.read(cx).text(cx)) { + Ok(excluded_files) => { + self.panels_with_errors.remove(&InputPanel::Exclude); + excluded_files + } + Err(_e) => { + self.panels_with_errors.insert(InputPanel::Exclude); + cx.notify(); + return None; + } + }; if self.regex { match SearchQuery::regex( text, @@ -640,6 +626,14 @@ impl ProjectSearchView { } } + fn load_glob_set(text: &str) -> Result> { + text.split(',') + .map(str::trim) + .filter(|glob_str| !glob_str.is_empty()) + .map(|glob_str| anyhow::Ok(Glob::new(glob_str)?.compile_matcher())) + .collect() + } + fn select_match(&mut self, direction: Direction, cx: &mut ViewContext) { if let Some(index) = self.active_match_index { let match_ranges = self.model.read(cx).match_ranges.clone(); From 847d1e73a33e075e3129e72d1b47eb2e83566568 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 May 2023 09:36:46 -0700 Subject: [PATCH 150/168] Replace remaining usages of glob crate with globset --- Cargo.lock | 4 +--- Cargo.toml | 1 - crates/copilot_button/src/copilot_button.rs | 5 ++--- crates/editor/Cargo.toml | 2 -- crates/language/Cargo.toml | 2 +- crates/language/src/language_settings.rs | 7 ++++--- crates/settings/Cargo.toml | 1 - 7 files changed, 8 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7129cc8b81..3ae96f4751 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2038,7 +2038,6 @@ dependencies = [ "futures 0.3.28", "fuzzy", "git", - "glob", "gpui", "indoc", "itertools", @@ -3442,7 +3441,7 @@ dependencies = [ "futures 0.3.28", "fuzzy", "git", - "glob", + "globset", "gpui", "indoc", "lazy_static", @@ -6111,7 +6110,6 @@ dependencies = [ "collections", "fs", "futures 0.3.28", - "glob", "gpui", "json_comments", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index c31624b5fe..4854be0c7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,6 @@ ctor = { version = "0.1" } env_logger = { version = "0.9" } futures = { version = "0.3" } globset = { version = "0.4" } -glob = { version = "0.3.1" } lazy_static = { version = "1.4.0" } log = { version = "0.4.16", features = ["kv_unstable_serde"] } ordered-float = { version = "2.1.1" } diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 73cd8f6a1d..fdd0da1970 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -335,10 +335,9 @@ async fn configure_disabled_globs( .get::(None) .copilot .disabled_globs - .clone() .iter() - .map(|glob| glob.as_str().to_string()) - .collect::>() + .map(|glob| glob.glob().to_string()) + .collect() }); if let Some(path_to_disable) = &path_to_disable { diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index fc7bf4b8ab..325883b7c0 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -49,7 +49,6 @@ workspace = { path = "../workspace" } aho-corasick = "0.7" anyhow.workspace = true futures.workspace = true -glob.workspace = true indoc = "1.0.4" itertools = "0.10" lazy_static.workspace = true @@ -82,7 +81,6 @@ workspace = { path = "../workspace", features = ["test-support"] } ctor.workspace = true env_logger.workspace = true -glob.workspace = true rand.workspace = true unindent.workspace = true tree-sitter = "0.20" diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 5a7644d98e..7e81620e5c 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -41,7 +41,7 @@ anyhow.workspace = true async-broadcast = "0.4" async-trait.workspace = true futures.workspace = true -glob.workspace = true +globset.workspace = true lazy_static.workspace = true log.workspace = true parking_lot.workspace = true diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index b47982819a..d877304f1d 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -1,5 +1,6 @@ use anyhow::Result; use collections::HashMap; +use globset::GlobMatcher; use gpui::AppContext; use schemars::{ schema::{InstanceType, ObjectValidation, Schema, SchemaObject}, @@ -45,7 +46,7 @@ pub struct LanguageSettings { #[derive(Clone, Debug, Default)] pub struct CopilotSettings { pub feature_enabled: bool, - pub disabled_globs: Vec, + pub disabled_globs: Vec, } #[derive(Clone, Serialize, Deserialize, JsonSchema)] @@ -151,7 +152,7 @@ impl AllLanguageSettings { .copilot .disabled_globs .iter() - .any(|glob| glob.matches_path(path)) + .any(|glob| glob.is_match(path)) } pub fn copilot_enabled(&self, language_name: Option<&str>, path: Option<&Path>) -> bool { @@ -236,7 +237,7 @@ impl settings::Setting for AllLanguageSettings { feature_enabled: copilot_enabled, disabled_globs: copilot_globs .iter() - .filter_map(|pattern| glob::Pattern::new(pattern).ok()) + .filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher())) .collect(), }, defaults, diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 2cb6637ead..1ec0ff4a63 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -22,7 +22,6 @@ util = { path = "../util" } anyhow.workspace = true futures.workspace = true -glob.workspace = true json_comments = "0.2" lazy_static.workspace = true postage.workspace = true From 679278821663074836084bb80cdb8a1ba469a197 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 11:08:58 -0700 Subject: [PATCH 151/168] Remove unnescessary double lookup --- crates/project/src/worktree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 403d893425..61f12f7de9 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -125,7 +125,7 @@ impl Snapshot { let mut max_len = 0; let mut current_candidate = None; for (work_directory, repo) in (&self.repository_entries).iter() { - if repo.contains(self, path) { + if path.starts_with(&work_directory.0) { if work_directory.0.as_os_str().len() >= max_len { current_candidate = Some(repo); max_len = work_directory.0.as_os_str().len(); From 729a93db6b2ad9372a7249e690be3ebdc49436bd Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 May 2023 11:29:02 -0700 Subject: [PATCH 152/168] Optimize retrieving repos for entries when rendering the project panel Co-authored-by: Mikayla --- crates/project/src/worktree.rs | 63 +++++++++++++++++++++-- crates/project_panel/src/project_panel.rs | 11 ++-- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 403d893425..25470927a8 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1643,8 +1643,38 @@ impl Snapshot { self.traverse_from_offset(true, include_ignored, 0) } - pub fn repositories(&self) -> impl Iterator { - self.repository_entries.values() + pub fn repositories(&self) -> impl Iterator, &RepositoryEntry)> { + self.repository_entries + .iter() + .map(|(path, entry)| (&path.0, entry)) + } + + /// Given an ordered iterator of entries, returns an iterator of those entries, + /// along with their containing git repository. + pub fn entries_with_repos<'a>( + &'a self, + entries: impl 'a + Iterator, + ) -> impl 'a + Iterator)> { + let mut containing_repos = Vec::<(&Arc, &RepositoryEntry)>::new(); + let mut repositories = self.repositories().peekable(); + entries.map(move |entry| { + while let Some((repo_path, _)) = containing_repos.last() { + if !entry.path.starts_with(repo_path) { + containing_repos.pop(); + } else { + break; + } + } + while let Some((repo_path, _)) = repositories.peek() { + if entry.path.starts_with(repo_path) { + containing_repos.push(repositories.next().unwrap()); + } else { + break; + } + } + let repo = containing_repos.last().map(|(_, repo)| *repo); + (entry, repo) + }) } pub fn paths(&self) -> impl Iterator> { @@ -4008,6 +4038,7 @@ mod tests { #[gpui::test] async fn test_git_repository_for_path(cx: &mut TestAppContext) { let root = temp_tree(json!({ + "c.txt": "", "dir1": { ".git": {}, "deps": { @@ -4022,7 +4053,6 @@ mod tests { "b.txt": "" } }, - "c.txt": "", })); let http_client = FakeHttpClient::with_404_response(); @@ -4062,6 +4092,33 @@ mod tests { .map(|directory| directory.as_ref().to_owned()), Some(Path::new("dir1/deps/dep1").to_owned()) ); + + let entries = tree.files(false, 0); + + let paths_with_repos = tree + .entries_with_repos(entries) + .map(|(entry, repo)| { + ( + entry.path.as_ref(), + repo.and_then(|repo| { + repo.work_directory(&tree) + .map(|work_directory| work_directory.0.to_path_buf()) + }), + ) + }) + .collect::>(); + + assert_eq!( + paths_with_repos, + &[ + (Path::new("c.txt"), None), + ( + Path::new("dir1/deps/dep1/src/a.txt"), + Some(Path::new("dir1/deps/dep1").into()) + ), + (Path::new("dir1/src/b.txt"), Some(Path::new("dir1").into())), + ] + ); }); let repo_update_events = Arc::new(Mutex::new(vec![])); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 683ce8ad06..a599bac110 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1010,14 +1010,11 @@ impl ProjectPanel { .unwrap_or(&[]); let entry_range = range.start.saturating_sub(ix)..end_ix - ix; - for entry in &visible_worktree_entries[entry_range] { - let path = &entry.path; + for (entry, repo) in + snapshot.entries_with_repos(visible_worktree_entries[entry_range].iter()) + { let status = (entry.path.parent().is_some() && !entry.is_ignored) - .then(|| { - snapshot - .repo_for(path) - .and_then(|entry| entry.status_for_path(&snapshot, path)) - }) + .then(|| repo.and_then(|repo| repo.status_for_path(&snapshot, &entry.path))) .flatten(); let mut details = EntryDetails { From 9f157bdb6712734e86285347c389906f8ddd60d8 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 11:30:10 -0700 Subject: [PATCH 153/168] Remove unescessary methods --- crates/project/src/worktree.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 61f12f7de9..f178d7162a 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -169,10 +169,6 @@ impl RepositoryEntry { .map(|entry| RepositoryWorkDirectory(entry.path.clone())) } - pub(crate) fn contains(&self, snapshot: &Snapshot, path: &Path) -> bool { - self.work_directory.contains(snapshot, path) - } - pub fn status_for_file(&self, snapshot: &Snapshot, path: &Path) -> Option { self.work_directory .relativize(snapshot, path) @@ -305,14 +301,6 @@ impl AsRef for RepositoryWorkDirectory { pub struct WorkDirectoryEntry(ProjectEntryId); impl WorkDirectoryEntry { - // Note that these paths should be relative to the worktree root. - pub(crate) fn contains(&self, snapshot: &Snapshot, path: &Path) -> bool { - snapshot - .entry_for_id(self.0) - .map(|entry| path.starts_with(&entry.path)) - .unwrap_or(false) - } - pub(crate) fn relativize(&self, worktree: &Snapshot, path: &Path) -> Option { worktree.entry_for_id(self.0).and_then(|entry| { path.strip_prefix(&entry.path) From 2c8fffc4f8e1667ecc6eb1779c1a03f7de07393c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 19 May 2023 21:47:00 +0300 Subject: [PATCH 154/168] Use better name for the method that closes deleted buffers co-authored-by: Max --- crates/workspace/src/pane.rs | 2 +- crates/workspace/src/workspace.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index c54f8d393b..39f1a9fd9e 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1305,7 +1305,7 @@ impl Pane { &self.toolbar } - pub fn delete_item( + pub fn handle_deleted_project_item( &mut self, entry_id: ProjectEntryId, cx: &mut ViewContext, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 11703aba6e..8a5ce7a105 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -539,7 +539,9 @@ impl Workspace { project::Event::DeletedEntry(entry_id) => { for pane in this.panes.iter() { - pane.update(cx, |pane, cx| pane.delete_item(*entry_id, cx)); + pane.update(cx, |pane, cx| { + pane.handle_deleted_project_item(*entry_id, cx) + }); } } From 63593337493270cc82d2cff815dfe3d89ae4daf1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 May 2023 13:49:24 -0700 Subject: [PATCH 155/168] Don't store next_entry_id on worktree's local snapshot --- crates/project/src/worktree.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 26b52b848e..7431199d28 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -331,7 +331,6 @@ pub struct LocalSnapshot { // work_directory_id git_repositories: TreeMap, removed_entry_ids: HashMap, - next_entry_id: Arc, snapshot: Snapshot, } @@ -418,7 +417,6 @@ impl Worktree { ignores_by_parent_abs_path: Default::default(), removed_entry_ids: Default::default(), git_repositories: Default::default(), - next_entry_id, snapshot: Snapshot { id: WorktreeId::from_usize(cx.model_id()), abs_path: abs_path.clone(), @@ -437,7 +435,7 @@ impl Worktree { Entry::new( Arc::from(Path::new("")), &metadata, - &snapshot.next_entry_id, + &next_entry_id, snapshot.root_char_bag, ), fs.as_ref(), @@ -481,6 +479,7 @@ impl Worktree { let events = fs.watch(&abs_path, Duration::from_millis(100)).await; BackgroundScanner::new( snapshot, + next_entry_id, fs, scan_states_tx, background, @@ -1229,7 +1228,6 @@ impl LocalWorktree { let mut prev_snapshot = LocalSnapshot { ignores_by_parent_abs_path: Default::default(), removed_entry_ids: Default::default(), - next_entry_id: Default::default(), git_repositories: Default::default(), snapshot: Snapshot { id: WorktreeId(worktree_id as usize), @@ -2571,6 +2569,7 @@ struct BackgroundScanner { executor: Arc, refresh_requests_rx: channel::Receiver<(Vec, barrier::Sender)>, prev_state: Mutex, + next_entry_id: Arc, finished_initial_scan: bool, } @@ -2582,6 +2581,7 @@ struct BackgroundScannerState { impl BackgroundScanner { fn new( snapshot: LocalSnapshot, + next_entry_id: Arc, fs: Arc, status_updates_tx: UnboundedSender, executor: Arc, @@ -2592,6 +2592,7 @@ impl BackgroundScanner { status_updates_tx, executor, refresh_requests_rx, + next_entry_id, prev_state: Mutex::new(BackgroundScannerState { snapshot: snapshot.snapshot.clone(), event_paths: Default::default(), @@ -2864,7 +2865,7 @@ impl BackgroundScanner { ( snapshot.abs_path().clone(), snapshot.root_char_bag, - snapshot.next_entry_id.clone(), + self.next_entry_id.clone(), ) }; let mut child_paths = self.fs.read_dir(&job.abs_path).await?; @@ -3036,7 +3037,7 @@ impl BackgroundScanner { let mut fs_entry = Entry::new( path.clone(), &metadata, - snapshot.next_entry_id.as_ref(), + self.next_entry_id.as_ref(), snapshot.root_char_bag, ); fs_entry.is_ignored = ignore_stack.is_all(); From 32c71579063e7db4adeb4efa374c47cbb143a668 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 May 2023 14:28:26 -0700 Subject: [PATCH 156/168] :art: Make worktree repositories more consistent --- crates/project/src/worktree.rs | 59 ++++++++++++----------- crates/project_panel/src/project_panel.rs | 2 +- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 7431199d28..24e8a00482 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -120,25 +120,6 @@ pub struct Snapshot { completed_scan_id: usize, } -impl Snapshot { - pub fn repo_for(&self, path: &Path) -> Option { - let mut max_len = 0; - let mut current_candidate = None; - for (work_directory, repo) in (&self.repository_entries).iter() { - if path.starts_with(&work_directory.0) { - if work_directory.0.as_os_str().len() >= max_len { - current_candidate = Some(repo); - max_len = work_directory.0.as_os_str().len(); - } else { - break; - } - } - } - - current_candidate.map(|entry| entry.to_owned()) - } -} - #[derive(Clone, Debug, PartialEq, Eq)] pub struct RepositoryEntry { pub(crate) work_directory: WorkDirectoryEntry, @@ -900,7 +881,7 @@ impl LocalWorktree { let mut index_task = None; - if let Some(repo) = snapshot.repo_for(&path) { + if let Some(repo) = snapshot.repository_for_path(&path) { let repo_path = repo.work_directory.relativize(self, &path).unwrap(); if let Some(repo) = self.git_repositories.get(&*repo.work_directory) { let repo = repo.repo_ptr.to_owned(); @@ -1635,9 +1616,27 @@ impl Snapshot { .map(|(path, entry)| (&path.0, entry)) } + /// Get the repository whose work directory contains the given path. + pub fn repository_for_path(&self, path: &Path) -> Option { + let mut max_len = 0; + let mut current_candidate = None; + for (work_directory, repo) in (&self.repository_entries).iter() { + if path.starts_with(&work_directory.0) { + if work_directory.0.as_os_str().len() >= max_len { + current_candidate = Some(repo); + max_len = work_directory.0.as_os_str().len(); + } else { + break; + } + } + } + + current_candidate.map(|entry| entry.to_owned()) + } + /// Given an ordered iterator of entries, returns an iterator of those entries, /// along with their containing git repository. - pub fn entries_with_repos<'a>( + pub fn entries_with_repositories<'a>( &'a self, entries: impl 'a + Iterator, ) -> impl 'a + Iterator)> { @@ -3077,7 +3076,7 @@ impl BackgroundScanner { .any(|component| component.as_os_str() == *DOT_GIT) { let scan_id = snapshot.scan_id; - let repo = snapshot.repo_for(&path)?; + let repo = snapshot.repository_for_path(&path)?; let repo_path = repo.work_directory.relativize(&snapshot, &path)?; @@ -3153,7 +3152,7 @@ impl BackgroundScanner { return None; } - let repo = snapshot.repo_for(&path)?; + let repo = snapshot.repository_for_path(&path)?; let work_dir = repo.work_directory(snapshot)?; let work_dir_id = repo.work_directory.clone(); @@ -4064,9 +4063,9 @@ mod tests { tree.read_with(cx, |tree, _cx| { let tree = tree.as_local().unwrap(); - assert!(tree.repo_for("c.txt".as_ref()).is_none()); + assert!(tree.repository_for_path("c.txt".as_ref()).is_none()); - let entry = tree.repo_for("dir1/src/b.txt".as_ref()).unwrap(); + let entry = tree.repository_for_path("dir1/src/b.txt".as_ref()).unwrap(); assert_eq!( entry .work_directory(tree) @@ -4074,7 +4073,9 @@ mod tests { Some(Path::new("dir1").to_owned()) ); - let entry = tree.repo_for("dir1/deps/dep1/src/a.txt".as_ref()).unwrap(); + let entry = tree + .repository_for_path("dir1/deps/dep1/src/a.txt".as_ref()) + .unwrap(); assert_eq!( entry .work_directory(tree) @@ -4085,7 +4086,7 @@ mod tests { let entries = tree.files(false, 0); let paths_with_repos = tree - .entries_with_repos(entries) + .entries_with_repositories(entries) .map(|(entry, repo)| { ( entry.path.as_ref(), @@ -4138,7 +4139,9 @@ mod tests { tree.read_with(cx, |tree, _cx| { let tree = tree.as_local().unwrap(); - assert!(tree.repo_for("dir1/src/b.txt".as_ref()).is_none()); + assert!(tree + .repository_for_path("dir1/src/b.txt".as_ref()) + .is_none()); }); } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 93c8f41cef..ce0dd9e222 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1011,7 +1011,7 @@ impl ProjectPanel { let entry_range = range.start.saturating_sub(ix)..end_ix - ix; for (entry, repo) in - snapshot.entries_with_repos(visible_worktree_entries[entry_range].iter()) + snapshot.entries_with_repositories(visible_worktree_entries[entry_range].iter()) { let status = (entry.path.parent().is_some() && !entry.is_ignored) .then(|| repo.and_then(|repo| repo.status_for_path(&snapshot, &entry.path))) From c193b0b8fc03abf8ff31982fe81c7cec73e25f51 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 15:10:29 -0700 Subject: [PATCH 157/168] Add guards to other pane index removals --- crates/workspace/src/pane.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 39f1a9fd9e..a196133f3a 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -711,10 +711,7 @@ impl Pane { let pane_handle = workspace.active_pane().clone(); let pane = pane_handle.read(cx); - if pane.items.is_empty() { - return None; - } - let active_item_id = pane.items[pane.active_item_index].id(); + let active_item_id = pane.items.get(pane.active_item_index)?.id(); let task = Self::close_item_by_id(workspace, pane_handle, active_item_id, cx); @@ -742,7 +739,8 @@ impl Pane { ) -> Option>> { let pane_handle = workspace.active_pane().clone(); let pane = pane_handle.read(cx); - let active_item_id = pane.items[pane.active_item_index].id(); + + let active_item_id = pane.items.get(pane.active_item_index)?.id(); let task = Self::close_items(workspace, pane_handle, cx, move |item_id| { item_id != active_item_id @@ -785,7 +783,7 @@ impl Pane { ) -> Option>> { let pane_handle = workspace.active_pane().clone(); let pane = pane_handle.read(cx); - let active_item_id = pane.items[pane.active_item_index].id(); + let active_item_id = pane.items.get(pane.active_item_index)?.id(); let task = Self::close_items_to_the_left_by_id(workspace, pane_handle, active_item_id, cx); @@ -825,7 +823,7 @@ impl Pane { ) -> Option>> { let pane_handle = workspace.active_pane().clone(); let pane = pane_handle.read(cx); - let active_item_id = pane.items[pane.active_item_index].id(); + let active_item_id = pane.items.get(pane.active_item_index)?.id(); let task = Self::close_items_to_the_right_by_id(workspace, pane_handle, active_item_id, cx); From 7ae642b9b8746252b2b1cbb42d75c096fea295df Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 19 May 2023 14:23:35 -0700 Subject: [PATCH 158/168] Avoid storing removed_entry_ids on the LocalSnapshot --- crates/project/src/worktree.rs | 247 ++++++++++++++++++--------------- 1 file changed, 138 insertions(+), 109 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 24e8a00482..b7cb82f628 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -227,7 +227,7 @@ impl RepositoryEntry { work_directory_id: self.work_directory_id().to_proto(), branch: self.branch.as_ref().map(|str| str.to_string()), removed_repo_paths: removed_statuses, - updated_statuses: updated_statuses, + updated_statuses, } } } @@ -307,12 +307,22 @@ impl<'a> From for WorkDirectoryEntry { #[derive(Debug, Clone)] pub struct LocalSnapshot { - ignores_by_parent_abs_path: HashMap, (Arc, bool)>, // (gitignore, needs_update) - // The ProjectEntryId corresponds to the entry for the .git dir - // work_directory_id - git_repositories: TreeMap, - removed_entry_ids: HashMap, snapshot: Snapshot, + /// All of the gitignore files in the worktree, indexed by their relative path. + /// The boolean indicates whether the gitignore needs to be updated. + ignores_by_parent_abs_path: HashMap, (Arc, bool)>, + /// All of the git repositories in the worktree, indexed by the project entry + /// id of their parent directory. + git_repositories: TreeMap, +} + +pub struct LocalMutableSnapshot { + snapshot: LocalSnapshot, + /// The ids of all of the entries that were removed from the snapshot + /// as part of the current update. These entry ids may be re-used + /// if the same inode is discovered at a new path, or if the given + /// path is re-created after being deleted. + removed_entry_ids: HashMap, } #[derive(Debug, Clone)] @@ -346,6 +356,20 @@ impl DerefMut for LocalSnapshot { } } +impl Deref for LocalMutableSnapshot { + type Target = LocalSnapshot; + + fn deref(&self) -> &Self::Target { + &self.snapshot + } +} + +impl DerefMut for LocalMutableSnapshot { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.snapshot + } +} + enum ScanState { Started, Updated { @@ -396,7 +420,6 @@ impl Worktree { let mut snapshot = LocalSnapshot { ignores_by_parent_abs_path: Default::default(), - removed_entry_ids: Default::default(), git_repositories: Default::default(), snapshot: Snapshot { id: WorktreeId::from_usize(cx.model_id()), @@ -1208,7 +1231,6 @@ impl LocalWorktree { let mut share_tx = Some(share_tx); let mut prev_snapshot = LocalSnapshot { ignores_by_parent_abs_path: Default::default(), - removed_entry_ids: Default::default(), git_repositories: Default::default(), snapshot: Snapshot { id: WorktreeId(worktree_id as usize), @@ -1910,8 +1932,6 @@ impl LocalSnapshot { } } - self.reuse_entry_id(&mut entry); - if entry.kind == EntryKind::PendingDir { if let Some(existing_entry) = self.entries_by_path.get(&PathKey(entry.path.clone()), &()) @@ -1940,60 +1960,6 @@ impl LocalSnapshot { entry } - fn populate_dir( - &mut self, - parent_path: Arc, - entries: impl IntoIterator, - ignore: Option>, - fs: &dyn Fs, - ) { - let mut parent_entry = if let Some(parent_entry) = - self.entries_by_path.get(&PathKey(parent_path.clone()), &()) - { - parent_entry.clone() - } else { - log::warn!( - "populating a directory {:?} that has been removed", - parent_path - ); - return; - }; - - match parent_entry.kind { - EntryKind::PendingDir => { - parent_entry.kind = EntryKind::Dir; - } - EntryKind::Dir => {} - _ => return, - } - - if let Some(ignore) = ignore { - self.ignores_by_parent_abs_path - .insert(self.abs_path.join(&parent_path).into(), (ignore, false)); - } - - if parent_path.file_name() == Some(&DOT_GIT) { - self.build_repo(parent_path, fs); - } - - let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)]; - let mut entries_by_id_edits = Vec::new(); - - for mut entry in entries { - self.reuse_entry_id(&mut entry); - entries_by_id_edits.push(Edit::Insert(PathEntry { - id: entry.id, - path: entry.path.clone(), - is_ignored: entry.is_ignored, - scan_id: self.scan_id, - })); - entries_by_path_edits.push(Edit::Insert(entry)); - } - - self.entries_by_path.edit(entries_by_path_edits, &()); - self.entries_by_id.edit(entries_by_id_edits, &()); - } - fn build_repo(&mut self, parent_path: Arc, fs: &dyn Fs) -> Option<()> { let abs_path = self.abs_path.join(&parent_path); let work_dir: Arc = parent_path.parent().unwrap().into(); @@ -2041,46 +2007,6 @@ impl LocalSnapshot { Some(()) } - fn reuse_entry_id(&mut self, entry: &mut Entry) { - if let Some(removed_entry_id) = self.removed_entry_ids.remove(&entry.inode) { - entry.id = removed_entry_id; - } else if let Some(existing_entry) = self.entry_for_path(&entry.path) { - entry.id = existing_entry.id; - } - } - - fn remove_path(&mut self, path: &Path) { - let mut new_entries; - let removed_entries; - { - let mut cursor = self.entries_by_path.cursor::(); - new_entries = cursor.slice(&TraversalTarget::Path(path), Bias::Left, &()); - removed_entries = cursor.slice(&TraversalTarget::PathSuccessor(path), Bias::Left, &()); - new_entries.push_tree(cursor.suffix(&()), &()); - } - self.entries_by_path = new_entries; - - let mut entries_by_id_edits = Vec::new(); - for entry in removed_entries.cursor::<()>() { - let removed_entry_id = self - .removed_entry_ids - .entry(entry.inode) - .or_insert(entry.id); - *removed_entry_id = cmp::max(*removed_entry_id, entry.id); - entries_by_id_edits.push(Edit::Remove(entry.id)); - } - self.entries_by_id.edit(entries_by_id_edits, &()); - - if path.file_name() == Some(&GITIGNORE) { - let abs_parent_path = self.abs_path.join(path.parent().unwrap()); - if let Some((_, needs_update)) = self - .ignores_by_parent_abs_path - .get_mut(abs_parent_path.as_path()) - { - *needs_update = true; - } - } - } fn ancestor_inodes_for_path(&self, path: &Path) -> TreeSet { let mut inodes = TreeSet::default(); @@ -2120,6 +2046,109 @@ impl LocalSnapshot { } } +impl LocalMutableSnapshot { + fn reuse_entry_id(&mut self, entry: &mut Entry) { + if let Some(removed_entry_id) = self.removed_entry_ids.remove(&entry.inode) { + entry.id = removed_entry_id; + } else if let Some(existing_entry) = self.entry_for_path(&entry.path) { + entry.id = existing_entry.id; + } + } + + fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs) -> Entry { + self.reuse_entry_id(&mut entry); + self.snapshot.insert_entry(entry, fs) + } + + fn populate_dir( + &mut self, + parent_path: Arc, + entries: impl IntoIterator, + ignore: Option>, + fs: &dyn Fs, + ) { + let mut parent_entry = if let Some(parent_entry) = + self.entries_by_path.get(&PathKey(parent_path.clone()), &()) + { + parent_entry.clone() + } else { + log::warn!( + "populating a directory {:?} that has been removed", + parent_path + ); + return; + }; + + match parent_entry.kind { + EntryKind::PendingDir => { + parent_entry.kind = EntryKind::Dir; + } + EntryKind::Dir => {} + _ => return, + } + + if let Some(ignore) = ignore { + let abs_parent_path = self.abs_path.join(&parent_path).into(); + self.ignores_by_parent_abs_path + .insert(abs_parent_path, (ignore, false)); + } + + if parent_path.file_name() == Some(&DOT_GIT) { + self.build_repo(parent_path, fs); + } + + let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)]; + let mut entries_by_id_edits = Vec::new(); + + for mut entry in entries { + self.reuse_entry_id(&mut entry); + entries_by_id_edits.push(Edit::Insert(PathEntry { + id: entry.id, + path: entry.path.clone(), + is_ignored: entry.is_ignored, + scan_id: self.scan_id, + })); + entries_by_path_edits.push(Edit::Insert(entry)); + } + + self.entries_by_path.edit(entries_by_path_edits, &()); + self.entries_by_id.edit(entries_by_id_edits, &()); + } + + fn remove_path(&mut self, path: &Path) { + let mut new_entries; + let removed_entries; + { + let mut cursor = self.entries_by_path.cursor::(); + new_entries = cursor.slice(&TraversalTarget::Path(path), Bias::Left, &()); + removed_entries = cursor.slice(&TraversalTarget::PathSuccessor(path), Bias::Left, &()); + new_entries.push_tree(cursor.suffix(&()), &()); + } + self.entries_by_path = new_entries; + + let mut entries_by_id_edits = Vec::new(); + for entry in removed_entries.cursor::<()>() { + let removed_entry_id = self + .removed_entry_ids + .entry(entry.inode) + .or_insert(entry.id); + *removed_entry_id = cmp::max(*removed_entry_id, entry.id); + entries_by_id_edits.push(Edit::Remove(entry.id)); + } + self.entries_by_id.edit(entries_by_id_edits, &()); + + if path.file_name() == Some(&GITIGNORE) { + let abs_parent_path = self.abs_path.join(path.parent().unwrap()); + if let Some((_, needs_update)) = self + .ignores_by_parent_abs_path + .get_mut(abs_parent_path.as_path()) + { + *needs_update = true; + } + } + } +} + async fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result { let contents = fs.load(abs_path).await?; let parent = abs_path.parent().unwrap_or_else(|| Path::new("/")); @@ -2562,7 +2591,7 @@ impl<'a> sum_tree::Dimension<'a, EntrySummary> for PathKey { } struct BackgroundScanner { - snapshot: Mutex, + snapshot: Mutex, fs: Arc, status_updates_tx: UnboundedSender, executor: Arc, @@ -2596,7 +2625,10 @@ impl BackgroundScanner { snapshot: snapshot.snapshot.clone(), event_paths: Default::default(), }), - snapshot: Mutex::new(snapshot), + snapshot: Mutex::new(LocalMutableSnapshot { + snapshot, + removed_entry_ids: Default::default(), + }), finished_initial_scan: false, } } @@ -2750,10 +2782,7 @@ impl BackgroundScanner { .is_some() }); snapshot.snapshot.repository_entries = git_repository_entries; - - snapshot.removed_entry_ids.clear(); snapshot.completed_scan_id = snapshot.scan_id; - drop(snapshot); self.send_status_update(false, None); From 2a41a32aac5948c5a16bd2640bec7acce3f88ccc Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 12:11:38 -0700 Subject: [PATCH 159/168] Calculate y offsets correctly --- crates/editor/src/element.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 0a17fc8baf..35279913c5 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -42,6 +42,7 @@ use language::{ }; use project::ProjectPath; use smallvec::SmallVec; +use text::Point; use std::{ borrow::Cow, cmp::{self, Ordering}, @@ -1058,11 +1059,13 @@ impl EditorElement { .buffer_snapshot .git_diff_hunks_in_range(0..(max_row.floor() as u32), false) { - let start_y = y_for_row(hunk.buffer_range.start as f32); + let start_display = Point::new(hunk.buffer_range.start, 0).to_display_point(&layout.position_map.snapshot.display_snapshot); + let end_display = Point::new(hunk.buffer_range.end, 0).to_display_point(&layout.position_map.snapshot.display_snapshot); + let start_y = y_for_row(start_display.row() as f32); let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end { - y_for_row((hunk.buffer_range.end + 1) as f32) + y_for_row((end_display.row() + 1) as f32) } else { - y_for_row((hunk.buffer_range.end) as f32) + y_for_row((end_display.row()) as f32) }; if end_y - start_y < 1. { From 560160b1008646e39ad40779dfcbf4017057d2c6 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 15:06:38 -0700 Subject: [PATCH 160/168] Batch anchor conversions in git hunk iterator --- crates/git/src/diff.rs | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/crates/git/src/diff.rs b/crates/git/src/diff.rs index b28af26f16..a2349649c5 100644 --- a/crates/git/src/diff.rs +++ b/crates/git/src/diff.rs @@ -1,6 +1,6 @@ -use std::ops::Range; +use std::{cell::RefCell, iter, ops::Range}; use sum_tree::SumTree; -use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point}; +use text::{Anchor, BufferSnapshot, Point}; pub use git2 as libgit; use libgit::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch}; @@ -94,25 +94,37 @@ impl BufferDiff { !before_start && !after_end }); - std::iter::from_fn(move || { + use std::rc::Rc; + let cell = Rc::new(RefCell::new(None)); + + let anchor_iter = std::iter::from_fn(move || { if reversed { cursor.prev(buffer); } else { cursor.next(buffer); } - let hunk = cursor.item()?; + cursor.item() + }) + .flat_map({ + let cell = cell.clone(); + move |hunk| { + *cell.borrow_mut() = Some(hunk.diff_base_byte_range.clone()); + iter::once(&hunk.buffer_range.start).chain(iter::once(&hunk.buffer_range.end)) + } + }); - let range = hunk.buffer_range.to_point(buffer); - let end_row = if range.end.column > 0 { - range.end.row + 1 - } else { - range.end.row - }; + let mut summaries = buffer.summaries_for_anchors::(anchor_iter); + iter::from_fn(move || { + let start = summaries.next()?; + let end = summaries.next()?; + let base = (cell.borrow_mut()).clone()?; + + let end_row = if end.column > 0 { end.row + 1 } else { end.row }; Some(DiffHunk { - buffer_range: range.start.row..end_row, - diff_base_byte_range: hunk.diff_base_byte_range.clone(), + buffer_range: start.row..end_row, + diff_base_byte_range: base, }) }) } @@ -279,6 +291,8 @@ pub fn assert_hunks( #[cfg(test)] mod tests { + use std::assert_eq; + use super::*; use text::Buffer; use unindent::Unindent as _; From 623a177fe6ab363a0dbcc2cae509a3a2fc281331 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 15:30:10 -0700 Subject: [PATCH 161/168] Fix bug where git diff hunks would not extend through a soft wrap --- crates/editor/src/element.rs | 8 ++++---- crates/editor/src/git.rs | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 35279913c5..8a74a08c86 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -652,7 +652,7 @@ impl EditorElement { //TODO: This rendering is entirely a horrible hack DiffHunkStatus::Removed => { - let row = *display_row_range.start(); + let row = display_row_range.start; let offset = line_height / 2.; let start_y = row as f32 * line_height - offset - scroll_top; @@ -674,11 +674,11 @@ impl EditorElement { } }; - let start_row = *display_row_range.start(); - let end_row = *display_row_range.end(); + let start_row = display_row_range.start; + let end_row = display_row_range.end; let start_y = start_row as f32 * line_height - scroll_top; - let end_y = end_row as f32 * line_height - scroll_top + line_height; + let end_y = end_row as f32 * line_height - scroll_top; let width = diff_style.width_em * line_height; let highlight_origin = bounds.origin() + vec2f(-width, start_y); diff --git a/crates/editor/src/git.rs b/crates/editor/src/git.rs index 549d74a0b5..5055cc6466 100644 --- a/crates/editor/src/git.rs +++ b/crates/editor/src/git.rs @@ -1,4 +1,5 @@ -use std::ops::RangeInclusive; + +use std::ops::Range; use git::diff::{DiffHunk, DiffHunkStatus}; use language::Point; @@ -15,7 +16,7 @@ pub enum DisplayDiffHunk { }, Unfolded { - display_row_range: RangeInclusive, + display_row_range: Range, status: DiffHunkStatus, }, } @@ -26,7 +27,7 @@ impl DisplayDiffHunk { &DisplayDiffHunk::Folded { display_row } => display_row, DisplayDiffHunk::Unfolded { display_row_range, .. - } => *display_row_range.start(), + } => display_row_range.start, } } @@ -36,7 +37,7 @@ impl DisplayDiffHunk { DisplayDiffHunk::Unfolded { display_row_range, .. - } => display_row_range.clone(), + } => display_row_range.start..=display_row_range.end - 1, }; range.contains(&display_row) @@ -80,13 +81,12 @@ pub fn diff_hunk_to_display(hunk: DiffHunk, snapshot: &DisplaySnapshot) -> let hunk_end_row_inclusive = hunk .buffer_range .end - .saturating_sub(1) .max(hunk.buffer_range.start); let hunk_end_point = Point::new(hunk_end_row_inclusive, 0); let end = hunk_end_point.to_display_point(snapshot).row(); DisplayDiffHunk::Unfolded { - display_row_range: start..=end, + display_row_range: start..end, status: hunk.status(), } } From c795c9b8449077bdcd34996bf424d4c28c9e6b2a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 16:23:21 -0700 Subject: [PATCH 162/168] Rearrange git tests in worktree Add support for renaming work directories --- crates/editor/src/element.rs | 8 +- crates/editor/src/git.rs | 6 +- crates/project/src/worktree.rs | 901 ++++++++++++++++++--------------- 3 files changed, 503 insertions(+), 412 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 8a74a08c86..7285db7366 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -42,7 +42,6 @@ use language::{ }; use project::ProjectPath; use smallvec::SmallVec; -use text::Point; use std::{ borrow::Cow, cmp::{self, Ordering}, @@ -51,6 +50,7 @@ use std::{ ops::Range, sync::Arc, }; +use text::Point; use workspace::{item::Item, GitGutterSetting, WorkspaceSettings}; enum FoldMarkers {} @@ -1059,8 +1059,10 @@ impl EditorElement { .buffer_snapshot .git_diff_hunks_in_range(0..(max_row.floor() as u32), false) { - let start_display = Point::new(hunk.buffer_range.start, 0).to_display_point(&layout.position_map.snapshot.display_snapshot); - let end_display = Point::new(hunk.buffer_range.end, 0).to_display_point(&layout.position_map.snapshot.display_snapshot); + let start_display = Point::new(hunk.buffer_range.start, 0) + .to_display_point(&layout.position_map.snapshot.display_snapshot); + let end_display = Point::new(hunk.buffer_range.end, 0) + .to_display_point(&layout.position_map.snapshot.display_snapshot); let start_y = y_for_row(start_display.row() as f32); let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end { y_for_row((end_display.row() + 1) as f32) diff --git a/crates/editor/src/git.rs b/crates/editor/src/git.rs index 5055cc6466..3452138126 100644 --- a/crates/editor/src/git.rs +++ b/crates/editor/src/git.rs @@ -1,4 +1,3 @@ - use std::ops::Range; use git::diff::{DiffHunk, DiffHunkStatus}; @@ -78,10 +77,7 @@ pub fn diff_hunk_to_display(hunk: DiffHunk, snapshot: &DisplaySnapshot) -> } else { let start = hunk_start_point.to_display_point(snapshot).row(); - let hunk_end_row_inclusive = hunk - .buffer_range - .end - .max(hunk.buffer_range.start); + let hunk_end_row_inclusive = hunk.buffer_range.end.max(hunk.buffer_range.start); let hunk_end_point = Point::new(hunk_end_row_inclusive, 0); let end = hunk_end_point.to_display_point(snapshot).row(); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index b7cb82f628..b4f188a2c3 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -150,13 +150,6 @@ impl RepositoryEntry { .map(|entry| RepositoryWorkDirectory(entry.path.clone())) } - pub fn status_for_file(&self, snapshot: &Snapshot, path: &Path) -> Option { - self.work_directory - .relativize(snapshot, path) - .and_then(|repo_path| self.statuses.get(&repo_path)) - .cloned() - } - pub fn status_for_path(&self, snapshot: &Snapshot, path: &Path) -> Option { self.work_directory .relativize(snapshot, path) @@ -182,6 +175,14 @@ impl RepositoryEntry { }) } + #[cfg(any(test, feature = "test-support"))] + pub fn status_for_file(&self, snapshot: &Snapshot, path: &Path) -> Option { + self.work_directory + .relativize(snapshot, path) + .and_then(|repo_path| (&self.statuses).get(&repo_path)) + .cloned() + } + pub fn build_update(&self, other: &Self) -> proto::RepositoryEntry { let mut updated_statuses: Vec = Vec::new(); let mut removed_statuses: Vec = Vec::new(); @@ -1638,6 +1639,11 @@ impl Snapshot { .map(|(path, entry)| (&path.0, entry)) } + /// Get the repository whose work directory contains the given path. + pub fn repository_for_work_directory(&self, path: &Path) -> Option { + self.repository_entries.get(&RepositoryWorkDirectory(path.into())).cloned() + } + /// Get the repository whose work directory contains the given path. pub fn repository_for_path(&self, path: &Path) -> Option { let mut max_len = 0; @@ -1653,7 +1659,7 @@ impl Snapshot { } } - current_candidate.map(|entry| entry.to_owned()) + current_candidate.cloned() } /// Given an ordered iterator of entries, returns an iterator of those entries, @@ -3105,6 +3111,17 @@ impl BackgroundScanner { .any(|component| component.as_os_str() == *DOT_GIT) { let scan_id = snapshot.scan_id; + + if let Some(repository) = snapshot.repository_for_work_directory(path) { + let entry = repository.work_directory.0; + snapshot.git_repositories.remove(&entry); + snapshot + .snapshot + .repository_entries + .remove(&RepositoryWorkDirectory(path.into())); + return Some(()); + } + let repo = snapshot.repository_for_path(&path)?; let repo_path = repo.work_directory.relativize(&snapshot, &path)?; @@ -3975,6 +3992,8 @@ mod tests { #[gpui::test] async fn test_rescan_with_gitignore(cx: &mut TestAppContext) { + // .gitignores are handled explicitly by Zed and do not use the git + // machinery that the git_tests module checks let parent_dir = temp_tree(json!({ ".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n", "tree": { @@ -4052,402 +4071,6 @@ mod tests { }); } - #[gpui::test] - async fn test_git_repository_for_path(cx: &mut TestAppContext) { - let root = temp_tree(json!({ - "c.txt": "", - "dir1": { - ".git": {}, - "deps": { - "dep1": { - ".git": {}, - "src": { - "a.txt": "" - } - } - }, - "src": { - "b.txt": "" - } - }, - })); - - let http_client = FakeHttpClient::with_404_response(); - let client = cx.read(|cx| Client::new(http_client, cx)); - let tree = Worktree::local( - client, - root.path(), - true, - Arc::new(RealFs), - Default::default(), - &mut cx.to_async(), - ) - .await - .unwrap(); - - cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) - .await; - tree.flush_fs_events(cx).await; - - tree.read_with(cx, |tree, _cx| { - let tree = tree.as_local().unwrap(); - - assert!(tree.repository_for_path("c.txt".as_ref()).is_none()); - - let entry = tree.repository_for_path("dir1/src/b.txt".as_ref()).unwrap(); - assert_eq!( - entry - .work_directory(tree) - .map(|directory| directory.as_ref().to_owned()), - Some(Path::new("dir1").to_owned()) - ); - - let entry = tree - .repository_for_path("dir1/deps/dep1/src/a.txt".as_ref()) - .unwrap(); - assert_eq!( - entry - .work_directory(tree) - .map(|directory| directory.as_ref().to_owned()), - Some(Path::new("dir1/deps/dep1").to_owned()) - ); - - let entries = tree.files(false, 0); - - let paths_with_repos = tree - .entries_with_repositories(entries) - .map(|(entry, repo)| { - ( - entry.path.as_ref(), - repo.and_then(|repo| { - repo.work_directory(&tree) - .map(|work_directory| work_directory.0.to_path_buf()) - }), - ) - }) - .collect::>(); - - assert_eq!( - paths_with_repos, - &[ - (Path::new("c.txt"), None), - ( - Path::new("dir1/deps/dep1/src/a.txt"), - Some(Path::new("dir1/deps/dep1").into()) - ), - (Path::new("dir1/src/b.txt"), Some(Path::new("dir1").into())), - ] - ); - }); - - let repo_update_events = Arc::new(Mutex::new(vec![])); - tree.update(cx, |_, cx| { - let repo_update_events = repo_update_events.clone(); - cx.subscribe(&tree, move |_, _, event, _| { - if let Event::UpdatedGitRepositories(update) = event { - repo_update_events.lock().push(update.clone()); - } - }) - .detach(); - }); - - std::fs::write(root.path().join("dir1/.git/random_new_file"), "hello").unwrap(); - tree.flush_fs_events(cx).await; - - assert_eq!( - repo_update_events.lock()[0] - .keys() - .cloned() - .collect::>>(), - vec![Path::new("dir1").into()] - ); - - std::fs::remove_dir_all(root.path().join("dir1/.git")).unwrap(); - tree.flush_fs_events(cx).await; - - tree.read_with(cx, |tree, _cx| { - let tree = tree.as_local().unwrap(); - - assert!(tree - .repository_for_path("dir1/src/b.txt".as_ref()) - .is_none()); - }); - } - - #[gpui::test] - async fn test_git_status(cx: &mut TestAppContext) { - #[track_caller] - fn git_init(path: &Path) -> git2::Repository { - git2::Repository::init(path).expect("Failed to initialize git repository") - } - - #[track_caller] - fn git_add(path: &Path, repo: &git2::Repository) { - let mut index = repo.index().expect("Failed to get index"); - index.add_path(path).expect("Failed to add a.txt"); - index.write().expect("Failed to write index"); - } - - #[track_caller] - fn git_remove_index(path: &Path, repo: &git2::Repository) { - let mut index = repo.index().expect("Failed to get index"); - index.remove_path(path).expect("Failed to add a.txt"); - index.write().expect("Failed to write index"); - } - - #[track_caller] - fn git_commit(msg: &'static str, repo: &git2::Repository) { - use git2::Signature; - - let signature = Signature::now("test", "test@zed.dev").unwrap(); - let oid = repo.index().unwrap().write_tree().unwrap(); - let tree = repo.find_tree(oid).unwrap(); - if let Some(head) = repo.head().ok() { - let parent_obj = head.peel(git2::ObjectType::Commit).unwrap(); - - let parent_commit = parent_obj.as_commit().unwrap(); - - repo.commit( - Some("HEAD"), - &signature, - &signature, - msg, - &tree, - &[parent_commit], - ) - .expect("Failed to commit with parent"); - } else { - repo.commit(Some("HEAD"), &signature, &signature, msg, &tree, &[]) - .expect("Failed to commit"); - } - } - - #[track_caller] - fn git_stash(repo: &mut git2::Repository) { - use git2::Signature; - - let signature = Signature::now("test", "test@zed.dev").unwrap(); - repo.stash_save(&signature, "N/A", None) - .expect("Failed to stash"); - } - - #[track_caller] - fn git_reset(offset: usize, repo: &git2::Repository) { - let head = repo.head().expect("Couldn't get repo head"); - let object = head.peel(git2::ObjectType::Commit).unwrap(); - let commit = object.as_commit().unwrap(); - let new_head = commit - .parents() - .inspect(|parnet| { - parnet.message(); - }) - .skip(offset) - .next() - .expect("Not enough history"); - repo.reset(&new_head.as_object(), git2::ResetType::Soft, None) - .expect("Could not reset"); - } - - #[allow(dead_code)] - #[track_caller] - fn git_status(repo: &git2::Repository) -> HashMap { - repo.statuses(None) - .unwrap() - .iter() - .map(|status| (status.path().unwrap().to_string(), status.status())) - .collect() - } - - const IGNORE_RULE: &'static str = "**/target"; - - let root = temp_tree(json!({ - "project": { - "a.txt": "a", - "b.txt": "bb", - "c": { - "d": { - "e.txt": "eee" - } - }, - "f.txt": "ffff", - "target": { - "build_file": "???" - }, - ".gitignore": IGNORE_RULE - }, - - })); - - let http_client = FakeHttpClient::with_404_response(); - let client = cx.read(|cx| Client::new(http_client, cx)); - let tree = Worktree::local( - client, - root.path(), - true, - Arc::new(RealFs), - Default::default(), - &mut cx.to_async(), - ) - .await - .unwrap(); - - cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) - .await; - - const A_TXT: &'static str = "a.txt"; - const B_TXT: &'static str = "b.txt"; - const E_TXT: &'static str = "c/d/e.txt"; - const F_TXT: &'static str = "f.txt"; - const DOTGITIGNORE: &'static str = ".gitignore"; - const BUILD_FILE: &'static str = "target/build_file"; - - let work_dir = root.path().join("project"); - let mut repo = git_init(work_dir.as_path()); - repo.add_ignore_rule(IGNORE_RULE).unwrap(); - git_add(Path::new(A_TXT), &repo); - git_add(Path::new(E_TXT), &repo); - git_add(Path::new(DOTGITIGNORE), &repo); - git_commit("Initial commit", &repo); - - std::fs::write(work_dir.join(A_TXT), "aa").unwrap(); - - tree.flush_fs_events(cx).await; - - // Check that the right git state is observed on startup - tree.read_with(cx, |tree, _cx| { - let snapshot = tree.snapshot(); - assert_eq!(snapshot.repository_entries.iter().count(), 1); - let (dir, repo) = snapshot.repository_entries.iter().next().unwrap(); - assert_eq!(dir.0.as_ref(), Path::new("project")); - - assert_eq!(repo.statuses.iter().count(), 3); - assert_eq!( - repo.statuses.get(&Path::new(A_TXT).into()), - Some(&GitFileStatus::Modified) - ); - assert_eq!( - repo.statuses.get(&Path::new(B_TXT).into()), - Some(&GitFileStatus::Added) - ); - assert_eq!( - repo.statuses.get(&Path::new(F_TXT).into()), - Some(&GitFileStatus::Added) - ); - }); - - git_add(Path::new(A_TXT), &repo); - git_add(Path::new(B_TXT), &repo); - git_commit("Committing modified and added", &repo); - tree.flush_fs_events(cx).await; - - // Check that repo only changes are tracked - tree.read_with(cx, |tree, _cx| { - let snapshot = tree.snapshot(); - let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - - assert_eq!(repo.statuses.iter().count(), 1); - assert_eq!( - repo.statuses.get(&Path::new(F_TXT).into()), - Some(&GitFileStatus::Added) - ); - }); - - git_reset(0, &repo); - git_remove_index(Path::new(B_TXT), &repo); - git_stash(&mut repo); - std::fs::write(work_dir.join(E_TXT), "eeee").unwrap(); - std::fs::write(work_dir.join(BUILD_FILE), "this should be ignored").unwrap(); - tree.flush_fs_events(cx).await; - - // Check that more complex repo changes are tracked - tree.read_with(cx, |tree, _cx| { - let snapshot = tree.snapshot(); - let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - - assert_eq!(repo.statuses.iter().count(), 3); - assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); - assert_eq!( - repo.statuses.get(&Path::new(B_TXT).into()), - Some(&GitFileStatus::Added) - ); - assert_eq!( - repo.statuses.get(&Path::new(E_TXT).into()), - Some(&GitFileStatus::Modified) - ); - assert_eq!( - repo.statuses.get(&Path::new(F_TXT).into()), - Some(&GitFileStatus::Added) - ); - }); - - std::fs::remove_file(work_dir.join(B_TXT)).unwrap(); - std::fs::remove_dir_all(work_dir.join("c")).unwrap(); - std::fs::write( - work_dir.join(DOTGITIGNORE), - [IGNORE_RULE, "f.txt"].join("\n"), - ) - .unwrap(); - - git_add(Path::new(DOTGITIGNORE), &repo); - git_commit("Committing modified git ignore", &repo); - - tree.flush_fs_events(cx).await; - - // Check that non-repo behavior is tracked - tree.read_with(cx, |tree, _cx| { - let snapshot = tree.snapshot(); - let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - - assert_eq!(repo.statuses.iter().count(), 0); - }); - - let mut renamed_dir_name = "first_directory/second_directory"; - const RENAMED_FILE: &'static str = "rf.txt"; - - std::fs::create_dir_all(work_dir.join(renamed_dir_name)).unwrap(); - std::fs::write( - work_dir.join(renamed_dir_name).join(RENAMED_FILE), - "new-contents", - ) - .unwrap(); - - tree.flush_fs_events(cx).await; - - tree.read_with(cx, |tree, _cx| { - let snapshot = tree.snapshot(); - let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - - assert_eq!(repo.statuses.iter().count(), 1); - assert_eq!( - repo.statuses - .get(&Path::new(renamed_dir_name).join(RENAMED_FILE).into()), - Some(&GitFileStatus::Added) - ); - }); - - renamed_dir_name = "new_first_directory/second_directory"; - - std::fs::rename( - work_dir.join("first_directory"), - work_dir.join("new_first_directory"), - ) - .unwrap(); - - tree.flush_fs_events(cx).await; - - tree.read_with(cx, |tree, _cx| { - let snapshot = tree.snapshot(); - let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); - - assert_eq!(repo.statuses.iter().count(), 1); - assert_eq!( - repo.statuses - .get(&Path::new(renamed_dir_name).join(RENAMED_FILE).into()), - Some(&GitFileStatus::Added) - ); - }); - } - #[gpui::test] async fn test_write_file(cx: &mut TestAppContext) { let dir = temp_tree(json!({ @@ -5100,4 +4723,474 @@ mod tests { paths } } + + mod git_tests { + use super::*; + use pretty_assertions::assert_eq; + + #[gpui::test] + async fn test_rename_work_directory(cx: &mut TestAppContext) { + let root = temp_tree(json!({ + "projects": { + "project1": { + "a": "", + "b": "", + } + }, + + })); + let root_path = root.path(); + + let http_client = FakeHttpClient::with_404_response(); + let client = cx.read(|cx| Client::new(http_client, cx)); + let tree = Worktree::local( + client, + root_path, + true, + Arc::new(RealFs), + Default::default(), + &mut cx.to_async(), + ) + .await + .unwrap(); + + let repo = git_init(&root_path.join("projects/project1")); + git_add("a", &repo); + git_commit("init", &repo); + std::fs::write(root_path.join("projects/project1/a"), "aa").ok(); + + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + + tree.flush_fs_events(cx).await; + + cx.read(|cx| { + let tree = tree.read(cx); + let (work_dir, repo) = tree.repositories().next().unwrap(); + assert_eq!(work_dir.as_ref(), Path::new("projects/project1")); + assert_eq!( + repo.status_for_file(tree, Path::new("projects/project1/a")), + Some(GitFileStatus::Modified) + ); + assert_eq!( + repo.status_for_file(tree, Path::new("projects/project1/b")), + Some(GitFileStatus::Added) + ); + }); + dbg!("RENAMING"); + std::fs::rename( + root_path.join("projects/project1"), + root_path.join("projects/project2"), + ) + .ok(); + tree.flush_fs_events(cx).await; + + cx.read(|cx| { + let tree = tree.read(cx); + let (work_dir, repo) = tree.repositories().next().unwrap(); + assert_eq!(work_dir.as_ref(), Path::new("projects/project2")); + assert_eq!( + repo.status_for_file(tree, Path::new("projects/project2/a")), + Some(GitFileStatus::Modified) + ); + assert_eq!( + repo.status_for_file(tree, Path::new("projects/project2/b")), + Some(GitFileStatus::Added) + ); + }); + } + + #[gpui::test] + async fn test_git_repository_for_path(cx: &mut TestAppContext) { + let root = temp_tree(json!({ + "c.txt": "", + "dir1": { + ".git": {}, + "deps": { + "dep1": { + ".git": {}, + "src": { + "a.txt": "" + } + } + }, + "src": { + "b.txt": "" + } + }, + })); + + let http_client = FakeHttpClient::with_404_response(); + let client = cx.read(|cx| Client::new(http_client, cx)); + let tree = Worktree::local( + client, + root.path(), + true, + Arc::new(RealFs), + Default::default(), + &mut cx.to_async(), + ) + .await + .unwrap(); + + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + tree.flush_fs_events(cx).await; + + tree.read_with(cx, |tree, _cx| { + let tree = tree.as_local().unwrap(); + + assert!(tree.repository_for_path("c.txt".as_ref()).is_none()); + + let entry = tree.repository_for_path("dir1/src/b.txt".as_ref()).unwrap(); + assert_eq!( + entry + .work_directory(tree) + .map(|directory| directory.as_ref().to_owned()), + Some(Path::new("dir1").to_owned()) + ); + + let entry = tree.repository_for_path("dir1/deps/dep1/src/a.txt".as_ref()).unwrap(); + assert_eq!( + entry + .work_directory(tree) + .map(|directory| directory.as_ref().to_owned()), + Some(Path::new("dir1/deps/dep1").to_owned()) + ); + + let entries = tree.files(false, 0); + + let paths_with_repos = tree + .entries_with_repositories(entries) + .map(|(entry, repo)| { + ( + entry.path.as_ref(), + repo.and_then(|repo| { + repo.work_directory(&tree) + .map(|work_directory| work_directory.0.to_path_buf()) + }), + ) + }) + .collect::>(); + + assert_eq!( + paths_with_repos, + &[ + (Path::new("c.txt"), None), + ( + Path::new("dir1/deps/dep1/src/a.txt"), + Some(Path::new("dir1/deps/dep1").into()) + ), + (Path::new("dir1/src/b.txt"), Some(Path::new("dir1").into())), + ] + ); + }); + + let repo_update_events = Arc::new(Mutex::new(vec![])); + tree.update(cx, |_, cx| { + let repo_update_events = repo_update_events.clone(); + cx.subscribe(&tree, move |_, _, event, _| { + if let Event::UpdatedGitRepositories(update) = event { + repo_update_events.lock().push(update.clone()); + } + }) + .detach(); + }); + + std::fs::write(root.path().join("dir1/.git/random_new_file"), "hello").unwrap(); + tree.flush_fs_events(cx).await; + + assert_eq!( + repo_update_events.lock()[0] + .keys() + .cloned() + .collect::>>(), + vec![Path::new("dir1").into()] + ); + + std::fs::remove_dir_all(root.path().join("dir1/.git")).unwrap(); + tree.flush_fs_events(cx).await; + + tree.read_with(cx, |tree, _cx| { + let tree = tree.as_local().unwrap(); + + assert!(tree.repository_for_path("dir1/src/b.txt".as_ref()).is_none()); + }); + } + + #[gpui::test] + async fn test_git_status(cx: &mut TestAppContext) { + const IGNORE_RULE: &'static str = "**/target"; + + let root = temp_tree(json!({ + "project": { + "a.txt": "a", + "b.txt": "bb", + "c": { + "d": { + "e.txt": "eee" + } + }, + "f.txt": "ffff", + "target": { + "build_file": "???" + }, + ".gitignore": IGNORE_RULE + }, + + })); + + let http_client = FakeHttpClient::with_404_response(); + let client = cx.read(|cx| Client::new(http_client, cx)); + let tree = Worktree::local( + client, + root.path(), + true, + Arc::new(RealFs), + Default::default(), + &mut cx.to_async(), + ) + .await + .unwrap(); + + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + + const A_TXT: &'static str = "a.txt"; + const B_TXT: &'static str = "b.txt"; + const E_TXT: &'static str = "c/d/e.txt"; + const F_TXT: &'static str = "f.txt"; + const DOTGITIGNORE: &'static str = ".gitignore"; + const BUILD_FILE: &'static str = "target/build_file"; + + let work_dir = root.path().join("project"); + let mut repo = git_init(work_dir.as_path()); + repo.add_ignore_rule(IGNORE_RULE).unwrap(); + git_add(Path::new(A_TXT), &repo); + git_add(Path::new(E_TXT), &repo); + git_add(Path::new(DOTGITIGNORE), &repo); + git_commit("Initial commit", &repo); + + std::fs::write(work_dir.join(A_TXT), "aa").unwrap(); + + tree.flush_fs_events(cx).await; + + // Check that the right git state is observed on startup + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + assert_eq!(snapshot.repository_entries.iter().count(), 1); + let (dir, repo) = snapshot.repository_entries.iter().next().unwrap(); + assert_eq!(dir.0.as_ref(), Path::new("project")); + + assert_eq!(repo.statuses.iter().count(), 3); + assert_eq!( + repo.statuses.get(&Path::new(A_TXT).into()), + Some(&GitFileStatus::Modified) + ); + assert_eq!( + repo.statuses.get(&Path::new(B_TXT).into()), + Some(&GitFileStatus::Added) + ); + assert_eq!( + repo.statuses.get(&Path::new(F_TXT).into()), + Some(&GitFileStatus::Added) + ); + }); + + git_add(Path::new(A_TXT), &repo); + git_add(Path::new(B_TXT), &repo); + git_commit("Committing modified and added", &repo); + tree.flush_fs_events(cx).await; + + // Check that repo only changes are tracked + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + + assert_eq!(repo.statuses.iter().count(), 1); + assert_eq!( + repo.statuses.get(&Path::new(F_TXT).into()), + Some(&GitFileStatus::Added) + ); + }); + + git_reset(0, &repo); + git_remove_index(Path::new(B_TXT), &repo); + git_stash(&mut repo); + std::fs::write(work_dir.join(E_TXT), "eeee").unwrap(); + std::fs::write(work_dir.join(BUILD_FILE), "this should be ignored").unwrap(); + tree.flush_fs_events(cx).await; + + // Check that more complex repo changes are tracked + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + + assert_eq!(repo.statuses.iter().count(), 3); + assert_eq!(repo.statuses.get(&Path::new(A_TXT).into()), None); + assert_eq!( + repo.statuses.get(&Path::new(B_TXT).into()), + Some(&GitFileStatus::Added) + ); + assert_eq!( + repo.statuses.get(&Path::new(E_TXT).into()), + Some(&GitFileStatus::Modified) + ); + assert_eq!( + repo.statuses.get(&Path::new(F_TXT).into()), + Some(&GitFileStatus::Added) + ); + }); + + std::fs::remove_file(work_dir.join(B_TXT)).unwrap(); + std::fs::remove_dir_all(work_dir.join("c")).unwrap(); + std::fs::write( + work_dir.join(DOTGITIGNORE), + [IGNORE_RULE, "f.txt"].join("\n"), + ) + .unwrap(); + + git_add(Path::new(DOTGITIGNORE), &repo); + git_commit("Committing modified git ignore", &repo); + + tree.flush_fs_events(cx).await; + + // Check that non-repo behavior is tracked + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + + assert_eq!(repo.statuses.iter().count(), 0); + }); + + let mut renamed_dir_name = "first_directory/second_directory"; + const RENAMED_FILE: &'static str = "rf.txt"; + + std::fs::create_dir_all(work_dir.join(renamed_dir_name)).unwrap(); + std::fs::write( + work_dir.join(renamed_dir_name).join(RENAMED_FILE), + "new-contents", + ) + .unwrap(); + + tree.flush_fs_events(cx).await; + + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + + assert_eq!(repo.statuses.iter().count(), 1); + assert_eq!( + repo.statuses + .get(&Path::new(renamed_dir_name).join(RENAMED_FILE).into()), + Some(&GitFileStatus::Added) + ); + }); + + renamed_dir_name = "new_first_directory/second_directory"; + + std::fs::rename( + work_dir.join("first_directory"), + work_dir.join("new_first_directory"), + ) + .unwrap(); + + tree.flush_fs_events(cx).await; + + tree.read_with(cx, |tree, _cx| { + let snapshot = tree.snapshot(); + let (_, repo) = snapshot.repository_entries.iter().next().unwrap(); + + assert_eq!(repo.statuses.iter().count(), 1); + assert_eq!( + repo.statuses + .get(&Path::new(renamed_dir_name).join(RENAMED_FILE).into()), + Some(&GitFileStatus::Added) + ); + }); + } + + #[track_caller] + fn git_init(path: &Path) -> git2::Repository { + git2::Repository::init(path).expect("Failed to initialize git repository") + } + + #[track_caller] + fn git_add>(path: P, repo: &git2::Repository) { + let path = path.as_ref(); + let mut index = repo.index().expect("Failed to get index"); + index.add_path(path).expect("Failed to add a.txt"); + index.write().expect("Failed to write index"); + } + + #[track_caller] + fn git_remove_index(path: &Path, repo: &git2::Repository) { + let mut index = repo.index().expect("Failed to get index"); + index.remove_path(path).expect("Failed to add a.txt"); + index.write().expect("Failed to write index"); + } + + #[track_caller] + fn git_commit(msg: &'static str, repo: &git2::Repository) { + use git2::Signature; + + let signature = Signature::now("test", "test@zed.dev").unwrap(); + let oid = repo.index().unwrap().write_tree().unwrap(); + let tree = repo.find_tree(oid).unwrap(); + if let Some(head) = repo.head().ok() { + let parent_obj = head.peel(git2::ObjectType::Commit).unwrap(); + + let parent_commit = parent_obj.as_commit().unwrap(); + + repo.commit( + Some("HEAD"), + &signature, + &signature, + msg, + &tree, + &[parent_commit], + ) + .expect("Failed to commit with parent"); + } else { + repo.commit(Some("HEAD"), &signature, &signature, msg, &tree, &[]) + .expect("Failed to commit"); + } + } + + #[track_caller] + fn git_stash(repo: &mut git2::Repository) { + use git2::Signature; + + let signature = Signature::now("test", "test@zed.dev").unwrap(); + repo.stash_save(&signature, "N/A", None) + .expect("Failed to stash"); + } + + #[track_caller] + fn git_reset(offset: usize, repo: &git2::Repository) { + let head = repo.head().expect("Couldn't get repo head"); + let object = head.peel(git2::ObjectType::Commit).unwrap(); + let commit = object.as_commit().unwrap(); + let new_head = commit + .parents() + .inspect(|parnet| { + parnet.message(); + }) + .skip(offset) + .next() + .expect("Not enough history"); + repo.reset(&new_head.as_object(), git2::ResetType::Soft, None) + .expect("Could not reset"); + } + + #[allow(dead_code)] + #[track_caller] + fn git_status(repo: &git2::Repository) -> HashMap { + repo.statuses(None) + .unwrap() + .iter() + .map(|status| (status.path().unwrap().to_string(), status.status())) + .collect() + } + } } From 14744292719a29ca87ad0eaf466c0a81579cb0a5 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 16:32:07 -0700 Subject: [PATCH 163/168] fmt --- crates/project/src/worktree.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index b4f188a2c3..4d6853d2ee 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1641,7 +1641,9 @@ impl Snapshot { /// Get the repository whose work directory contains the given path. pub fn repository_for_work_directory(&self, path: &Path) -> Option { - self.repository_entries.get(&RepositoryWorkDirectory(path.into())).cloned() + self.repository_entries + .get(&RepositoryWorkDirectory(path.into())) + .cloned() } /// Get the repository whose work directory contains the given path. @@ -4850,7 +4852,9 @@ mod tests { Some(Path::new("dir1").to_owned()) ); - let entry = tree.repository_for_path("dir1/deps/dep1/src/a.txt".as_ref()).unwrap(); + let entry = tree + .repository_for_path("dir1/deps/dep1/src/a.txt".as_ref()) + .unwrap(); assert_eq!( entry .work_directory(tree) @@ -4914,7 +4918,9 @@ mod tests { tree.read_with(cx, |tree, _cx| { let tree = tree.as_local().unwrap(); - assert!(tree.repository_for_path("dir1/src/b.txt".as_ref()).is_none()); + assert!(tree + .repository_for_path("dir1/src/b.txt".as_ref()) + .is_none()); }); } From 43e301eeef57ac731556ea81b84f03967afb0205 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 16:52:57 -0700 Subject: [PATCH 164/168] refine batched anchor conversions co-authored-by: max --- crates/git/src/diff.rs | 34 +++++++++++++++++----------------- crates/project/src/project.rs | 2 -- crates/text/src/text.rs | 17 +++++++++++++---- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/crates/git/src/diff.rs b/crates/git/src/diff.rs index a2349649c5..09a0d930c5 100644 --- a/crates/git/src/diff.rs +++ b/crates/git/src/diff.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, iter, ops::Range}; +use std::{iter, ops::Range}; use sum_tree::SumTree; use text::{Anchor, BufferSnapshot, Point}; @@ -94,9 +94,6 @@ impl BufferDiff { !before_start && !after_end }); - use std::rc::Rc; - let cell = Rc::new(RefCell::new(None)); - let anchor_iter = std::iter::from_fn(move || { if reversed { cursor.prev(buffer); @@ -106,25 +103,28 @@ impl BufferDiff { cursor.item() }) - .flat_map({ - let cell = cell.clone(); - move |hunk| { - *cell.borrow_mut() = Some(hunk.diff_base_byte_range.clone()); - iter::once(&hunk.buffer_range.start).chain(iter::once(&hunk.buffer_range.end)) - } + .flat_map(move |hunk| { + [ + (&hunk.buffer_range.start, hunk.diff_base_byte_range.start), + (&hunk.buffer_range.end, hunk.diff_base_byte_range.end), + ] + .into_iter() }); - let mut summaries = buffer.summaries_for_anchors::(anchor_iter); + let mut summaries = buffer.summaries_for_anchors_with_payload::(anchor_iter); iter::from_fn(move || { - let start = summaries.next()?; - let end = summaries.next()?; - let base = (cell.borrow_mut()).clone()?; + let (start_point, start_base) = summaries.next()?; + let (end_point, end_base) = summaries.next()?; - let end_row = if end.column > 0 { end.row + 1 } else { end.row }; + let end_row = if end_point.column > 0 { + end_point.row + 1 + } else { + end_point.row + }; Some(DiffHunk { - buffer_range: start.row..end_row, - diff_base_byte_range: base, + buffer_range: start_point.row..end_row, + diff_base_byte_range: start_base..end_base, }) }) } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 15741a27f1..f91cd999f9 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2870,10 +2870,8 @@ impl Project { if let Some(LanguageServerState::Running { watched_paths, .. }) = self.language_servers.get_mut(&language_server_id) { - eprintln!("change watch"); let mut builders = HashMap::default(); for watcher in params.watchers { - eprintln!(" {}", watcher.glob_pattern); for worktree in &self.worktrees { if let Some(worktree) = worktree.upgrade(cx) { let worktree = worktree.read(cx); diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 86bb7f4a26..278fd058fe 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1783,6 +1783,15 @@ impl BufferSnapshot { where D: 'a + TextDimension, A: 'a + IntoIterator, + { + let anchors = anchors.into_iter(); + self.summaries_for_anchors_with_payload::(anchors.map(|a| (a, ()))).map(|d| d.0) + } + + pub fn summaries_for_anchors_with_payload<'a, D, A, T>(&'a self, anchors: A) -> impl 'a + Iterator + where + D: 'a + TextDimension, + A: 'a + IntoIterator, { let anchors = anchors.into_iter(); let mut insertion_cursor = self.insertions.cursor::(); @@ -1790,11 +1799,11 @@ impl BufferSnapshot { let mut text_cursor = self.visible_text.cursor(0); let mut position = D::default(); - anchors.map(move |anchor| { + anchors.map(move |(anchor, payload)| { if *anchor == Anchor::MIN { - return D::default(); + return (D::default(), payload); } else if *anchor == Anchor::MAX { - return D::from_text_summary(&self.visible_text.summary()); + return (D::from_text_summary(&self.visible_text.summary()), payload); } let anchor_key = InsertionFragmentKey { @@ -1825,7 +1834,7 @@ impl BufferSnapshot { } position.add_assign(&text_cursor.summary(fragment_offset)); - position.clone() + (position.clone(), payload) }) } From bbb68c523cc4bfac234e08015cf2fa6673500f6b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 19 May 2023 18:09:47 -0700 Subject: [PATCH 165/168] Refactored apart the forward and the backwards iterator for diff hunks --- crates/collab/src/tests/integration_tests.rs | 18 +-- crates/editor/src/editor.rs | 132 +++++++++++------- crates/editor/src/element.rs | 4 +- crates/editor/src/multi_buffer.rs | 85 ++++++++--- crates/editor/src/test/editor_test_context.rs | 4 + crates/git/src/diff.rs | 47 +++++-- crates/language/src/buffer.rs | 14 +- crates/project/src/worktree.rs | 2 +- crates/text/src/text.rs | 8 +- 9 files changed, 213 insertions(+), 101 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index d0625066d5..807510d705 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2437,7 +2437,7 @@ async fn test_git_diff_base_change( buffer_local_a.read_with(cx_a, |buffer, _| { assert_eq!(buffer.diff_base(), Some(diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(1..2, "", "two\n")], @@ -2457,7 +2457,7 @@ async fn test_git_diff_base_change( buffer_remote_a.read_with(cx_b, |buffer, _| { assert_eq!(buffer.diff_base(), Some(diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(1..2, "", "two\n")], @@ -2481,7 +2481,7 @@ async fn test_git_diff_base_change( assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(2..3, "", "three\n")], @@ -2492,7 +2492,7 @@ async fn test_git_diff_base_change( buffer_remote_a.read_with(cx_b, |buffer, _| { assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(2..3, "", "three\n")], @@ -2535,7 +2535,7 @@ async fn test_git_diff_base_change( buffer_local_b.read_with(cx_a, |buffer, _| { assert_eq!(buffer.diff_base(), Some(diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(1..2, "", "two\n")], @@ -2555,7 +2555,7 @@ async fn test_git_diff_base_change( buffer_remote_b.read_with(cx_b, |buffer, _| { assert_eq!(buffer.diff_base(), Some(diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(1..2, "", "two\n")], @@ -2583,12 +2583,12 @@ async fn test_git_diff_base_change( "{:?}", buffer .snapshot() - .git_diff_hunks_in_row_range(0..4, false) + .git_diff_hunks_in_row_range(0..4) .collect::>() ); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(2..3, "", "three\n")], @@ -2599,7 +2599,7 @@ async fn test_git_diff_base_change( buffer_remote_b.read_with(cx_b, |buffer, _| { assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref())); git::diff::assert_hunks( - buffer.snapshot().git_diff_hunks_in_row_range(0..4, false), + buffer.snapshot().git_diff_hunks_in_row_range(0..4), &buffer, &diff_base, &[(2..3, "", "three\n")], diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0fb7a10a16..7207e3c91c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -20,6 +20,7 @@ mod editor_tests; #[cfg(any(test, feature = "test-support"))] pub mod test; +use ::git::diff::DiffHunk; use aho_corasick::AhoCorasick; use anyhow::{anyhow, Result}; use blink_manager::BlinkManager; @@ -527,7 +528,7 @@ pub struct EditorSnapshot { impl EditorSnapshot { fn has_scrollbar_info(&self) -> bool { self.buffer_snapshot - .git_diff_hunks_in_range(0..self.max_point().row(), false) + .git_diff_hunks_in_range(0..self.max_point().row()) .next() .is_some() } @@ -5569,68 +5570,91 @@ impl Editor { } fn go_to_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext) { - self.go_to_hunk_impl(Direction::Next, cx) - } - - fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext) { - self.go_to_hunk_impl(Direction::Prev, cx) - } - - pub fn go_to_hunk_impl(&mut self, direction: Direction, cx: &mut ViewContext) { let snapshot = self .display_map .update(cx, |display_map, cx| display_map.snapshot(cx)); let selection = self.selections.newest::(cx); - fn seek_in_direction( - this: &mut Editor, - snapshot: &DisplaySnapshot, - initial_point: Point, - is_wrapped: bool, - direction: Direction, - cx: &mut ViewContext, - ) -> bool { - let hunks = if direction == Direction::Next { + if !self.seek_in_direction( + &snapshot, + selection.head(), + false, + snapshot + .buffer_snapshot + .git_diff_hunks_in_range((selection.head().row + 1)..u32::MAX), + cx, + ) { + let wrapped_point = Point::zero(); + self.seek_in_direction( + &snapshot, + wrapped_point, + true, snapshot .buffer_snapshot - .git_diff_hunks_in_range(initial_point.row..u32::MAX, false) - } else { - snapshot - .buffer_snapshot - .git_diff_hunks_in_range(0..initial_point.row, true) - }; - - let display_point = initial_point.to_display_point(snapshot); - let mut hunks = hunks - .map(|hunk| diff_hunk_to_display(hunk, &snapshot)) - .skip_while(|hunk| { - if is_wrapped { - false - } else { - hunk.contains_display_row(display_point.row()) - } - }) - .dedup(); - - if let Some(hunk) = hunks.next() { - this.change_selections(Some(Autoscroll::fit()), cx, |s| { - let row = hunk.start_display_row(); - let point = DisplayPoint::new(row, 0); - s.select_display_ranges([point..point]); - }); - - true - } else { - false - } + .git_diff_hunks_in_range((wrapped_point.row + 1)..u32::MAX), + cx, + ); } + } - if !seek_in_direction(self, &snapshot, selection.head(), false, direction, cx) { - let wrapped_point = match direction { - Direction::Next => Point::zero(), - Direction::Prev => snapshot.buffer_snapshot.max_point(), - }; - seek_in_direction(self, &snapshot, wrapped_point, true, direction, cx); + fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext) { + let snapshot = self + .display_map + .update(cx, |display_map, cx| display_map.snapshot(cx)); + let selection = self.selections.newest::(cx); + + if !self.seek_in_direction( + &snapshot, + selection.head(), + false, + snapshot + .buffer_snapshot + .git_diff_hunks_in_range_rev(0..selection.head().row), + cx, + ) { + let wrapped_point = snapshot.buffer_snapshot.max_point(); + self.seek_in_direction( + &snapshot, + wrapped_point, + true, + snapshot + .buffer_snapshot + .git_diff_hunks_in_range_rev(0..wrapped_point.row), + cx, + ); + } + } + + fn seek_in_direction( + &mut self, + snapshot: &DisplaySnapshot, + initial_point: Point, + is_wrapped: bool, + hunks: impl Iterator>, + cx: &mut ViewContext, + ) -> bool { + let display_point = initial_point.to_display_point(snapshot); + let mut hunks = hunks + .map(|hunk| diff_hunk_to_display(hunk, &snapshot)) + .skip_while(|hunk| { + if is_wrapped { + false + } else { + hunk.contains_display_row(display_point.row()) + } + }) + .dedup(); + + if let Some(hunk) = hunks.next() { + self.change_selections(Some(Autoscroll::fit()), cx, |s| { + let row = hunk.start_display_row(); + let point = DisplayPoint::new(row, 0); + s.select_display_ranges([point..point]); + }); + + true + } else { + false } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7285db7366..57dc3293f6 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1057,7 +1057,7 @@ impl EditorElement { .position_map .snapshot .buffer_snapshot - .git_diff_hunks_in_range(0..(max_row.floor() as u32), false) + .git_diff_hunks_in_range(0..(max_row.floor() as u32)) { let start_display = Point::new(hunk.buffer_range.start, 0) .to_display_point(&layout.position_map.snapshot.display_snapshot); @@ -1274,7 +1274,7 @@ impl EditorElement { .row; buffer_snapshot - .git_diff_hunks_in_range(buffer_start_row..buffer_end_row, false) + .git_diff_hunks_in_range(buffer_start_row..buffer_end_row) .map(|hunk| diff_hunk_to_display(hunk, snapshot)) .dedup() .collect() diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index f3e8fd7440..1423473e1a 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2841,20 +2841,15 @@ impl MultiBufferSnapshot { }) } - pub fn git_diff_hunks_in_range<'a>( + pub fn git_diff_hunks_in_range_rev<'a>( &'a self, row_range: Range, - reversed: bool, ) -> impl 'a + Iterator> { let mut cursor = self.excerpts.cursor::(); - if reversed { - cursor.seek(&Point::new(row_range.end, 0), Bias::Left, &()); - if cursor.item().is_none() { - cursor.prev(&()); - } - } else { - cursor.seek(&Point::new(row_range.start, 0), Bias::Right, &()); + cursor.seek(&Point::new(row_range.end, 0), Bias::Left, &()); + if cursor.item().is_none() { + cursor.prev(&()); } std::iter::from_fn(move || { @@ -2884,7 +2879,7 @@ impl MultiBufferSnapshot { let buffer_hunks = excerpt .buffer - .git_diff_hunks_intersecting_range(buffer_start..buffer_end, reversed) + .git_diff_hunks_intersecting_range_rev(buffer_start..buffer_end) .filter_map(move |hunk| { let start = multibuffer_start.row + hunk @@ -2904,12 +2899,70 @@ impl MultiBufferSnapshot { }) }); - if reversed { - cursor.prev(&()); - } else { - cursor.next(&()); + cursor.prev(&()); + + Some(buffer_hunks) + }) + .flatten() + } + + pub fn git_diff_hunks_in_range<'a>( + &'a self, + row_range: Range, + ) -> impl 'a + Iterator> { + let mut cursor = self.excerpts.cursor::(); + + cursor.seek(&Point::new(row_range.start, 0), Bias::Right, &()); + + std::iter::from_fn(move || { + let excerpt = cursor.item()?; + let multibuffer_start = *cursor.start(); + let multibuffer_end = multibuffer_start + excerpt.text_summary.lines; + if multibuffer_start.row >= row_range.end { + return None; } + let mut buffer_start = excerpt.range.context.start; + let mut buffer_end = excerpt.range.context.end; + let excerpt_start_point = buffer_start.to_point(&excerpt.buffer); + let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines; + + if row_range.start > multibuffer_start.row { + let buffer_start_point = + excerpt_start_point + Point::new(row_range.start - multibuffer_start.row, 0); + buffer_start = excerpt.buffer.anchor_before(buffer_start_point); + } + + if row_range.end < multibuffer_end.row { + let buffer_end_point = + excerpt_start_point + Point::new(row_range.end - multibuffer_start.row, 0); + buffer_end = excerpt.buffer.anchor_before(buffer_end_point); + } + + let buffer_hunks = excerpt + .buffer + .git_diff_hunks_intersecting_range(buffer_start..buffer_end) + .filter_map(move |hunk| { + let start = multibuffer_start.row + + hunk + .buffer_range + .start + .saturating_sub(excerpt_start_point.row); + let end = multibuffer_start.row + + hunk + .buffer_range + .end + .min(excerpt_end_point.row + 1) + .saturating_sub(excerpt_start_point.row); + + Some(DiffHunk { + buffer_range: start..end, + diff_base_byte_range: hunk.diff_base_byte_range.clone(), + }) + }); + + cursor.next(&()); + Some(buffer_hunks) }) .flatten() @@ -4647,7 +4700,7 @@ mod tests { assert_eq!( snapshot - .git_diff_hunks_in_range(0..12, false) + .git_diff_hunks_in_range(0..12) .map(|hunk| (hunk.status(), hunk.buffer_range)) .collect::>(), &expected, @@ -4655,7 +4708,7 @@ mod tests { assert_eq!( snapshot - .git_diff_hunks_in_range(0..12, true) + .git_diff_hunks_in_range_rev(0..12) .map(|hunk| (hunk.status(), hunk.buffer_range)) .collect::>(), expected diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index ced99a3f23..e520562ebb 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -204,6 +204,7 @@ impl<'a> EditorTestContext<'a> { self.assert_selections(expected_selections, marked_text.to_string()) } + #[track_caller] pub fn assert_editor_background_highlights(&mut self, marked_text: &str) { let expected_ranges = self.ranges(marked_text); let actual_ranges: Vec> = self.update_editor(|editor, cx| { @@ -220,6 +221,7 @@ impl<'a> EditorTestContext<'a> { assert_set_eq!(actual_ranges, expected_ranges); } + #[track_caller] pub fn assert_editor_text_highlights(&mut self, marked_text: &str) { let expected_ranges = self.ranges(marked_text); let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx)); @@ -233,12 +235,14 @@ impl<'a> EditorTestContext<'a> { assert_set_eq!(actual_ranges, expected_ranges); } + #[track_caller] pub fn assert_editor_selections(&mut self, expected_selections: Vec>) { let expected_marked_text = generate_marked_text(&self.buffer_text(), &expected_selections, true); self.assert_selections(expected_selections, expected_marked_text) } + #[track_caller] fn assert_selections( &mut self, expected_selections: Vec>, diff --git a/crates/git/src/diff.rs b/crates/git/src/diff.rs index 09a0d930c5..8704f85005 100644 --- a/crates/git/src/diff.rs +++ b/crates/git/src/diff.rs @@ -1,6 +1,6 @@ use std::{iter, ops::Range}; use sum_tree::SumTree; -use text::{Anchor, BufferSnapshot, Point}; +use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point}; pub use git2 as libgit; use libgit::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch}; @@ -75,18 +75,17 @@ impl BufferDiff { &'a self, range: Range, buffer: &'a BufferSnapshot, - reversed: bool, ) -> impl 'a + Iterator> { let start = buffer.anchor_before(Point::new(range.start, 0)); let end = buffer.anchor_after(Point::new(range.end, 0)); - self.hunks_intersecting_range(start..end, buffer, reversed) + + self.hunks_intersecting_range(start..end, buffer) } pub fn hunks_intersecting_range<'a>( &'a self, range: Range, buffer: &'a BufferSnapshot, - reversed: bool, ) -> impl 'a + Iterator> { let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| { let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt(); @@ -95,12 +94,7 @@ impl BufferDiff { }); let anchor_iter = std::iter::from_fn(move || { - if reversed { - cursor.prev(buffer); - } else { - cursor.next(buffer); - } - + cursor.next(buffer); cursor.item() }) .flat_map(move |hunk| { @@ -129,6 +123,35 @@ impl BufferDiff { }) } + pub fn hunks_intersecting_range_rev<'a>( + &'a self, + range: Range, + buffer: &'a BufferSnapshot, + ) -> impl 'a + Iterator> { + let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| { + let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt(); + let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt(); + !before_start && !after_end + }); + + std::iter::from_fn(move || { + cursor.prev(buffer); + + let hunk = cursor.item()?; + let range = hunk.buffer_range.to_point(buffer); + let end_row = if range.end.column > 0 { + range.end.row + 1 + } else { + range.end.row + }; + + Some(DiffHunk { + buffer_range: range.start.row..end_row, + diff_base_byte_range: hunk.diff_base_byte_range.clone(), + }) + }) + } + pub fn clear(&mut self, buffer: &text::BufferSnapshot) { self.last_buffer_version = Some(buffer.version().clone()); self.tree = SumTree::new(); @@ -163,7 +186,7 @@ impl BufferDiff { fn hunks<'a>(&'a self, text: &'a BufferSnapshot) -> impl 'a + Iterator> { let start = text.anchor_before(Point::new(0, 0)); let end = text.anchor_after(Point::new(u32::MAX, u32::MAX)); - self.hunks_intersecting_range(start..end, text, false) + self.hunks_intersecting_range(start..end, text) } fn diff<'a>(head: &'a str, current: &'a str) -> Option> { @@ -379,7 +402,7 @@ mod tests { assert_eq!(diff.hunks(&buffer).count(), 8); assert_hunks( - diff.hunks_in_row_range(7..12, &buffer, false), + diff.hunks_in_row_range(7..12, &buffer), &buffer, &diff_base, &[ diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index aee646091a..3a97702487 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2509,18 +2509,22 @@ impl BufferSnapshot { pub fn git_diff_hunks_in_row_range<'a>( &'a self, range: Range, - reversed: bool, ) -> impl 'a + Iterator> { - self.git_diff.hunks_in_row_range(range, self, reversed) + self.git_diff.hunks_in_row_range(range, self) } pub fn git_diff_hunks_intersecting_range<'a>( &'a self, range: Range, - reversed: bool, ) -> impl 'a + Iterator> { - self.git_diff - .hunks_intersecting_range(range, self, reversed) + self.git_diff.hunks_intersecting_range(range, self) + } + + pub fn git_diff_hunks_intersecting_range_rev<'a>( + &'a self, + range: Range, + ) -> impl 'a + Iterator> { + self.git_diff.hunks_intersecting_range_rev(range, self) } pub fn diagnostics_in_range<'a, T, O>( diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 4d6853d2ee..4f898aa91d 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -4779,7 +4779,7 @@ mod tests { Some(GitFileStatus::Added) ); }); - dbg!("RENAMING"); + std::fs::rename( root_path.join("projects/project1"), root_path.join("projects/project2"), diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 278fd058fe..dcfaf818d1 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1785,10 +1785,14 @@ impl BufferSnapshot { A: 'a + IntoIterator, { let anchors = anchors.into_iter(); - self.summaries_for_anchors_with_payload::(anchors.map(|a| (a, ()))).map(|d| d.0) + self.summaries_for_anchors_with_payload::(anchors.map(|a| (a, ()))) + .map(|d| d.0) } - pub fn summaries_for_anchors_with_payload<'a, D, A, T>(&'a self, anchors: A) -> impl 'a + Iterator + pub fn summaries_for_anchors_with_payload<'a, D, A, T>( + &'a self, + anchors: A, + ) -> impl 'a + Iterator where D: 'a + TextDimension, A: 'a + IntoIterator, From 986eafd84e59f7dbf28ac1dc78f6a896a40a5fad Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 20 May 2023 09:54:38 -0600 Subject: [PATCH 166/168] Enable test-support on editor in tests --- crates/activity_indicator/Cargo.toml | 3 +++ crates/copilot_button/Cargo.toml | 3 +++ crates/feedback/Cargo.toml | 3 +++ crates/file_finder/Cargo.toml | 1 + crates/go_to_line/Cargo.toml | 3 +++ crates/journal/Cargo.toml | 3 +++ crates/language_selector/Cargo.toml | 3 +++ crates/lsp_log/Cargo.toml | 1 + crates/outline/Cargo.toml | 3 +++ crates/picker/Cargo.toml | 1 + crates/project_symbols/Cargo.toml | 1 + crates/recent_projects/Cargo.toml | 3 +++ crates/terminal_view/Cargo.toml | 1 + crates/theme_selector/Cargo.toml | 3 +++ crates/welcome/Cargo.toml | 3 +++ 15 files changed, 35 insertions(+) diff --git a/crates/activity_indicator/Cargo.toml b/crates/activity_indicator/Cargo.toml index 917383234a..43d16e6b9b 100644 --- a/crates/activity_indicator/Cargo.toml +++ b/crates/activity_indicator/Cargo.toml @@ -21,3 +21,6 @@ workspace = { path = "../workspace" } futures.workspace = true smallvec.workspace = true + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/copilot_button/Cargo.toml b/crates/copilot_button/Cargo.toml index ad3febd68c..50fbaa64ee 100644 --- a/crates/copilot_button/Cargo.toml +++ b/crates/copilot_button/Cargo.toml @@ -23,3 +23,6 @@ workspace = { path = "../workspace" } anyhow.workspace = true smol.workspace = true futures.workspace = true + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index e74e14ff4c..ddd6ab0009 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -35,3 +35,6 @@ serde_derive.workspace = true sysinfo = "0.27.1" tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } urlencoding = "2.1.2" + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/file_finder/Cargo.toml b/crates/file_finder/Cargo.toml index cae3fa25ca..6f6be7427b 100644 --- a/crates/file_finder/Cargo.toml +++ b/crates/file_finder/Cargo.toml @@ -23,6 +23,7 @@ workspace = { path = "../workspace" } postage.workspace = true [dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } language = { path = "../language", features = ["test-support"] } workspace = { path = "../workspace", features = ["test-support"] } diff --git a/crates/go_to_line/Cargo.toml b/crates/go_to_line/Cargo.toml index 441f7ef7e4..b32b4aaf13 100644 --- a/crates/go_to_line/Cargo.toml +++ b/crates/go_to_line/Cargo.toml @@ -18,3 +18,6 @@ workspace = { path = "../workspace" } postage.workspace = true theme = { path = "../theme" } util = { path = "../util" } + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/journal/Cargo.toml b/crates/journal/Cargo.toml index c1d9bde89e..b7cbc62559 100644 --- a/crates/journal/Cargo.toml +++ b/crates/journal/Cargo.toml @@ -22,3 +22,6 @@ serde.workspace = true schemars.workspace = true log.workspace = true shellexpand = "2.1.0" + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/language_selector/Cargo.toml b/crates/language_selector/Cargo.toml index e7b3d8d4be..f6e213f25f 100644 --- a/crates/language_selector/Cargo.toml +++ b/crates/language_selector/Cargo.toml @@ -20,3 +20,6 @@ settings = { path = "../settings" } util = { path = "../util" } workspace = { path = "../workspace" } anyhow.workspace = true + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/lsp_log/Cargo.toml b/crates/lsp_log/Cargo.toml index 8741f0a4cf..6f47057b44 100644 --- a/crates/lsp_log/Cargo.toml +++ b/crates/lsp_log/Cargo.toml @@ -24,6 +24,7 @@ serde.workspace = true anyhow.workspace = true [dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } unindent.workspace = true diff --git a/crates/outline/Cargo.toml b/crates/outline/Cargo.toml index 95272b063e..f4e2b849fa 100644 --- a/crates/outline/Cargo.toml +++ b/crates/outline/Cargo.toml @@ -22,3 +22,6 @@ workspace = { path = "../workspace" } ordered-float.workspace = true postage.workspace = true smol.workspace = true + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/picker/Cargo.toml b/crates/picker/Cargo.toml index b723cd788c..54e4b15ad5 100644 --- a/crates/picker/Cargo.toml +++ b/crates/picker/Cargo.toml @@ -20,6 +20,7 @@ workspace = { path = "../workspace" } parking_lot.workspace = true [dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } serde_json.workspace = true workspace = { path = "../workspace", features = ["test-support"] } diff --git a/crates/project_symbols/Cargo.toml b/crates/project_symbols/Cargo.toml index 7e23e42b26..85939634ad 100644 --- a/crates/project_symbols/Cargo.toml +++ b/crates/project_symbols/Cargo.toml @@ -27,6 +27,7 @@ smol.workspace = true [dev-dependencies] futures.workspace = true +editor = { path = "../editor", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } language = { path = "../language", features = ["test-support"] } diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index d9e7546f34..14f8853c9c 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -24,3 +24,6 @@ workspace = { path = "../workspace" } ordered-float.workspace = true postage.workspace = true smol.workspace = true + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/terminal_view/Cargo.toml b/crates/terminal_view/Cargo.toml index 3a25317870..a42d6c550e 100644 --- a/crates/terminal_view/Cargo.toml +++ b/crates/terminal_view/Cargo.toml @@ -39,6 +39,7 @@ serde_derive.workspace = true [dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } client = { path = "../client", features = ["test-support"]} project = { path = "../project", features = ["test-support"]} diff --git a/crates/theme_selector/Cargo.toml b/crates/theme_selector/Cargo.toml index ac3a85d89a..377f64aad6 100644 --- a/crates/theme_selector/Cargo.toml +++ b/crates/theme_selector/Cargo.toml @@ -23,3 +23,6 @@ log.workspace = true parking_lot.workspace = true postage.workspace = true smol.workspace = true + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index 65f5151584..ea01f822a7 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -30,3 +30,6 @@ anyhow.workspace = true log.workspace = true schemars.workspace = true serde.workspace = true + +[dev-dependencies] +editor = { path = "../editor", features = ["test-support"] } From c701901c7b808b9f621bdfe5c60ed83bc6e33d0e Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Sun, 21 May 2023 23:38:01 -0400 Subject: [PATCH 167/168] Fix get preview channel changes script --- script/get-preview-channel-changes | 69 ++++++++++++++++-------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/script/get-preview-channel-changes b/script/get-preview-channel-changes index ac1dcb5b6e..4690c88a3c 100755 --- a/script/get-preview-channel-changes +++ b/script/get-preview-channel-changes @@ -2,8 +2,8 @@ const { execFileSync } = require("child_process"); const { GITHUB_ACCESS_TOKEN } = process.env; -const PR_REGEX = /pull request #(\d+)/; -const FIXES_REGEX = /(fixes|closes) (.+[/#]\d+.*)$/im; +const PR_REGEX = /#\d+/ // Ex: matches on #4241 +const FIXES_REGEX = /(fixes|closes|completes) (.+[/#]\d+.*)$/im; main(); @@ -15,7 +15,7 @@ async function main() { { encoding: "utf8" } ) .split("\n") - .filter((t) => t.startsWith("v") && t.endsWith('-pre')); + .filter((t) => t.startsWith("v") && t.endsWith("-pre")); // Print the previous release console.log(`Changes from ${oldTag} to ${newTag}\n`); @@ -34,37 +34,11 @@ async function main() { } // Get the PRs merged between those two tags. - const pullRequestNumbers = execFileSync( - "git", - [ - "log", - `${oldTag}..${newTag}`, - "--oneline", - "--grep", - "Merge pull request", - ], - { encoding: "utf8" } - ) - .split("\n") - .filter((line) => line.length > 0) - .map((line) => line.match(PR_REGEX)[1]); + const pullRequestNumbers = getPullRequestNumbers(oldTag, newTag) // Get the PRs that were cherry-picked between main and the old tag. - const existingPullRequestNumbers = new Set(execFileSync( - "git", - [ - "log", - `main..${oldTag}`, - "--oneline", - "--grep", - "Merge pull request", - ], - { encoding: "utf8" } - ) - .split("\n") - .filter((line) => line.length > 0) - .map((line) => line.match(PR_REGEX)[1])); - + const existingPullRequestNumbers = new Set(getPullRequestNumbers("main", oldTag)) + // Filter out those existing PRs from the set of new PRs. const newPullRequestNumbers = pullRequestNumbers.filter(number => !existingPullRequestNumbers.has(number)); @@ -86,10 +60,39 @@ async function main() { console.log(" URL: ", webURL); // If the pull request contains a 'closes' line, print the closed issue. - const fixesMatch = (pullRequest.body || '').match(FIXES_REGEX); + const fixesMatch = (pullRequest.body || "").match(FIXES_REGEX); if (fixesMatch) { const fixedIssueURL = fixesMatch[2]; console.log(" Issue: ", fixedIssueURL); } + + let releaseNotes = (pullRequest.body || "").split("Release Notes:")[1]; + + if (releaseNotes) { + releaseNotes = releaseNotes.trim() + console.log(" Release Notes:"); + console.log(` ${releaseNotes}`); + } } } + +function getPullRequestNumbers(oldTag, newTag) { + const pullRequestNumbers = execFileSync( + "git", + [ + "log", + `${oldTag}..${newTag}`, + "--oneline" + ], + { encoding: "utf8" } + ) + .split("\n") + .filter(line => line.length > 0) + .map(line => { + const match = line.match(/#(\d+)/); + return match ? match[1] : null; + }) + .filter(line => line) + + return pullRequestNumbers +} From cb1b64e51ba3d0866834965def43343f82050b79 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Mon, 22 May 2023 00:02:19 -0400 Subject: [PATCH 168/168] Fix up formatting for get preview channel changes script --- script/get-preview-channel-changes | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/script/get-preview-channel-changes b/script/get-preview-channel-changes index 4690c88a3c..47623125f9 100755 --- a/script/get-preview-channel-changes +++ b/script/get-preview-channel-changes @@ -34,16 +34,16 @@ async function main() { } // Get the PRs merged between those two tags. - const pullRequestNumbers = getPullRequestNumbers(oldTag, newTag) + const pullRequestNumbers = getPullRequestNumbers(oldTag, newTag); // Get the PRs that were cherry-picked between main and the old tag. - const existingPullRequestNumbers = new Set(getPullRequestNumbers("main", oldTag)) + const existingPullRequestNumbers = new Set(getPullRequestNumbers("main", oldTag)); // Filter out those existing PRs from the set of new PRs. const newPullRequestNumbers = pullRequestNumbers.filter(number => !existingPullRequestNumbers.has(number)); // Fetch the pull requests from the GitHub API. - console.log("Merged Pull requests:") + console.log("Merged Pull requests:"); for (const pullRequestNumber of newPullRequestNumbers) { const webURL = `https://github.com/zed-industries/zed/pull/${pullRequestNumber}`; const apiURL = `https://api.github.com/repos/zed-industries/zed/pulls/${pullRequestNumber}`; @@ -57,22 +57,24 @@ async function main() { // Print the pull request title and URL. const pullRequest = await response.json(); console.log("*", pullRequest.title); - console.log(" URL: ", webURL); + console.log(" PR URL: ", webURL); // If the pull request contains a 'closes' line, print the closed issue. const fixesMatch = (pullRequest.body || "").match(FIXES_REGEX); if (fixesMatch) { const fixedIssueURL = fixesMatch[2]; - console.log(" Issue: ", fixedIssueURL); + console.log(" Issue URL: ", fixedIssueURL); } let releaseNotes = (pullRequest.body || "").split("Release Notes:")[1]; if (releaseNotes) { - releaseNotes = releaseNotes.trim() + releaseNotes = releaseNotes.trim(); console.log(" Release Notes:"); console.log(` ${releaseNotes}`); } + + console.log() } } @@ -92,7 +94,7 @@ function getPullRequestNumbers(oldTag, newTag) { const match = line.match(/#(\d+)/); return match ? match[1] : null; }) - .filter(line => line) + .filter(line => line); - return pullRequestNumbers + return pullRequestNumbers; }