Maintain active entry Project and render it in ProjectPanel

This commit is contained in:
Antonio Scandurra 2021-09-29 16:50:33 +02:00
parent 67c40eb4be
commit 1519e1d45f
5 changed files with 129 additions and 10 deletions

View File

@ -172,6 +172,10 @@ padding = { top = 3, bottom = 3 }
extends = "$project_panel.entry"
background = "$state.hover"
[project_panel.active_entry]
extends = "$project_panel.entry"
background = "#ff0000"
[selector]
background = "$surface.0"
padding = 8

View File

@ -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| {

View File

@ -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,
},
]
);

View File

@ -114,6 +114,7 @@ pub struct ProjectPanel {
pub entry_base_padding: f32,
pub entry: ContainedText,
pub hovered_entry: ContainedText,
pub active_entry: ContainedText,
}
#[derive(Deserialize)]

View File

@ -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)
})