@@ -12,17 +12,21 @@ use std::{path::Path, sync::Arc};
pub struct Project {
worktrees: Vec<ModelHandle<Worktree>>,
+ active_entry: Option<(usize, usize)>,
languages: Arc<LanguageRegistry>,
rpc: Arc<Client>,
fs: Arc<dyn Fs>,
}
-pub enum Event {}
+pub enum Event {
+ ActiveEntryChanged(Option<(usize, usize)>),
+}
impl Project {
pub fn new(app_state: &AppState) -> Self {
Self {
worktrees: Default::default(),
+ active_entry: None,
languages: app_state.languages.clone(),
rpc: app_state.rpc.clone(),
fs: app_state.fs.clone(),
@@ -89,6 +93,26 @@ impl Project {
cx.notify();
}
+ pub fn set_active_entry(
+ &mut self,
+ entry: Option<(usize, Arc<Path>)>,
+ cx: &mut ModelContext<Self>,
+ ) {
+ let new_active_entry = entry.and_then(|(worktree_id, path)| {
+ let worktree = self.worktree_for_id(worktree_id)?;
+ let entry = worktree.read(cx).entry_for_path(path)?;
+ Some((worktree_id, entry.id))
+ });
+ if new_active_entry != self.active_entry {
+ self.active_entry = new_active_entry;
+ cx.emit(Event::ActiveEntryChanged(new_active_entry));
+ }
+ }
+
+ pub fn active_entry(&self) -> Option<(usize, usize)> {
+ self.active_entry
+ }
+
pub fn share_worktree(&self, remote_id: u64, cx: &mut ModelContext<Self>) {
let rpc = self.rpc.clone();
cx.spawn(|this, mut cx| {
@@ -1,4 +1,7 @@
-use crate::{project::Project, theme, Settings};
+use crate::{
+ project::{self, Project},
+ theme, Settings,
+};
use gpui::{
action,
elements::{Label, MouseEventHandler, UniformList, UniformListState},
@@ -24,6 +27,7 @@ struct EntryDetails {
depth: usize,
is_dir: bool,
is_expanded: bool,
+ is_active: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@@ -48,10 +52,18 @@ impl ProjectPanel {
cx: &mut ViewContext<Self>,
) -> Self {
cx.observe(&project, |this, _, cx| {
- this.update_visible_entries(cx);
+ this.update_visible_entries(false, cx);
cx.notify();
})
.detach();
+ cx.subscribe(&project, |this, _, event, cx| {
+ if let project::Event::ActiveEntryChanged(Some((worktree_id, entry_id))) = event {
+ this.expand_active_entry(*worktree_id, *entry_id, cx);
+ this.update_visible_entries(true, cx);
+ cx.notify();
+ }
+ })
+ .detach();
let mut this = Self {
project,
@@ -61,7 +73,7 @@ impl ProjectPanel {
expanded_dir_ids: Default::default(),
handle: cx.handle().downgrade(),
};
- this.update_visible_entries(cx);
+ this.update_visible_entries(false, cx);
this
}
@@ -79,12 +91,15 @@ impl ProjectPanel {
expanded_dir_ids.insert(ix, entry_id);
}
}
- self.update_visible_entries(cx);
+ self.update_visible_entries(false, cx);
}
- fn update_visible_entries(&mut self, cx: &mut ViewContext<Self>) {
- let worktrees = self.project.read(cx).worktrees();
+ fn update_visible_entries(&mut self, scroll_to_active_entry: bool, cx: &mut ViewContext<Self>) {
+ let project = self.project.read(cx);
+ let worktrees = project.worktrees();
self.visible_entries.clear();
+
+ let mut entry_ix = 0;
for (worktree_ix, worktree) in worktrees.iter().enumerate() {
let snapshot = worktree.read(cx).snapshot();
@@ -98,6 +113,13 @@ impl ProjectPanel {
let mut entry_iter = snapshot.entries(false);
while let Some(item) = entry_iter.entry() {
visible_worktree_entries.push(entry_iter.offset());
+ if scroll_to_active_entry
+ && project.active_entry() == Some((worktree.id(), item.id))
+ {
+ self.list.scroll_to(entry_ix);
+ }
+
+ entry_ix += 1;
if expanded_dir_ids.binary_search(&item.id).is_err() {
if entry_iter.advance_to_sibling() {
continue;
@@ -109,6 +131,40 @@ impl ProjectPanel {
}
}
+ fn expand_active_entry(
+ &mut self,
+ worktree_id: usize,
+ entry_id: usize,
+ cx: &mut ViewContext<Self>,
+ ) {
+ let project = self.project.read(cx);
+ if let Some(worktree) = project.worktree_for_id(worktree_id) {
+ let worktree_ix = project
+ .worktrees()
+ .iter()
+ .position(|w| w.id() == worktree_id)
+ .unwrap();
+ let expanded_dir_ids = &mut self.expanded_dir_ids[worktree_ix];
+ 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(parent_entry) =
+ entry.path.parent().and_then(|p| worktree.entry_for_path(p))
+ {
+ entry = parent_entry;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+
fn append_visible_entries<C: ReadModel, T>(
&self,
range: Range<usize>,
@@ -116,7 +172,9 @@ impl ProjectPanel {
cx: &mut C,
mut render_item: impl FnMut(ProjectEntry, EntryDetails, &mut C) -> T,
) {
- let worktrees = self.project.read(cx).worktrees().to_vec();
+ let project = self.project.read(cx);
+ let active_entry = project.active_entry();
+ let worktrees = project.worktrees().to_vec();
let mut total_ix = 0;
for (worktree_ix, visible_worktree_entries) in self.visible_entries.iter().enumerate() {
if total_ix >= range.end {
@@ -128,7 +186,8 @@ impl ProjectPanel {
}
let expanded_entry_ids = &self.expanded_dir_ids[worktree_ix];
- let snapshot = worktrees[worktree_ix].read(cx).snapshot();
+ let worktree = &worktrees[worktree_ix];
+ let snapshot = worktree.read(cx).snapshot();
let mut cursor = snapshot.entries(false);
for ix in visible_worktree_entries[(range.start - total_ix)..]
.iter()
@@ -144,6 +203,7 @@ impl ProjectPanel {
depth: entry.path.components().count(),
is_dir: entry.is_dir(),
is_expanded: expanded_entry_ids.binary_search(&entry.id).is_ok(),
+ is_active: active_entry == Some((worktree.id(), entry.id)),
};
let entry = ProjectEntry {
worktree_ix,
@@ -167,7 +227,9 @@ impl ProjectPanel {
(entry.worktree_ix, entry.entry_id),
cx,
|state, _| {
- let style = if state.hovered {
+ let style = if details.is_active {
+ &theme.active_entry
+ } else if state.hovered {
&theme.hovered_entry
} else {
&theme.entry
@@ -285,30 +347,35 @@ mod tests {
depth: 0,
is_dir: true,
is_expanded: true,
+ is_active: false,
},
EntryDetails {
filename: ".dockerignore".to_string(),
depth: 1,
is_dir: false,
is_expanded: false,
+ is_active: false,
},
EntryDetails {
filename: "a".to_string(),
depth: 1,
is_dir: true,
is_expanded: false,
+ is_active: false,
},
EntryDetails {
filename: "b".to_string(),
depth: 1,
is_dir: true,
is_expanded: false,
+ is_active: false,
},
EntryDetails {
filename: "c".to_string(),
depth: 1,
is_dir: true,
is_expanded: false,
+ is_active: false,
},
]
);
@@ -322,42 +389,49 @@ mod tests {
depth: 0,
is_dir: true,
is_expanded: true,
+ is_active: false,
},
EntryDetails {
filename: ".dockerignore".to_string(),
depth: 1,
is_dir: false,
is_expanded: false,
+ is_active: false,
},
EntryDetails {
filename: "a".to_string(),
depth: 1,
is_dir: true,
is_expanded: false,
+ is_active: false,
},
EntryDetails {
filename: "b".to_string(),
depth: 1,
is_dir: true,
is_expanded: true,
+ is_active: false,
},
EntryDetails {
filename: "3".to_string(),
depth: 2,
is_dir: true,
is_expanded: false,
+ is_active: false,
},
EntryDetails {
filename: "4".to_string(),
depth: 2,
is_dir: true,
is_expanded: false,
+ is_active: false,
},
EntryDetails {
filename: "c".to_string(),
depth: 1,
is_dir: true,
is_expanded: false,
+ is_active: false,
},
]
);
@@ -371,6 +371,12 @@ impl Workspace {
let pane = cx.add_view(|_| Pane::new(app_state.settings.clone()));
let pane_id = pane.id();
+ cx.observe(&pane, move |me, _, cx| {
+ let active_entry = me.active_entry(cx);
+ me.project
+ .update(cx, |project, cx| project.set_active_entry(active_entry, cx));
+ })
+ .detach();
cx.subscribe(&pane, move |me, _, event, cx| {
me.handle_pane_event(pane_id, event, cx)
})
@@ -725,6 +731,10 @@ impl Workspace {
self.active_pane().read(cx).active_item()
}
+ fn active_entry(&self, cx: &ViewContext<Self>) -> Option<(usize, Arc<Path>)> {
+ self.active_item(cx).and_then(|item| item.entry_id(cx))
+ }
+
pub fn save_active_item(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
if let Some(item) = self.active_item(cx) {
let handle = cx.handle();
@@ -843,6 +853,12 @@ impl Workspace {
fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
let pane = cx.add_view(|_| Pane::new(self.settings.clone()));
let pane_id = pane.id();
+ cx.observe(&pane, move |me, _, cx| {
+ let active_entry = me.active_entry(cx);
+ me.project
+ .update(cx, |project, cx| project.set_active_entry(active_entry, cx));
+ })
+ .detach();
cx.subscribe(&pane, move |me, _, event, cx| {
me.handle_pane_event(pane_id, event, cx)
})