Refine project find's UX
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
51c645f6b4
commit
f649074d36
6 changed files with 73 additions and 31 deletions
|
@ -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()
|
||||||
|
|
|
@ -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 {
|
||||||
|
let model = &self.model.read(cx);
|
||||||
|
let results = if model.highlighted_ranges.is_empty() {
|
||||||
|
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)
|
||||||
|
.flexible(1., true)
|
||||||
|
.boxed()
|
||||||
|
};
|
||||||
|
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.with_child(self.render_query_editor(cx))
|
.with_child(self.render_query_editor(cx))
|
||||||
.with_child(
|
.with_child(results)
|
||||||
ChildView::new(&self.results_editor)
|
|
||||||
.flexible(1., true)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,17 +286,16 @@ 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 theme = &self.settings.borrow().theme.find;
|
let highlighted_ranges = self.model.read(cx).highlighted_ranges.clone();
|
||||||
self.results_editor.update(cx, |editor, cx| {
|
if !highlighted_ranges.is_empty() {
|
||||||
let model = self.model.read(cx);
|
let theme = &self.settings.borrow().theme.find;
|
||||||
editor.highlight_ranges::<Self>(
|
self.results_editor.update(cx, |editor, cx| {
|
||||||
model.highlighted_ranges.clone(),
|
editor.highlight_ranges::<Self>(highlighted_ranges, theme.match_background, cx);
|
||||||
theme.match_background,
|
editor.select_ranges([0..0], Some(Autoscroll::Fit), cx);
|
||||||
cx,
|
});
|
||||||
);
|
cx.focus(&self.results_editor);
|
||||||
editor.select_ranges([0..0], Some(Autoscroll::Fit), cx);
|
}
|
||||||
});
|
cx.notify();
|
||||||
cx.focus(&self.results_editor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_query_editor(&self, cx: &mut RenderContext<Self>) -> ElementBox {
|
fn render_query_editor(&self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||||
|
|
|
@ -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() {
|
||||||
self.stack.push(StackEntry {
|
if !self.at_end {
|
||||||
tree: self.tree,
|
self.stack.push(StackEntry {
|
||||||
index: 0,
|
tree: self.tree,
|
||||||
position: D::default(),
|
index: 0,
|
||||||
});
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue