Cargo.lock 🔗
@@ -3794,6 +3794,7 @@ dependencies = [
"fuzzy",
"gpui",
"ordered-float",
+ "picker",
"postage",
"project",
"settings",
Max Brunsfeld created
Cargo.lock | 1
crates/picker/src/picker.rs | 46 +-
crates/project_symbols/Cargo.toml | 1
crates/project_symbols/src/project_symbols.rs | 331 +++++++-------------
4 files changed, 145 insertions(+), 234 deletions(-)
@@ -3794,6 +3794,7 @@ dependencies = [
"fuzzy",
"gpui",
"ordered-float",
+ "picker",
"postage",
"project",
"settings",
@@ -15,6 +15,7 @@ pub struct Picker<D: PickerDelegate> {
delegate: WeakViewHandle<D>,
query_editor: ViewHandle<Editor>,
list_state: UniformListState,
+ update_task: Option<Task<()>>,
}
pub trait PickerDelegate: View {
@@ -87,12 +88,14 @@ impl<D: PickerDelegate> Picker<D> {
});
cx.subscribe(&query_editor, Self::on_query_editor_event)
.detach();
-
- Self {
- delegate,
+ let mut this = Self {
query_editor,
list_state: Default::default(),
- }
+ update_task: None,
+ delegate,
+ };
+ this.update_matches(cx);
+ this
}
fn render_matches(&self, cx: &AppContext) -> ElementBox {
@@ -137,22 +140,31 @@ impl<D: PickerDelegate> Picker<D> {
event: &editor::Event,
cx: &mut ViewContext<Self>,
) {
- if let Some(delegate) = self.delegate.upgrade(cx) {
- match event {
- editor::Event::BufferEdited { .. } => {
- let query = self.query_editor.read(cx).text(cx);
- let update = delegate.update(cx, |d, cx| d.update_matches(query, cx));
- cx.spawn(|this, mut cx| async move {
- update.await;
- this.update(&mut cx, |_, cx| cx.notify());
+ match event {
+ editor::Event::BufferEdited { .. } => self.update_matches(cx),
+ editor::Event::Blurred => {
+ if let Some(delegate) = self.delegate.upgrade(cx) {
+ delegate.update(cx, |delegate, cx| {
+ delegate.dismiss(cx);
})
- .detach();
}
- editor::Event::Blurred => delegate.update(cx, |delegate, cx| {
- delegate.dismiss(cx);
- }),
- _ => {}
}
+ _ => {}
+ }
+ }
+
+ fn update_matches(&mut self, cx: &mut ViewContext<Self>) {
+ if let Some(delegate) = self.delegate.upgrade(cx) {
+ let query = self.query_editor.read(cx).text(cx);
+ let update = delegate.update(cx, |d, cx| d.update_matches(query, cx));
+ cx.notify();
+ self.update_task = Some(cx.spawn(|this, mut cx| async move {
+ update.await;
+ this.update(&mut cx, |this, cx| {
+ cx.notify();
+ this.update_task.take();
+ });
+ }));
}
}
@@ -11,6 +11,7 @@ doctest = false
editor = { path = "../editor" }
fuzzy = { path = "../fuzzy" }
gpui = { path = "../gpui" }
+picker = { path = "../picker" }
project = { path = "../project" }
text = { path = "../text" }
settings = { path = "../settings" }
@@ -3,43 +3,32 @@ use editor::{
};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
- actions, elements::*, keymap, AppContext, Axis, Entity, ModelHandle, MutableAppContext,
- RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+ actions, elements::*, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Task,
+ View, ViewContext, ViewHandle,
};
use ordered_float::OrderedFloat;
+use picker::{Picker, PickerDelegate};
use project::{Project, Symbol};
use settings::Settings;
-use std::{
- borrow::Cow,
- cmp::{self, Reverse},
-};
+use std::{borrow::Cow, cmp::Reverse};
use util::ResultExt;
-use workspace::{
- menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev},
- Workspace,
-};
+use workspace::Workspace;
actions!(project_symbols, [Toggle]);
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(ProjectSymbolsView::toggle);
- cx.add_action(ProjectSymbolsView::confirm);
- cx.add_action(ProjectSymbolsView::select_prev);
- cx.add_action(ProjectSymbolsView::select_next);
- cx.add_action(ProjectSymbolsView::select_first);
- cx.add_action(ProjectSymbolsView::select_last);
+ Picker::<ProjectSymbolsView>::init(cx);
}
pub struct ProjectSymbolsView {
- handle: WeakViewHandle<Self>,
+ picker: ViewHandle<Picker<Self>>,
project: ModelHandle<Project>,
selected_match_index: usize,
- list_state: UniformListState,
symbols: Vec<Symbol>,
match_candidates: Vec<StringMatchCandidate>,
+ show_worktree_root_name: bool,
matches: Vec<StringMatch>,
- pending_symbols_task: Task<Option<()>>,
- query_editor: ViewHandle<Editor>,
}
pub enum Event {
@@ -56,59 +45,29 @@ impl View for ProjectSymbolsView {
"ProjectSymbolsView"
}
- fn keymap_context(&self, _: &AppContext) -> keymap::Context {
- let mut cx = Self::default_keymap_context();
- cx.set.insert("menu".into());
- cx
- }
-
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
- let settings = cx.global::<Settings>();
- Flex::new(Axis::Vertical)
- .with_child(
- Container::new(ChildView::new(&self.query_editor).boxed())
- .with_style(settings.theme.selector.input_editor.container)
- .boxed(),
- )
- .with_child(
- FlexItem::new(self.render_matches(cx))
- .flex(1., false)
- .boxed(),
- )
- .contained()
- .with_style(settings.theme.selector.container)
- .constrained()
- .with_max_width(500.0)
- .with_max_height(420.0)
- .aligned()
- .top()
- .named("project symbols view")
+ fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
+ ChildView::new(self.picker.clone()).boxed()
}
fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
- cx.focus(&self.query_editor);
+ cx.focus(&self.picker);
}
}
impl ProjectSymbolsView {
fn new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
- let query_editor = cx.add_view(|cx| {
- Editor::single_line(Some(|theme| theme.selector.input_editor.clone()), cx)
- });
- cx.subscribe(&query_editor, Self::on_query_editor_event)
- .detach();
+ let handle = cx.weak_handle();
+ let picker = cx.add_view(|cx| Picker::new(handle, cx));
let mut this = Self {
- handle: cx.weak_handle(),
+ picker,
project,
selected_match_index: 0,
- list_state: Default::default(),
symbols: Default::default(),
match_candidates: Default::default(),
matches: Default::default(),
- pending_symbols_task: Task::ready(None),
- query_editor,
+ show_worktree_root_name: false,
};
- this.update_matches(cx);
+ this.update_matches(String::new(), cx).detach();
this
}
@@ -121,72 +80,7 @@ impl ProjectSymbolsView {
});
}
- fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
- if self.selected_match_index > 0 {
- self.select(self.selected_match_index - 1, cx);
- }
- }
-
- fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
- if self.selected_match_index + 1 < self.matches.len() {
- self.select(self.selected_match_index + 1, cx);
- }
- }
-
- fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
- self.select(0, cx);
- }
-
- fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
- self.select(self.matches.len().saturating_sub(1), cx);
- }
-
- fn select(&mut self, index: usize, cx: &mut ViewContext<Self>) {
- self.selected_match_index = index;
- self.list_state.scroll_to(ScrollTarget::Show(index));
- cx.notify();
- }
-
- fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
- if let Some(symbol) = self
- .matches
- .get(self.selected_match_index)
- .map(|mat| self.symbols[mat.candidate_id].clone())
- {
- cx.emit(Event::Selected(symbol));
- }
- }
-
- fn update_matches(&mut self, cx: &mut ViewContext<Self>) {
- self.filter(cx);
- let query = self.query_editor.read(cx).text(cx);
- let symbols = self
- .project
- .update(cx, |project, cx| project.symbols(&query, cx));
- self.pending_symbols_task = cx.spawn_weak(|this, mut cx| async move {
- let symbols = symbols.await.log_err()?;
- if let Some(this) = this.upgrade(&cx) {
- this.update(&mut cx, |this, cx| {
- this.match_candidates = symbols
- .iter()
- .enumerate()
- .map(|(id, symbol)| {
- StringMatchCandidate::new(
- id,
- symbol.label.text[symbol.label.filter_range.clone()].to_string(),
- )
- })
- .collect();
- this.symbols = symbols;
- this.filter(cx);
- });
- }
- None
- });
- }
-
- fn filter(&mut self, cx: &mut ViewContext<Self>) {
- let query = self.query_editor.read(cx).text(cx);
+ fn filter(&mut self, query: &str, cx: &mut ViewContext<Self>) {
let mut matches = if query.is_empty() {
self.match_candidates
.iter()
@@ -201,7 +95,7 @@ impl ProjectSymbolsView {
} else {
smol::block_on(fuzzy::match_strings(
&self.match_candidates,
- &query,
+ query,
false,
100,
&Default::default(),
@@ -225,57 +119,111 @@ impl ProjectSymbolsView {
}
self.matches = matches;
- self.select_first(&SelectFirst, cx);
+ self.set_selected_index(0, cx);
cx.notify();
}
- fn render_matches(&self, cx: &AppContext) -> ElementBox {
- if self.matches.is_empty() {
- let settings = cx.global::<Settings>();
- return Container::new(
- Label::new(
- "No matches".into(),
- settings.theme.selector.empty.label.clone(),
- )
- .boxed(),
- )
- .with_style(settings.theme.selector.empty.container)
- .named("empty matches");
+ fn on_event(
+ workspace: &mut Workspace,
+ _: ViewHandle<Self>,
+ event: &Event,
+ cx: &mut ViewContext<Workspace>,
+ ) {
+ match event {
+ Event::Dismissed => workspace.dismiss_modal(cx),
+ Event::Selected(symbol) => {
+ let buffer = workspace
+ .project()
+ .update(cx, |project, cx| project.open_buffer_for_symbol(symbol, cx));
+
+ let symbol = symbol.clone();
+ cx.spawn(|workspace, mut cx| async move {
+ let buffer = buffer.await?;
+ workspace.update(&mut cx, |workspace, cx| {
+ let position = buffer
+ .read(cx)
+ .clip_point_utf16(symbol.range.start, Bias::Left);
+
+ let editor = workspace.open_project_item::<Editor>(buffer, cx);
+ editor.update(cx, |editor, cx| {
+ editor.select_ranges(
+ [position..position],
+ Some(Autoscroll::Center),
+ cx,
+ );
+ });
+ });
+ Ok::<_, anyhow::Error>(())
+ })
+ .detach_and_log_err(cx);
+ workspace.dismiss_modal(cx);
+ }
}
+ }
+}
- let handle = self.handle.clone();
- let list = UniformList::new(
- self.list_state.clone(),
- self.matches.len(),
- move |mut range, items, cx| {
- let cx = cx.as_ref();
- let view = handle.upgrade(cx).unwrap();
- let view = view.read(cx);
- let start = range.start;
- range.end = cmp::min(range.end, view.matches.len());
+impl PickerDelegate for ProjectSymbolsView {
+ fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+ if let Some(symbol) = self
+ .matches
+ .get(self.selected_match_index)
+ .map(|mat| self.symbols[mat.candidate_id].clone())
+ {
+ cx.emit(Event::Selected(symbol));
+ }
+ }
+
+ fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+ cx.emit(Event::Dismissed);
+ }
- let show_worktree_root_name =
- view.project.read(cx).visible_worktrees(cx).count() > 1;
- items.extend(view.matches[range].iter().enumerate().map(move |(ix, m)| {
- view.render_match(m, start + ix, show_worktree_root_name, cx)
- }));
- },
- );
+ fn match_count(&self) -> usize {
+ self.matches.len()
+ }
- Container::new(list.boxed())
- .with_margin_top(6.0)
- .named("matches")
+ fn selected_index(&self) -> usize {
+ self.selected_match_index
}
- fn render_match(
- &self,
- string_match: &StringMatch,
- index: usize,
- show_worktree_root_name: bool,
- cx: &AppContext,
- ) -> ElementBox {
+ fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
+ self.selected_match_index = ix;
+ cx.notify();
+ }
+
+ fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
+ self.filter(&query, cx);
+ self.show_worktree_root_name = self.project.read(cx).visible_worktrees(cx).count() > 1;
+ let symbols = self
+ .project
+ .update(cx, |project, cx| project.symbols(&query, cx));
+ cx.spawn_weak(|this, mut cx| async move {
+ let symbols = symbols.await.log_err();
+ if let Some(this) = this.upgrade(&cx) {
+ if let Some(symbols) = symbols {
+ this.update(&mut cx, |this, cx| {
+ this.match_candidates = symbols
+ .iter()
+ .enumerate()
+ .map(|(id, symbol)| {
+ StringMatchCandidate::new(
+ id,
+ symbol.label.text[symbol.label.filter_range.clone()]
+ .to_string(),
+ )
+ })
+ .collect();
+ this.symbols = symbols;
+ this.filter(&query, cx);
+ });
+ }
+ }
+ })
+ }
+
+ fn render_match(&self, ix: usize, selected: bool, cx: &AppContext) -> ElementBox {
+ let string_match = &self.matches[ix];
let settings = cx.global::<Settings>();
- let style = if index == self.selected_match_index {
+ let style = if selected {
&settings.theme.selector.active_item
} else {
&settings.theme.selector.item
@@ -284,7 +232,7 @@ impl ProjectSymbolsView {
let syntax_runs = styled_runs_for_code_label(&symbol.label, &settings.theme.editor.syntax);
let mut path = symbol.path.to_string_lossy();
- if show_worktree_root_name {
+ if self.show_worktree_root_name {
let project = self.project.read(cx);
if let Some(worktree) = project.worktree_for_id(symbol.worktree_id, cx) {
path = Cow::Owned(format!(
@@ -317,55 +265,4 @@ impl ProjectSymbolsView {
.with_style(style.container)
.boxed()
}
-
- fn on_query_editor_event(
- &mut self,
- _: ViewHandle<Editor>,
- event: &editor::Event,
- cx: &mut ViewContext<Self>,
- ) {
- match event {
- editor::Event::Blurred => cx.emit(Event::Dismissed),
- editor::Event::BufferEdited { .. } => self.update_matches(cx),
- _ => {}
- }
- }
-
- fn on_event(
- workspace: &mut Workspace,
- _: ViewHandle<Self>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- match event {
- Event::Dismissed => workspace.dismiss_modal(cx),
- Event::Selected(symbol) => {
- let buffer = workspace
- .project()
- .update(cx, |project, cx| project.open_buffer_for_symbol(symbol, cx));
-
- let symbol = symbol.clone();
- cx.spawn(|workspace, mut cx| async move {
- let buffer = buffer.await?;
- workspace.update(&mut cx, |workspace, cx| {
- let position = buffer
- .read(cx)
- .clip_point_utf16(symbol.range.start, Bias::Left);
-
- let editor = workspace.open_project_item::<Editor>(buffer, cx);
- editor.update(cx, |editor, cx| {
- editor.select_ranges(
- [position..position],
- Some(Autoscroll::Center),
- cx,
- );
- });
- });
- Ok::<_, anyhow::Error>(())
- })
- .detach_and_log_err(cx);
- workspace.dismiss_modal(cx);
- }
- }
- }
}