Detailed changes
@@ -259,6 +259,7 @@ pub enum Event {
LanguageServerLog(LanguageServerId, String),
Notification(String),
ActiveEntryChanged(Option<ProjectEntryId>),
+ ActivateProjectPanel,
WorktreeAdded,
WorktreeRemoved(WorktreeId),
WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet),
@@ -174,6 +174,7 @@ pub enum Event {
NewSearchInDirectory {
dir_entry: Entry,
},
+ ActivatePanel,
}
#[derive(Serialize, Deserialize)]
@@ -200,6 +201,9 @@ impl ProjectPanel {
cx.notify();
}
}
+ project::Event::ActivateProjectPanel => {
+ cx.emit(Event::ActivatePanel);
+ }
project::Event::WorktreeRemoved(id) => {
this.expanded_dir_ids.remove(id);
this.update_visible_entries(None, cx);
@@ -1014,7 +1018,10 @@ impl ProjectPanel {
None
}
- fn selected_entry<'a>(&self, cx: &'a AppContext) -> Option<(&'a Worktree, &'a project::Entry)> {
+ pub 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))
}
@@ -73,7 +73,9 @@ const DEBUG_CELL_WIDTH: f32 = 5.;
const DEBUG_LINE_HEIGHT: f32 = 5.;
lazy_static! {
- // Regex Copied from alacritty's ui_config.rs
+ // Regex Copied from alacritty's ui_config.rs and modified its declaration slightly:
+ // * avoid Rust-specific escaping.
+ // * use more strict regex for `file://` protocol matching: original regex has `file:` inside, but we want to avoid matching `some::file::module` strings.
static ref URL_REGEX: RegexSearch = RegexSearch::new(r#"(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file://|git://|ssh:|ftp://)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>"\s{-}\^⟨⟩`]+"#).unwrap();
static ref WORD_REGEX: RegexSearch = RegexSearch::new("[\\w.:/@-~]+").unwrap();
@@ -187,37 +187,56 @@ impl TerminalView {
}
let potential_abs_paths = possible_open_targets(&workspace, maybe_path, cx);
if let Some(path) = potential_abs_paths.into_iter().next() {
- let visible = path.path_like.is_dir();
+ let is_dir = path.path_like.is_dir();
let task_workspace = workspace.clone();
cx.spawn(|_, mut cx| async move {
- let opened_item = task_workspace
+ let opened_items = task_workspace
.update(&mut cx, |workspace, cx| {
- workspace.open_abs_path(path.path_like, visible, cx)
+ workspace.open_paths(vec![path.path_like], is_dir, cx)
})
.context("workspace update")?
- .await
- .context("workspace update")?;
- if let Some(row) = path.row {
- let col = path.column.unwrap_or(0);
- if let Some(active_editor) = opened_item.downcast::<Editor>() {
- active_editor
- .downgrade()
- .update(&mut cx, |editor, cx| {
- let snapshot = editor.snapshot(cx).display_snapshot;
- let point = snapshot.buffer_snapshot.clip_point(
- language::Point::new(
- row.saturating_sub(1),
- col.saturating_sub(1),
- ),
- Bias::Left,
- );
- editor.change_selections(
- Some(Autoscroll::center()),
- cx,
- |s| s.select_ranges([point..point]),
- );
- })
- .log_err();
+ .await;
+ anyhow::ensure!(
+ opened_items.len() == 1,
+ "For a single path open, expected single opened item"
+ );
+ let opened_item = opened_items
+ .into_iter()
+ .next()
+ .unwrap()
+ .transpose()
+ .context("path open")?;
+ if is_dir {
+ task_workspace.update(&mut cx, |workspace, cx| {
+ workspace.project().update(cx, |_, cx| {
+ cx.emit(project::Event::ActivateProjectPanel);
+ })
+ })?;
+ } else {
+ if let Some(row) = path.row {
+ let col = path.column.unwrap_or(0);
+ if let Some(active_editor) =
+ opened_item.and_then(|item| item.downcast::<Editor>())
+ {
+ active_editor
+ .downgrade()
+ .update(&mut cx, |editor, cx| {
+ let snapshot = editor.snapshot(cx).display_snapshot;
+ let point = snapshot.buffer_snapshot.clip_point(
+ language::Point::new(
+ row.saturating_sub(1),
+ col.saturating_sub(1),
+ ),
+ Bias::Left,
+ );
+ editor.change_selections(
+ Some(Autoscroll::center()),
+ cx,
+ |s| s.select_ranges([point..point]),
+ );
+ })
+ .log_err();
+ }
}
}
anyhow::Ok(())
@@ -898,6 +898,18 @@ impl Workspace {
pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
where
T::Event: std::fmt::Debug,
+ {
+ self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {})
+ }
+
+ pub fn add_panel_with_extra_event_handler<T: Panel, F>(
+ &mut self,
+ panel: ViewHandle<T>,
+ cx: &mut ViewContext<Self>,
+ handler: F,
+ ) where
+ T::Event: std::fmt::Debug,
+ F: Fn(&mut Self, &ViewHandle<T>, &T::Event, &mut ViewContext<Self>) + 'static,
{
let dock = match panel.position(cx) {
DockPosition::Left => &self.left_dock,
@@ -965,6 +977,8 @@ impl Workspace {
}
this.update_active_view_for_followers(cx);
cx.notify();
+ } else {
+ handler(this, &panel, event, cx)
}
}
}));
@@ -1417,45 +1431,65 @@ impl Workspace {
// Sort the paths to ensure we add worktrees for parents before their children.
abs_paths.sort_unstable();
cx.spawn(|this, mut cx| async move {
- let mut project_paths = Vec::new();
- for path in &abs_paths {
- if let Some(project_path) = this
+ let mut tasks = Vec::with_capacity(abs_paths.len());
+ for abs_path in &abs_paths {
+ let project_path = match this
.update(&mut cx, |this, cx| {
- Workspace::project_path_for_path(this.project.clone(), path, visible, cx)
+ Workspace::project_path_for_path(
+ this.project.clone(),
+ abs_path,
+ visible,
+ cx,
+ )
})
.log_err()
{
- project_paths.push(project_path.await.log_err());
- } else {
- project_paths.push(None);
- }
- }
+ Some(project_path) => project_path.await.log_err(),
+ None => None,
+ };
- let tasks = abs_paths
- .iter()
- .cloned()
- .zip(project_paths.into_iter())
- .map(|(abs_path, project_path)| {
- let this = this.clone();
- cx.spawn(|mut cx| {
- let fs = fs.clone();
- async move {
- let (_worktree, project_path) = project_path?;
- if fs.is_file(&abs_path).await {
- Some(
- this.update(&mut cx, |this, cx| {
- this.open_path(project_path, None, true, cx)
+ let this = this.clone();
+ let task = cx.spawn(|mut cx| {
+ let fs = fs.clone();
+ let abs_path = abs_path.clone();
+ async move {
+ let (worktree, project_path) = project_path?;
+ if fs.is_file(&abs_path).await {
+ Some(
+ this.update(&mut cx, |this, cx| {
+ this.open_path(project_path, None, true, cx)
+ })
+ .log_err()?
+ .await,
+ )
+ } else {
+ this.update(&mut cx, |workspace, cx| {
+ let worktree = worktree.read(cx);
+ let worktree_abs_path = worktree.abs_path();
+ let entry_id = if abs_path == worktree_abs_path.as_ref() {
+ worktree.root_entry()
+ } else {
+ abs_path
+ .strip_prefix(worktree_abs_path.as_ref())
+ .ok()
+ .and_then(|relative_path| {
+ worktree.entry_for_path(relative_path)
+ })
+ }
+ .map(|entry| entry.id);
+ if let Some(entry_id) = entry_id {
+ workspace.project().update(cx, |_, cx| {
+ cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
})
- .log_err()?
- .await,
- )
- } else {
- None
- }
+ }
+ })
+ .log_err()?;
+ None
}
- })
- })
- .collect::<Vec<_>>();
+ }
+ });
+ tasks.push(task);
+ }
futures::future::join_all(tasks).await
})
@@ -3009,10 +3043,6 @@ impl Workspace {
self.database_id
}
- pub fn push_subscription(&mut self, subscription: Subscription) {
- self.subscriptions.push(subscription)
- }
-
fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
let project = self.project().read(cx);
@@ -339,29 +339,21 @@ pub fn initialize_workspace(
let (project_panel, terminal_panel, assistant_panel) =
futures::try_join!(project_panel, terminal_panel, assistant_panel)?;
- cx.update(|cx| {
- if let Some(workspace) = workspace_handle.upgrade(cx) {
- cx.update_window(project_panel.window_id(), |cx| {
- workspace.update(cx, |workspace, cx| {
- let project_panel_subscription =
- cx.subscribe(&project_panel, move |workspace, _, event, cx| {
- if let project_panel::Event::NewSearchInDirectory { dir_entry } =
- event
- {
- search::ProjectSearchView::new_search_in_directory(
- workspace, dir_entry, cx,
- )
- }
- });
- workspace.push_subscription(project_panel_subscription);
- });
- });
- }
- });
-
workspace_handle.update(&mut cx, |workspace, cx| {
let project_panel_position = project_panel.position(cx);
- workspace.add_panel(project_panel, cx);
+ workspace.add_panel_with_extra_event_handler(
+ project_panel,
+ cx,
+ |workspace, _, event, cx| match event {
+ project_panel::Event::NewSearchInDirectory { dir_entry } => {
+ search::ProjectSearchView::new_search_in_directory(workspace, dir_entry, cx)
+ }
+ project_panel::Event::ActivatePanel => {
+ workspace.focus_panel::<ProjectPanel>(cx);
+ }
+ _ => {}
+ },
+ );
workspace.add_panel(terminal_panel, cx);
workspace.add_panel(assistant_panel, cx);
@@ -1106,8 +1098,46 @@ mod tests {
)
.await;
- let project = Project::test(app_state.fs.clone(), ["/dir1".as_ref()], cx).await;
- let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ cx.update(|cx| open_paths(&[PathBuf::from("/dir1/")], &app_state, None, cx))
+ .await
+ .unwrap();
+ assert_eq!(cx.window_ids().len(), 1);
+ let workspace = cx
+ .read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
+ .unwrap()
+ .downcast::<Workspace>()
+ .unwrap();
+
+ #[track_caller]
+ fn assert_project_panel_selection(
+ workspace: &Workspace,
+ expected_worktree_path: &Path,
+ expected_entry_path: &Path,
+ cx: &AppContext,
+ ) {
+ let project_panel = [
+ workspace.left_dock().read(cx).panel::<ProjectPanel>(),
+ workspace.right_dock().read(cx).panel::<ProjectPanel>(),
+ workspace.bottom_dock().read(cx).panel::<ProjectPanel>(),
+ ]
+ .into_iter()
+ .find_map(std::convert::identity)
+ .expect("found no project panels")
+ .read(cx);
+ let (selected_worktree, selected_entry) = project_panel
+ .selected_entry(cx)
+ .expect("project panel should have a selected entry");
+ assert_eq!(
+ selected_worktree.abs_path().as_ref(),
+ expected_worktree_path,
+ "Unexpected project panel selected worktree path"
+ );
+ assert_eq!(
+ selected_entry.path.as_ref(),
+ expected_entry_path,
+ "Unexpected project panel selected entry path"
+ );
+ }
// Open a file within an existing worktree.
workspace
@@ -1116,9 +1146,10 @@ mod tests {
})
.await;
cx.read(|cx| {
+ let workspace = workspace.read(cx);
+ assert_project_panel_selection(workspace, Path::new("/dir1"), Path::new("a.txt"), cx);
assert_eq!(
workspace
- .read(cx)
.active_pane()
.read(cx)
.active_item()
@@ -1139,8 +1170,9 @@ mod tests {
})
.await;
cx.read(|cx| {
+ let workspace = workspace.read(cx);
+ assert_project_panel_selection(workspace, Path::new("/dir2/b.txt"), Path::new(""), cx);
let worktree_roots = workspace
- .read(cx)
.worktrees(cx)
.map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
.collect::<HashSet<_>>();
@@ -1153,7 +1185,6 @@ mod tests {
);
assert_eq!(
workspace
- .read(cx)
.active_pane()
.read(cx)
.active_item()
@@ -1174,8 +1205,9 @@ mod tests {
})
.await;
cx.read(|cx| {
+ let workspace = workspace.read(cx);
+ assert_project_panel_selection(workspace, Path::new("/dir3"), Path::new("c.txt"), cx);
let worktree_roots = workspace
- .read(cx)
.worktrees(cx)
.map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
.collect::<HashSet<_>>();
@@ -1188,7 +1220,6 @@ mod tests {
);
assert_eq!(
workspace
- .read(cx)
.active_pane()
.read(cx)
.active_item()
@@ -1209,8 +1240,9 @@ mod tests {
})
.await;
cx.read(|cx| {
+ let workspace = workspace.read(cx);
+ assert_project_panel_selection(workspace, Path::new("/d.txt"), Path::new(""), cx);
let worktree_roots = workspace
- .read(cx)
.worktrees(cx)
.map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
.collect::<HashSet<_>>();
@@ -1223,7 +1255,6 @@ mod tests {
);
let visible_worktree_roots = workspace
- .read(cx)
.visible_worktrees(cx)
.map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
.collect::<HashSet<_>>();
@@ -1237,7 +1268,6 @@ mod tests {
assert_eq!(
workspace
- .read(cx)
.active_pane()
.read(cx)
.active_item()