Refine project find's UX

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2022-02-25 15:27:34 +01:00
parent 51c645f6b4
commit f649074d36
6 changed files with 73 additions and 31 deletions

View file

@ -1175,6 +1175,12 @@ impl MultiBuffer {
let mut buffers = Vec::new(); let mut buffers = Vec::new();
for _ in 0..mutation_count { for _ in 0..mutation_count {
if rng.gen_bool(0.05) {
log::info!("Clearing multi-buffer");
self.clear(cx);
continue;
}
let excerpt_ids = self let excerpt_ids = self
.buffers .buffers
.borrow() .borrow()

View file

@ -30,7 +30,7 @@ pub fn init(cx: &mut MutableAppContext) {
struct ProjectFind { struct ProjectFind {
project: ModelHandle<Project>, project: ModelHandle<Project>,
excerpts: ModelHandle<MultiBuffer>, excerpts: ModelHandle<MultiBuffer>,
pending_search: Task<Option<()>>, pending_search: Option<Task<Option<()>>>,
highlighted_ranges: Vec<Range<Anchor>>, highlighted_ranges: Vec<Range<Anchor>>,
} }
@ -55,7 +55,7 @@ impl ProjectFind {
Self { Self {
project, project,
excerpts: cx.add_model(|_| MultiBuffer::new(replica_id)), excerpts: cx.add_model(|_| MultiBuffer::new(replica_id)),
pending_search: Task::ready(None), pending_search: None,
highlighted_ranges: Default::default(), highlighted_ranges: Default::default(),
} }
} }
@ -64,7 +64,8 @@ impl ProjectFind {
let search = self let search = self
.project .project
.update(cx, |project, cx| project.search(query, cx)); .update(cx, |project, cx| project.search(query, cx));
self.pending_search = cx.spawn_weak(|this, mut cx| async move { self.highlighted_ranges.clear();
self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move {
let matches = search.await; let matches = search.await;
if let Some(this) = this.upgrade(&cx) { if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
@ -84,11 +85,13 @@ impl ProjectFind {
this.highlighted_ranges.extend(ranges_to_highlight); this.highlighted_ranges.extend(ranges_to_highlight);
} }
}); });
this.pending_search.take();
cx.notify(); cx.notify();
}); });
} }
None None
}); }));
cx.notify();
} }
} }
@ -147,13 +150,31 @@ impl View for ProjectFindView {
} }
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox { fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
Flex::column() let model = &self.model.read(cx);
.with_child(self.render_query_editor(cx)) let results = if model.highlighted_ranges.is_empty() {
.with_child( let theme = &self.settings.borrow().theme;
let text = if self.query_editor.read(cx).text(cx).is_empty() {
""
} else if model.pending_search.is_some() {
"Searching..."
} else {
"No results"
};
Label::new(text.to_string(), theme.find.results_status.clone())
.aligned()
.contained()
.with_background_color(theme.editor.background)
.flexible(1., true)
.boxed()
} else {
ChildView::new(&self.results_editor) ChildView::new(&self.results_editor)
.flexible(1., true) .flexible(1., true)
.boxed(), .boxed()
) };
Flex::column()
.with_child(self.render_query_editor(cx))
.with_child(results)
.boxed() .boxed()
} }
@ -265,18 +286,17 @@ impl ProjectFindView {
} }
fn on_model_changed(&mut self, _: ModelHandle<ProjectFind>, cx: &mut ViewContext<Self>) { fn on_model_changed(&mut self, _: ModelHandle<ProjectFind>, cx: &mut ViewContext<Self>) {
let highlighted_ranges = self.model.read(cx).highlighted_ranges.clone();
if !highlighted_ranges.is_empty() {
let theme = &self.settings.borrow().theme.find; let theme = &self.settings.borrow().theme.find;
self.results_editor.update(cx, |editor, cx| { self.results_editor.update(cx, |editor, cx| {
let model = self.model.read(cx); editor.highlight_ranges::<Self>(highlighted_ranges, theme.match_background, cx);
editor.highlight_ranges::<Self>(
model.highlighted_ranges.clone(),
theme.match_background,
cx,
);
editor.select_ranges([0..0], Some(Autoscroll::Fit), cx); editor.select_ranges([0..0], Some(Autoscroll::Fit), cx);
}); });
cx.focus(&self.results_editor); cx.focus(&self.results_editor);
} }
cx.notify();
}
fn render_query_editor(&self, cx: &mut RenderContext<Self>) -> ElementBox { fn render_query_editor(&self, cx: &mut RenderContext<Self>) -> ElementBox {
let theme = &self.settings.borrow().theme; let theme = &self.settings.borrow().theme;

View file

@ -34,13 +34,13 @@ where
stack: ArrayVec::new(), stack: ArrayVec::new(),
position: D::default(), position: D::default(),
did_seek: false, did_seek: false,
at_end: false, at_end: tree.is_empty(),
} }
} }
fn reset(&mut self) { fn reset(&mut self) {
self.did_seek = false; self.did_seek = false;
self.at_end = false; self.at_end = self.tree.is_empty();
self.stack.truncate(0); self.stack.truncate(0);
self.position = D::default(); self.position = D::default();
} }
@ -139,7 +139,7 @@ where
if self.at_end { if self.at_end {
self.position = D::default(); self.position = D::default();
self.descend_to_last_item(self.tree, cx); self.descend_to_last_item(self.tree, cx);
self.at_end = false; self.at_end = self.tree.is_empty();
} else { } else {
while let Some(entry) = self.stack.pop() { while let Some(entry) = self.stack.pop() {
if entry.index > 0 { if entry.index > 0 {
@ -195,13 +195,15 @@ where
{ {
let mut descend = false; let mut descend = false;
if self.stack.is_empty() && !self.at_end { if self.stack.is_empty() {
if !self.at_end {
self.stack.push(StackEntry { self.stack.push(StackEntry {
tree: self.tree, tree: self.tree,
index: 0, index: 0,
position: D::default(), position: D::default(),
}); });
descend = true; descend = true;
}
self.did_seek = true; self.did_seek = true;
} }
@ -279,6 +281,10 @@ where
cx: &<T::Summary as Summary>::Context, cx: &<T::Summary as Summary>::Context,
) { ) {
self.did_seek = true; self.did_seek = true;
if subtree.is_empty() {
return;
}
loop { loop {
match subtree.0.as_ref() { match subtree.0.as_ref() {
Node::Internal { Node::Internal {
@ -298,7 +304,7 @@ where
subtree = child_trees.last().unwrap(); subtree = child_trees.last().unwrap();
} }
Node::Leaf { item_summaries, .. } => { Node::Leaf { item_summaries, .. } => {
let last_index = item_summaries.len().saturating_sub(1); let last_index = item_summaries.len() - 1;
for item_summary in &item_summaries[0..last_index] { for item_summary in &item_summaries[0..last_index] {
self.position.add_summary(item_summary, cx); self.position.add_summary(item_summary, cx);
} }

View file

@ -821,6 +821,14 @@ mod tests {
assert_eq!(cursor.item(), None); assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), None); assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.start().sum, 0); assert_eq!(cursor.start().sum, 0);
cursor.prev(&());
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.start().sum, 0);
cursor.next(&());
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.start().sum, 0);
// Single-element tree // Single-element tree
let mut tree = SumTree::<u8>::new(); let mut tree = SumTree::<u8>::new();

View file

@ -107,6 +107,7 @@ pub struct Find {
pub active_hovered_option_button: ContainedText, pub active_hovered_option_button: ContainedText,
pub match_background: Color, pub match_background: Color,
pub match_index: ContainedText, pub match_index: ContainedText,
pub results_status: TextStyle,
} }
#[derive(Clone, Deserialize, Default)] #[derive(Clone, Deserialize, Default)]

View file

@ -351,6 +351,7 @@ tab_summary_spacing = 10
[find] [find]
match_background = "$state.highlighted_line" match_background = "$state.highlighted_line"
background = "$surface.1" background = "$surface.1"
results_status = { extends = "$text.0", size = 18 }
[find.option_button] [find.option_button]
extends = "$text.1" extends = "$text.1"