mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-12 05:15:00 +00:00
Maintain active entry Project
and render it in ProjectPanel
This commit is contained in:
parent
67c40eb4be
commit
1519e1d45f
5 changed files with 129 additions and 10 deletions
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
]
|
||||
);
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue