Focus project panel on directory select
This commit is contained in:
parent
dcaf8a9af8
commit
f05095a6dd
6 changed files with 184 additions and 95 deletions
|
@ -259,6 +259,7 @@ pub enum Event {
|
||||||
LanguageServerLog(LanguageServerId, String),
|
LanguageServerLog(LanguageServerId, String),
|
||||||
Notification(String),
|
Notification(String),
|
||||||
ActiveEntryChanged(Option<ProjectEntryId>),
|
ActiveEntryChanged(Option<ProjectEntryId>),
|
||||||
|
ActivateProjectPanel,
|
||||||
WorktreeAdded,
|
WorktreeAdded,
|
||||||
WorktreeRemoved(WorktreeId),
|
WorktreeRemoved(WorktreeId),
|
||||||
WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet),
|
WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet),
|
||||||
|
|
|
@ -174,6 +174,7 @@ pub enum Event {
|
||||||
NewSearchInDirectory {
|
NewSearchInDirectory {
|
||||||
dir_entry: Entry,
|
dir_entry: Entry,
|
||||||
},
|
},
|
||||||
|
ActivatePanel,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -200,6 +201,9 @@ impl ProjectPanel {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
project::Event::ActivateProjectPanel => {
|
||||||
|
cx.emit(Event::ActivatePanel);
|
||||||
|
}
|
||||||
project::Event::WorktreeRemoved(id) => {
|
project::Event::WorktreeRemoved(id) => {
|
||||||
this.expanded_dir_ids.remove(id);
|
this.expanded_dir_ids.remove(id);
|
||||||
this.update_visible_entries(None, cx);
|
this.update_visible_entries(None, cx);
|
||||||
|
@ -1014,7 +1018,10 @@ impl ProjectPanel {
|
||||||
None
|
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)?;
|
let (worktree, entry) = self.selected_entry_handle(cx)?;
|
||||||
Some((worktree.read(cx), entry))
|
Some((worktree.read(cx), entry))
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,9 @@ const DEBUG_CELL_WIDTH: f32 = 5.;
|
||||||
const DEBUG_LINE_HEIGHT: f32 = 5.;
|
const DEBUG_LINE_HEIGHT: f32 = 5.;
|
||||||
|
|
||||||
lazy_static! {
|
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 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();
|
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);
|
let potential_abs_paths = possible_open_targets(&workspace, maybe_path, cx);
|
||||||
if let Some(path) = potential_abs_paths.into_iter().next() {
|
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();
|
let task_workspace = workspace.clone();
|
||||||
cx.spawn(|_, mut cx| async move {
|
cx.spawn(|_, mut cx| async move {
|
||||||
let opened_item = task_workspace
|
let opened_items = task_workspace
|
||||||
.update(&mut cx, |workspace, cx| {
|
.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")?
|
.context("workspace update")?
|
||||||
.await
|
.await;
|
||||||
.context("workspace update")?;
|
anyhow::ensure!(
|
||||||
if let Some(row) = path.row {
|
opened_items.len() == 1,
|
||||||
let col = path.column.unwrap_or(0);
|
"For a single path open, expected single opened item"
|
||||||
if let Some(active_editor) = opened_item.downcast::<Editor>() {
|
);
|
||||||
active_editor
|
let opened_item = opened_items
|
||||||
.downgrade()
|
.into_iter()
|
||||||
.update(&mut cx, |editor, cx| {
|
.next()
|
||||||
let snapshot = editor.snapshot(cx).display_snapshot;
|
.unwrap()
|
||||||
let point = snapshot.buffer_snapshot.clip_point(
|
.transpose()
|
||||||
language::Point::new(
|
.context("path open")?;
|
||||||
row.saturating_sub(1),
|
if is_dir {
|
||||||
col.saturating_sub(1),
|
task_workspace.update(&mut cx, |workspace, cx| {
|
||||||
),
|
workspace.project().update(cx, |_, cx| {
|
||||||
Bias::Left,
|
cx.emit(project::Event::ActivateProjectPanel);
|
||||||
);
|
})
|
||||||
editor.change_selections(
|
})?;
|
||||||
Some(Autoscroll::center()),
|
} else {
|
||||||
cx,
|
if let Some(row) = path.row {
|
||||||
|s| s.select_ranges([point..point]),
|
let col = path.column.unwrap_or(0);
|
||||||
);
|
if let Some(active_editor) =
|
||||||
})
|
opened_item.and_then(|item| item.downcast::<Editor>())
|
||||||
.log_err();
|
{
|
||||||
|
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(())
|
anyhow::Ok(())
|
||||||
|
|
|
@ -898,6 +898,18 @@ impl Workspace {
|
||||||
pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
|
pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
|
||||||
where
|
where
|
||||||
T::Event: std::fmt::Debug,
|
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) {
|
let dock = match panel.position(cx) {
|
||||||
DockPosition::Left => &self.left_dock,
|
DockPosition::Left => &self.left_dock,
|
||||||
|
@ -965,6 +977,8 @@ impl Workspace {
|
||||||
}
|
}
|
||||||
this.update_active_view_for_followers(cx);
|
this.update_active_view_for_followers(cx);
|
||||||
cx.notify();
|
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.
|
// Sort the paths to ensure we add worktrees for parents before their children.
|
||||||
abs_paths.sort_unstable();
|
abs_paths.sort_unstable();
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let mut project_paths = Vec::new();
|
let mut tasks = Vec::with_capacity(abs_paths.len());
|
||||||
for path in &abs_paths {
|
for abs_path in &abs_paths {
|
||||||
if let Some(project_path) = this
|
let project_path = match this
|
||||||
.update(&mut cx, |this, cx| {
|
.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()
|
.log_err()
|
||||||
{
|
{
|
||||||
project_paths.push(project_path.await.log_err());
|
Some(project_path) => project_path.await.log_err(),
|
||||||
} else {
|
None => None,
|
||||||
project_paths.push(None);
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let tasks = abs_paths
|
let this = this.clone();
|
||||||
.iter()
|
let task = cx.spawn(|mut cx| {
|
||||||
.cloned()
|
let fs = fs.clone();
|
||||||
.zip(project_paths.into_iter())
|
let abs_path = abs_path.clone();
|
||||||
.map(|(abs_path, project_path)| {
|
async move {
|
||||||
let this = this.clone();
|
let (worktree, project_path) = project_path?;
|
||||||
cx.spawn(|mut cx| {
|
if fs.is_file(&abs_path).await {
|
||||||
let fs = fs.clone();
|
Some(
|
||||||
async move {
|
this.update(&mut cx, |this, cx| {
|
||||||
let (_worktree, project_path) = project_path?;
|
this.open_path(project_path, None, true, cx)
|
||||||
if fs.is_file(&abs_path).await {
|
})
|
||||||
Some(
|
.log_err()?
|
||||||
this.update(&mut cx, |this, cx| {
|
.await,
|
||||||
this.open_path(project_path, None, true, cx)
|
)
|
||||||
|
} 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,
|
})
|
||||||
)
|
.log_err()?;
|
||||||
} else {
|
None
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
})
|
});
|
||||||
.collect::<Vec<_>>();
|
tasks.push(task);
|
||||||
|
}
|
||||||
|
|
||||||
futures::future::join_all(tasks).await
|
futures::future::join_all(tasks).await
|
||||||
})
|
})
|
||||||
|
@ -3009,10 +3043,6 @@ impl Workspace {
|
||||||
self.database_id
|
self.database_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_subscription(&mut self, subscription: Subscription) {
|
|
||||||
self.subscriptions.push(subscription)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
|
fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
|
||||||
let project = self.project().read(cx);
|
let project = self.project().read(cx);
|
||||||
|
|
||||||
|
|
|
@ -339,29 +339,21 @@ pub fn initialize_workspace(
|
||||||
let (project_panel, terminal_panel, assistant_panel) =
|
let (project_panel, terminal_panel, assistant_panel) =
|
||||||
futures::try_join!(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| {
|
workspace_handle.update(&mut cx, |workspace, cx| {
|
||||||
let project_panel_position = project_panel.position(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(terminal_panel, cx);
|
||||||
workspace.add_panel(assistant_panel, cx);
|
workspace.add_panel(assistant_panel, cx);
|
||||||
|
|
||||||
|
@ -1106,8 +1098,46 @@ mod tests {
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(app_state.fs.clone(), ["/dir1".as_ref()], cx).await;
|
cx.update(|cx| open_paths(&[PathBuf::from("/dir1/")], &app_state, None, cx))
|
||||||
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, 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.
|
// Open a file within an existing worktree.
|
||||||
workspace
|
workspace
|
||||||
|
@ -1116,9 +1146,10 @@ mod tests {
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
cx.read(|cx| {
|
cx.read(|cx| {
|
||||||
|
let workspace = workspace.read(cx);
|
||||||
|
assert_project_panel_selection(workspace, Path::new("/dir1"), Path::new("a.txt"), cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
workspace
|
workspace
|
||||||
.read(cx)
|
|
||||||
.active_pane()
|
.active_pane()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.active_item()
|
.active_item()
|
||||||
|
@ -1139,8 +1170,9 @@ mod tests {
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
cx.read(|cx| {
|
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
|
let worktree_roots = workspace
|
||||||
.read(cx)
|
|
||||||
.worktrees(cx)
|
.worktrees(cx)
|
||||||
.map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
|
.map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
|
||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
|
@ -1153,7 +1185,6 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
workspace
|
workspace
|
||||||
.read(cx)
|
|
||||||
.active_pane()
|
.active_pane()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.active_item()
|
.active_item()
|
||||||
|
@ -1174,8 +1205,9 @@ mod tests {
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
cx.read(|cx| {
|
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
|
let worktree_roots = workspace
|
||||||
.read(cx)
|
|
||||||
.worktrees(cx)
|
.worktrees(cx)
|
||||||
.map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
|
.map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
|
||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
|
@ -1188,7 +1220,6 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
workspace
|
workspace
|
||||||
.read(cx)
|
|
||||||
.active_pane()
|
.active_pane()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.active_item()
|
.active_item()
|
||||||
|
@ -1209,8 +1240,9 @@ mod tests {
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
cx.read(|cx| {
|
cx.read(|cx| {
|
||||||
|
let workspace = workspace.read(cx);
|
||||||
|
assert_project_panel_selection(workspace, Path::new("/d.txt"), Path::new(""), cx);
|
||||||
let worktree_roots = workspace
|
let worktree_roots = workspace
|
||||||
.read(cx)
|
|
||||||
.worktrees(cx)
|
.worktrees(cx)
|
||||||
.map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
|
.map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
|
||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
|
@ -1223,7 +1255,6 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let visible_worktree_roots = workspace
|
let visible_worktree_roots = workspace
|
||||||
.read(cx)
|
|
||||||
.visible_worktrees(cx)
|
.visible_worktrees(cx)
|
||||||
.map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
|
.map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
|
||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
|
@ -1237,7 +1268,6 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
workspace
|
workspace
|
||||||
.read(cx)
|
|
||||||
.active_pane()
|
.active_pane()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.active_item()
|
.active_item()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue