diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 8d210513c2d3dc32fdac676b4953147cc4f0208e..fd275570411e296a59d2e476d92846dc665f8602 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -224,6 +224,8 @@ impl Server { .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) + .add_request_handler(forward_project_request::) + .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) .add_message_handler(create_buffer_for_peer) .add_request_handler(update_buffer) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 27d424879f1d566d1bf3d551912a00de89435840..92421687543e054e8d277722a7d0b033ae169389 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -51,6 +51,7 @@ use lsp_command::*; use postage::watch; use project_settings::ProjectSettings; use rand::prelude::*; +use rpc::proto::PeerId; use search::SearchQuery; use serde::Serialize; use settings::SettingsStore; @@ -478,6 +479,8 @@ impl Project { client.add_model_request_handler(Self::handle_rename_project_entry); client.add_model_request_handler(Self::handle_copy_project_entry); client.add_model_request_handler(Self::handle_delete_project_entry); + client.add_model_request_handler(Self::handle_expand_project_entry); + client.add_model_request_handler(Self::handle_collapse_project_entry); client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion); client.add_model_request_handler(Self::handle_apply_code_action); client.add_model_request_handler(Self::handle_on_type_formatting); @@ -5403,6 +5406,56 @@ impl Project { Some(ProjectPath { worktree_id, path }) } + pub fn mark_entry_expanded( + &mut self, + worktree_id: WorktreeId, + entry_id: ProjectEntryId, + cx: &mut ModelContext, + ) -> Option<()> { + if self.is_local() { + let worktree = self.worktree_for_id(worktree_id, cx)?; + worktree.update(cx, |worktree, cx| { + worktree + .as_local_mut() + .unwrap() + .mark_entry_expanded(entry_id, true, 0, cx); + }); + } else if let Some(project_id) = self.remote_id() { + cx.background() + .spawn(self.client.request(proto::ExpandProjectEntry { + project_id, + entry_id: entry_id.to_proto(), + })) + .log_err(); + } + Some(()) + } + + pub fn mark_entry_collapsed( + &mut self, + worktree_id: WorktreeId, + entry_id: ProjectEntryId, + cx: &mut ModelContext, + ) -> Option<()> { + if self.is_local() { + let worktree = self.worktree_for_id(worktree_id, cx)?; + worktree.update(cx, |worktree, cx| { + worktree + .as_local_mut() + .unwrap() + .mark_entry_expanded(entry_id, false, 0, cx); + }); + } else if let Some(project_id) = self.remote_id() { + cx.background() + .spawn(self.client.request(proto::CollapseProjectEntry { + project_id, + entry_id: entry_id.to_proto(), + })) + .log_err(); + } + Some(()) + } + pub fn absolute_path(&self, project_path: &ProjectPath, cx: &AppContext) -> Option { let workspace_root = self .worktree_for_id(project_path.worktree_id, cx)? @@ -5705,6 +5758,66 @@ impl Project { }) } + async fn handle_expand_project_entry( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + cx: AsyncAppContext, + ) -> Result { + Self::handle_expand_or_collapse_project_entry( + this, + envelope.payload.entry_id, + envelope.original_sender_id, + true, + cx, + ) + .await + } + + async fn handle_collapse_project_entry( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + cx: AsyncAppContext, + ) -> Result { + Self::handle_expand_or_collapse_project_entry( + this, + envelope.payload.entry_id, + envelope.original_sender_id, + false, + cx, + ) + .await + } + + async fn handle_expand_or_collapse_project_entry( + this: ModelHandle, + entry_id: u64, + original_sender_id: Option, + is_expanded: bool, + mut cx: AsyncAppContext, + ) -> Result { + let entry_id = ProjectEntryId::from_proto(entry_id); + let (worktree, replica_id) = this + .read_with(&cx, |this, cx| { + let replica_id = original_sender_id + .and_then(|peer_id| this.collaborators.get(&peer_id))? + .replica_id; + let worktree = this.worktree_for_entry(entry_id, cx)?; + Some((worktree, replica_id)) + }) + .ok_or_else(|| anyhow!("invalid request"))?; + worktree.update(&mut cx, |worktree, cx| { + worktree.as_local_mut().unwrap().mark_entry_expanded( + entry_id, + is_expanded, + replica_id, + cx, + ) + }); + Ok(proto::Ack {}) + } + async fn handle_update_diagnostic_summary( this: ModelHandle, envelope: TypedEnvelope, diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 2b0ba3d5218ea86d69dfdac2381bf2a7d62290be..5ffbbe5b8330ac43a75b1eb351935fe96b2ddb98 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -5,7 +5,7 @@ use ::ignore::gitignore::{Gitignore, GitignoreBuilder}; use anyhow::{anyhow, Context, Result}; use client::{proto, Client}; use clock::ReplicaId; -use collections::{HashMap, VecDeque}; +use collections::{HashMap, HashSet, VecDeque}; use fs::{ repository::{GitFileStatus, GitRepository, RepoPath}, Fs, LineEnding, @@ -67,7 +67,7 @@ pub enum Worktree { pub struct LocalWorktree { snapshot: LocalSnapshot, - path_changes_tx: channel::Sender<(Vec, barrier::Sender)>, + scan_requests_tx: channel::Sender, is_scanning: (watch::Sender, watch::Receiver), _background_scanner_task: Task<()>, share: Option, @@ -84,6 +84,18 @@ pub struct LocalWorktree { visible: bool, } +enum ScanRequest { + RescanPaths { + paths: Vec, + done: barrier::Sender, + }, + SetDirExpanded { + entry_id: ProjectEntryId, + replica_id: ReplicaId, + is_expanded: bool, + }, +} + pub struct RemoteWorktree { snapshot: Snapshot, background_snapshot: Arc>, @@ -214,6 +226,7 @@ pub struct LocalSnapshot { struct BackgroundScannerState { snapshot: LocalSnapshot, + expanded_dirs: HashSet<(ProjectEntryId, ReplicaId)>, /// 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 @@ -330,7 +343,7 @@ impl Worktree { ); } - let (path_changes_tx, path_changes_rx) = channel::unbounded(); + let (scan_requests_tx, scan_requests_rx) = channel::unbounded(); let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded(); cx.spawn_weak(|this, mut cx| async move { @@ -370,7 +383,7 @@ impl Worktree { fs, scan_states_tx, background, - path_changes_rx, + scan_requests_rx, ) .run(events) .await; @@ -381,7 +394,7 @@ impl Worktree { snapshot, is_scanning: watch::channel_with(true), share: None, - path_changes_tx, + scan_requests_tx, _background_scanner_task: background_scanner_task, diagnostics: Default::default(), diagnostic_summaries: Default::default(), @@ -1068,8 +1081,11 @@ impl LocalWorktree { this.update(&mut cx, |this, _| { this.as_local_mut() .unwrap() - .path_changes_tx - .try_send((vec![abs_path], tx)) + .scan_requests_tx + .try_send(ScanRequest::RescanPaths { + paths: vec![abs_path], + done: tx, + }) })?; rx.recv().await; Ok(()) @@ -1135,6 +1151,22 @@ impl LocalWorktree { })) } + pub fn mark_entry_expanded( + &mut self, + entry_id: ProjectEntryId, + is_expanded: bool, + replica_id: ReplicaId, + _cx: &mut ModelContext, + ) { + self.scan_requests_tx + .try_send(ScanRequest::SetDirExpanded { + entry_id, + replica_id, + is_expanded, + }) + .ok(); + } + fn refresh_entry( &self, path: Arc, @@ -1143,7 +1175,7 @@ impl LocalWorktree { ) -> Task> { let fs = self.fs.clone(); let abs_root_path = self.abs_path.clone(); - let path_changes_tx = self.path_changes_tx.clone(); + let path_changes_tx = self.scan_requests_tx.clone(); cx.spawn_weak(move |this, mut cx| async move { let abs_path = fs.canonicalize(&abs_root_path).await?; let mut paths = Vec::with_capacity(2); @@ -1161,7 +1193,7 @@ impl LocalWorktree { } let (tx, mut rx) = barrier::channel(); - path_changes_tx.try_send((paths, tx))?; + path_changes_tx.try_send(ScanRequest::RescanPaths { paths, done: tx })?; rx.recv().await; this.upgrade(&cx) .ok_or_else(|| anyhow!("worktree was dropped"))? @@ -2784,7 +2816,7 @@ struct BackgroundScanner { fs: Arc, status_updates_tx: UnboundedSender, executor: Arc, - refresh_requests_rx: channel::Receiver<(Vec, barrier::Sender)>, + scan_requests_rx: channel::Receiver, next_entry_id: Arc, phase: BackgroundScannerPhase, } @@ -2803,17 +2835,18 @@ impl BackgroundScanner { fs: Arc, status_updates_tx: UnboundedSender, executor: Arc, - refresh_requests_rx: channel::Receiver<(Vec, barrier::Sender)>, + scan_requests_rx: channel::Receiver, ) -> Self { Self { fs, status_updates_tx, executor, - refresh_requests_rx, + scan_requests_rx, next_entry_id, state: Mutex::new(BackgroundScannerState { prev_snapshot: snapshot.snapshot.clone(), snapshot, + expanded_dirs: Default::default(), removed_entry_ids: Default::default(), changed_paths: Default::default(), }), @@ -2898,9 +2931,9 @@ impl BackgroundScanner { select_biased! { // Process any path refresh requests from the worktree. Prioritize // 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.clone(), barrier).await { + request = self.scan_requests_rx.recv().fuse() => { + let Ok(request) = request else { break }; + if !self.process_scan_request(request).await { return; } } @@ -2917,9 +2950,29 @@ impl BackgroundScanner { } } - async fn process_refresh_request(&self, paths: Vec, barrier: barrier::Sender) -> bool { - self.reload_entries_for_paths(paths, None).await; - self.send_status_update(false, Some(barrier)) + async fn process_scan_request(&self, request: ScanRequest) -> bool { + match request { + ScanRequest::RescanPaths { paths, done } => { + self.reload_entries_for_paths(paths, None).await; + self.send_status_update(false, Some(done)) + } + ScanRequest::SetDirExpanded { + entry_id, + replica_id, + is_expanded, + } => { + let mut state = self.state.lock(); + if is_expanded { + state.expanded_dirs.insert((entry_id, replica_id)); + } else { + state.expanded_dirs.remove(&(entry_id, replica_id)); + } + + // todo + + true + } + } } async fn process_events(&mut self, paths: Vec) { @@ -2995,9 +3048,9 @@ impl BackgroundScanner { select_biased! { // Process any path refresh requests before moving on to process // the scan queue, so that user operations are prioritized. - request = self.refresh_requests_rx.recv().fuse() => { - let Ok((paths, barrier)) = request else { break }; - if !self.process_refresh_request(paths, barrier).await { + request = self.scan_requests_rx.recv().fuse() => { + let Ok(request) = request else { break }; + if !self.process_scan_request(request).await { return; } } @@ -3487,9 +3540,9 @@ impl BackgroundScanner { select_biased! { // Process any path refresh requests before moving on to process // the queue of ignore statuses. - request = self.refresh_requests_rx.recv().fuse() => { - let Ok((paths, barrier)) = request else { break }; - if !self.process_refresh_request(paths, barrier).await { + request = self.scan_requests_rx.recv().fuse() => { + let Ok(request) = request else { break }; + if !self.process_scan_request(request).await { return; } } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index dc592b758880601dec6bb63d22d73fb0a9e4e289..fd2ff1c6d55814cadbd8bad5d0cd4ebe62e770f9 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -431,18 +431,23 @@ impl ProjectPanel { fn collapse_selected_entry(&mut self, _: &CollapseSelectedEntry, cx: &mut ViewContext) { if let Some((worktree, mut entry)) = self.selected_entry(cx) { + let worktree_id = worktree.id(); let expanded_dir_ids = - if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree.id()) { + if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) { expanded_dir_ids } else { return; }; loop { - match expanded_dir_ids.binary_search(&entry.id) { + let entry_id = entry.id; + match expanded_dir_ids.binary_search(&entry_id) { Ok(ix) => { expanded_dir_ids.remove(ix); - self.update_visible_entries(Some((worktree.id(), entry.id)), cx); + self.update_visible_entries(Some((worktree_id, entry_id)), cx); + self.project.update(cx, |project, cx| { + project.mark_entry_collapsed(worktree_id, entry_id, cx); + }); cx.notify(); break; } @@ -938,10 +943,19 @@ impl ProjectPanel { } fn selected_entry<'a>(&self, cx: &'a AppContext) -> Option<(&'a Worktree, &'a project::Entry)> { + let (worktree, entry) = self.selected_entry_handle(cx)?; + Some((worktree.read(cx), entry)) + } + + fn selected_entry_handle<'a>( + &self, + cx: &'a AppContext, + ) -> Option<(ModelHandle, &'a project::Entry)> { let selection = self.selection?; let project = self.project.read(cx); - let worktree = project.worktree_for_id(selection.worktree_id, cx)?.read(cx); - Some((worktree, worktree.entry_for_id(selection.entry_id)?)) + let worktree = project.worktree_for_id(selection.worktree_id, cx)?; + let entry = worktree.read(cx).entry_for_id(selection.entry_id)?; + Some((worktree, entry)) } fn update_visible_entries( @@ -1058,29 +1072,31 @@ impl ProjectPanel { entry_id: ProjectEntryId, cx: &mut ViewContext, ) { - let project = self.project.read(cx); - if let Some((worktree, expanded_dir_ids)) = project - .worktree_for_id(worktree_id, cx) - .zip(self.expanded_dir_ids.get_mut(&worktree_id)) - { - let worktree = worktree.read(cx); + self.project.update(cx, |project, cx| { + if let Some((worktree, expanded_dir_ids)) = project + .worktree_for_id(worktree_id, cx) + .zip(self.expanded_dir_ids.get_mut(&worktree_id)) + { + project.mark_entry_expanded(worktree_id, entry_id, cx); + let worktree = worktree.read(cx); - if let Some(mut entry) = worktree.entry_for_id(entry_id) { - loop { - if let Err(ix) = expanded_dir_ids.binary_search(&entry.id) { - expanded_dir_ids.insert(ix, entry.id); - } + if let Some(mut entry) = worktree.entry_for_id(entry_id) { + loop { + if let Err(ix) = expanded_dir_ids.binary_search(&entry.id) { + expanded_dir_ids.insert(ix, entry.id); + } - if let Some(parent_entry) = - entry.path.parent().and_then(|p| worktree.entry_for_path(p)) - { - entry = parent_entry; - } else { - break; + if let Some(parent_entry) = + entry.path.parent().and_then(|p| worktree.entry_for_path(p)) + { + entry = parent_entry; + } else { + break; + } } } } - } + }); } fn for_each_visible_entry( diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index ce4dd7f7cf5514fa56aa4a62f1ed4553a9273b54..d7e8d16a0e954f6df17c5a6727eb869495140320 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -62,6 +62,8 @@ message Envelope { RenameProjectEntry rename_project_entry = 46; CopyProjectEntry copy_project_entry = 47; DeleteProjectEntry delete_project_entry = 48; + ExpandProjectEntry expand_project_entry = 114; + CollapseProjectEntry collapse_project_entry = 115; ProjectEntryResponse project_entry_response = 49; UpdateDiagnosticSummary update_diagnostic_summary = 50; @@ -372,6 +374,16 @@ message DeleteProjectEntry { uint64 entry_id = 2; } +message ExpandProjectEntry { + uint64 project_id = 1; + uint64 entry_id = 2; +} + +message CollapseProjectEntry { + uint64 project_id = 1; + uint64 entry_id = 2; +} + message ProjectEntryResponse { Entry entry = 1; uint64 worktree_scan_id = 2; diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 13794ea64dad1446e579dd00806ccb4afda4758b..9f8e9424926e0bce8c12afc76e5e5d21f50bd5cf 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -150,6 +150,8 @@ messages!( (DeclineCall, Foreground), (DeleteProjectEntry, Foreground), (Error, Foreground), + (ExpandProjectEntry, Foreground), + (CollapseProjectEntry, Foreground), (Follow, Foreground), (FollowResponse, Foreground), (FormatBuffers, Foreground), @@ -255,6 +257,8 @@ request_messages!( (CreateRoom, CreateRoomResponse), (DeclineCall, Ack), (DeleteProjectEntry, ProjectEntryResponse), + (ExpandProjectEntry, Ack), + (CollapseProjectEntry, Ack), (Follow, FollowResponse), (FormatBuffers, FormatBuffersResponse), (GetChannelMessages, GetChannelMessagesResponse), @@ -311,6 +315,8 @@ entity_messages!( CreateBufferForPeer, CreateProjectEntry, DeleteProjectEntry, + ExpandProjectEntry, + CollapseProjectEntry, Follow, FormatBuffers, GetCodeActions, diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index 8b101670918ad42dc58e9039f8150fb005a1595b..6b430d90e46072a6f885b60a4e912978ed26c6a2 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 = 58; +pub const PROTOCOL_VERSION: u32 = 59;