From 3bbc021a7eee040e415923a6ac2211df3f7e32c2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 15 Apr 2022 14:28:01 -0700 Subject: [PATCH] Use Picker in Outline view --- Cargo.lock | 1 + assets/keymaps/default.json | 12 - crates/file_finder/src/file_finder.rs | 7 +- crates/outline/Cargo.toml | 1 + crates/outline/src/outline.rs | 226 +++++------------- crates/picker/src/picker.rs | 112 +++++---- crates/project_symbols/src/project_symbols.rs | 9 +- 7 files changed, 131 insertions(+), 237 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 72fefd90ec..0a4540ba39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3374,6 +3374,7 @@ dependencies = [ "gpui", "language", "ordered-float", + "picker", "postage", "settings", "smol", diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index e517f853d5..506dd362d4 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -250,22 +250,10 @@ "\n" ] }, - "OutlineView": { - "escape": "outline::Toggle" - }, - "ProjectSymbolsView": { - "escape": "project_symbols::Toggle" - }, - "ThemeSelector": { - "escape": "theme_selector::Toggle" - }, "GoToLine": { "escape": "go_to_line::Toggle", "enter": "go_to_line::Confirm" }, - "FileFinder": { - "escape": "file_finder::Toggle" - }, "ChatPanel": { "enter": "chat_panel::Send" }, diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 755ba3dd76..9877cef3d8 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -113,14 +113,11 @@ impl FileFinder { } pub fn new(project: ModelHandle, cx: &mut ViewContext) -> Self { - cx.observe(&project, Self::project_updated).detach(); - let handle = cx.weak_handle(); - let picker = cx.add_view(|cx| Picker::new(handle, cx)); - + cx.observe(&project, Self::project_updated).detach(); Self { project, - picker, + picker: cx.add_view(|cx| Picker::new(handle, cx)), search_count: 0, latest_search_id: 0, latest_search_did_cancel: false, diff --git a/crates/outline/Cargo.toml b/crates/outline/Cargo.toml index fc9444a14c..5b4751e620 100644 --- a/crates/outline/Cargo.toml +++ b/crates/outline/Cargo.toml @@ -12,6 +12,7 @@ editor = { path = "../editor" } fuzzy = { path = "../fuzzy" } gpui = { path = "../gpui" } language = { path = "../language" } +picker = { path = "../picker" } settings = { path = "../settings" } text = { path = "../text" } workspace = { path = "../workspace" } diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 08bc18a016..a5492a8f69 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -4,38 +4,31 @@ use editor::{ }; use fuzzy::StringMatch; use gpui::{ - actions, elements::*, geometry::vector::Vector2F, keymap, AppContext, Axis, Entity, - MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle, + actions, elements::*, geometry::vector::Vector2F, AppContext, Entity, MutableAppContext, + RenderContext, Task, View, ViewContext, ViewHandle, }; use language::Outline; use ordered_float::OrderedFloat; +use picker::{Picker, PickerDelegate}; use settings::Settings; use std::cmp::{self, Reverse}; -use workspace::{ - menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev}, - Workspace, -}; +use workspace::Workspace; actions!(outline, [Toggle]); pub fn init(cx: &mut MutableAppContext) { cx.add_action(OutlineView::toggle); - cx.add_action(OutlineView::confirm); - cx.add_action(OutlineView::select_prev); - cx.add_action(OutlineView::select_next); - cx.add_action(OutlineView::select_first); - cx.add_action(OutlineView::select_last); + Picker::::init(cx); } struct OutlineView { - handle: WeakViewHandle, + picker: ViewHandle>, active_editor: ViewHandle, outline: Outline, selected_match_index: usize, prev_scroll_position: Option, matches: Vec, - query_editor: ViewHandle, - list_state: UniformListState, + last_query: String, } pub enum Event { @@ -55,38 +48,12 @@ impl View for OutlineView { "OutlineView" } - 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) -> ElementBox { - let settings = cx.global::(); - - 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.0, false) - .boxed(), - ) - .contained() - .with_style(settings.theme.selector.container) - .constrained() - .with_max_width(800.0) - .with_max_height(1200.0) - .aligned() - .top() - .named("outline view") + fn render(&mut self, _: &mut RenderContext) -> ElementBox { + ChildView::new(self.picker.clone()).boxed() } fn on_focus(&mut self, cx: &mut ViewContext) { - cx.focus(&self.query_editor); + cx.focus(&self.picker); } } @@ -96,24 +63,16 @@ impl OutlineView { editor: ViewHandle, cx: &mut ViewContext, ) -> 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 mut this = Self { - handle: cx.weak_handle(), + let handle = cx.weak_handle(); + Self { + picker: cx.add_view(|cx| Picker::new(handle, cx).with_max_size(800., 1200.)), + last_query: Default::default(), matches: Default::default(), selected_match_index: 0, prev_scroll_position: Some(editor.update(cx, |editor, cx| editor.scroll_position(cx))), active_editor: editor, outline, - query_editor, - list_state: Default::default(), - }; - this.update_matches(cx); - this + } } fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { @@ -137,34 +96,18 @@ impl OutlineView { } } - fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext) { - if self.selected_match_index > 0 { - self.select(self.selected_match_index - 1, true, false, cx); - } + fn restore_active_editor(&mut self, cx: &mut MutableAppContext) { + self.active_editor.update(cx, |editor, cx| { + editor.highlight_rows(None); + if let Some(scroll_position) = self.prev_scroll_position { + editor.set_scroll_position(scroll_position, cx); + } + }) } - fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext) { - if self.selected_match_index + 1 < self.matches.len() { - self.select(self.selected_match_index + 1, true, false, cx); - } - } - - fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext) { - self.select(0, true, false, cx); - } - - fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext) { - self.select(self.matches.len().saturating_sub(1), true, false, cx); - } - - fn select(&mut self, index: usize, navigate: bool, center: bool, cx: &mut ViewContext) { - self.selected_match_index = index; - self.list_state.scroll_to(if center { - ScrollTarget::Center(index) - } else { - ScrollTarget::Show(index) - }); - if navigate { + fn set_selected_index(&mut self, ix: usize, navigate: bool, cx: &mut ViewContext) { + self.selected_match_index = ix; + if navigate && !self.matches.is_empty() { let selected_match = &self.matches[self.selected_match_index]; let outline_item = &self.outline.items[selected_match.candidate_id]; self.active_editor.update(cx, |active_editor, cx| { @@ -181,27 +124,6 @@ impl OutlineView { cx.notify(); } - fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { - self.prev_scroll_position.take(); - self.active_editor.update(cx, |active_editor, cx| { - if let Some(rows) = active_editor.highlighted_rows() { - let snapshot = active_editor.snapshot(cx).display_snapshot; - let position = DisplayPoint::new(rows.start, 0).to_point(&snapshot); - active_editor.select_ranges([position..position], Some(Autoscroll::Center), cx); - } - }); - cx.emit(Event::Dismissed); - } - - fn restore_active_editor(&mut self, cx: &mut MutableAppContext) { - self.active_editor.update(cx, |editor, cx| { - editor.highlight_rows(None); - if let Some(scroll_position) = self.prev_scroll_position { - editor.set_scroll_position(scroll_position, cx); - } - }) - } - fn on_event( workspace: &mut Workspace, _: ViewHandle, @@ -212,24 +134,27 @@ impl OutlineView { Event::Dismissed => workspace.dismiss_modal(cx), } } +} - fn on_query_editor_event( - &mut self, - _: ViewHandle, - event: &editor::Event, - cx: &mut ViewContext, - ) { - match event { - editor::Event::Blurred => cx.emit(Event::Dismissed), - editor::Event::BufferEdited { .. } => self.update_matches(cx), - _ => {} - } +impl PickerDelegate for OutlineView { + fn match_count(&self) -> usize { + self.matches.len() } - fn update_matches(&mut self, cx: &mut ViewContext) { + fn selected_index(&self) -> usize { + self.selected_match_index + } + + fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext) { + self.set_selected_index(ix, true, cx); + } + + fn center_selection_after_match_updates(&self) -> bool { + true + } + + fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { let selected_index; - let navigate_to_selected_index; - let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx)); if query.is_empty() { self.restore_active_editor(cx); self.matches = self @@ -271,7 +196,6 @@ impl OutlineView { .max_by_key(|(_, depth, distance)| (*depth, Reverse(*distance))) .map(|(ix, _, _)| ix) .unwrap_or(0); - navigate_to_selected_index = false; } else { self.matches = smol::block_on(self.outline.search(&query, cx.background().clone())); selected_index = self @@ -281,57 +205,33 @@ impl OutlineView { .max_by_key(|(_, m)| OrderedFloat(m.score)) .map(|(ix, _)| ix) .unwrap_or(0); - navigate_to_selected_index = !self.matches.is_empty(); } - self.select(selected_index, navigate_to_selected_index, true, cx); + self.last_query = query; + self.set_selected_index(selected_index, false, cx); + Task::ready(()) } - fn render_matches(&self, cx: &AppContext) -> ElementBox { - if self.matches.is_empty() { - let settings = cx.global::(); - 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"); - } - - 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()); - items.extend( - view.matches[range] - .iter() - .enumerate() - .map(move |(ix, m)| view.render_match(m, start + ix, cx)), - ); - }, - ); - - Container::new(list.boxed()) - .with_margin_top(6.0) - .named("matches") + fn confirm(&mut self, cx: &mut ViewContext) { + self.prev_scroll_position.take(); + self.active_editor.update(cx, |active_editor, cx| { + if let Some(rows) = active_editor.highlighted_rows() { + let snapshot = active_editor.snapshot(cx).display_snapshot; + let position = DisplayPoint::new(rows.start, 0).to_point(&snapshot); + active_editor.select_ranges([position..position], Some(Autoscroll::Center), cx); + } + }); + cx.emit(Event::Dismissed); } - fn render_match( - &self, - string_match: &StringMatch, - index: usize, - cx: &AppContext, - ) -> ElementBox { + fn dismiss(&mut self, cx: &mut ViewContext) { + self.restore_active_editor(cx); + cx.emit(Event::Dismissed); + } + + fn render_match(&self, ix: usize, selected: bool, cx: &AppContext) -> ElementBox { let settings = cx.global::(); - let style = if index == self.selected_match_index { + let string_match = &self.matches[ix]; + let style = if selected { &settings.theme.selector.active_item } else { &settings.theme.selector.item diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 9feb944e3a..76b9d99d29 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -1,9 +1,10 @@ use editor::Editor; use gpui::{ elements::{ - ChildView, EventHandler, Flex, FlexItem, Label, ParentElement, ScrollTarget, UniformList, + ChildView, EventHandler, Flex, Label, ParentElement, ScrollTarget, UniformList, UniformListState, }, + geometry::vector::{vec2f, Vector2F}, keymap, AppContext, Axis, Element, ElementBox, Entity, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; @@ -18,6 +19,7 @@ pub struct Picker { query_editor: ViewHandle, list_state: UniformListState, update_task: Option>, + max_size: Vector2F, } pub trait PickerDelegate: View { @@ -28,6 +30,9 @@ pub trait PickerDelegate: View { fn confirm(&mut self, cx: &mut ViewContext); fn dismiss(&mut self, cx: &mut ViewContext); fn render_match(&self, ix: usize, selected: bool, cx: &AppContext) -> ElementBox; + fn center_selection_after_match_updates(&self) -> bool { + false + } } impl Entity for Picker { @@ -41,6 +46,13 @@ impl View for Picker { fn render(&mut self, cx: &mut RenderContext) -> gpui::ElementBox { let settings = cx.global::(); + let delegate = self.delegate.clone(); + let match_count = if let Some(delegate) = delegate.upgrade(cx.app) { + delegate.read(cx).match_count() + } else { + 0 + }; + Flex::new(Axis::Vertical) .with_child( ChildView::new(&self.query_editor) @@ -49,15 +61,44 @@ impl View for Picker { .boxed(), ) .with_child( - FlexItem::new(self.render_matches(cx)) - .flex(1., false) - .boxed(), + if match_count == 0 { + Label::new( + "No matches".into(), + settings.theme.selector.empty.label.clone(), + ) + .contained() + .with_style(settings.theme.selector.empty.container) + } else { + UniformList::new( + self.list_state.clone(), + match_count, + move |mut range, items, cx| { + let cx = cx.as_ref(); + let delegate = delegate.upgrade(cx).unwrap(); + let delegate = delegate.read(cx); + let selected_ix = delegate.selected_index(); + range.end = cmp::min(range.end, delegate.match_count()); + items.extend(range.map(move |ix| { + EventHandler::new(delegate.render_match(ix, ix == selected_ix, cx)) + .on_mouse_down(move |cx| { + cx.dispatch_action(SelectIndex(ix)); + true + }) + .boxed() + })); + }, + ) + .contained() + .with_margin_top(6.0) + } + .flex(1., false) + .boxed(), ) .contained() .with_style(settings.theme.selector.container) .constrained() - .with_max_width(500.0) - .with_max_height(420.0) + .with_max_width(self.max_size.x()) + .with_max_height(self.max_size.y()) .aligned() .top() .named("picker") @@ -91,57 +132,20 @@ impl Picker { }); cx.subscribe(&query_editor, Self::on_query_editor_event) .detach(); - let mut this = Self { + let this = Self { query_editor, list_state: Default::default(), update_task: None, delegate, + max_size: vec2f(500., 420.), }; - this.update_matches(cx); + cx.defer(|this, cx| this.update_matches(cx)); this } - fn render_matches(&self, cx: &AppContext) -> ElementBox { - let delegate = self.delegate.clone(); - let match_count = if let Some(delegate) = delegate.upgrade(cx) { - delegate.read(cx).match_count() - } else { - 0 - }; - - if match_count == 0 { - let settings = cx.global::(); - return Label::new( - "No matches".into(), - settings.theme.selector.empty.label.clone(), - ) - .contained() - .with_style(settings.theme.selector.empty.container) - .named("empty matches"); - } - - UniformList::new( - self.list_state.clone(), - match_count, - move |mut range, items, cx| { - let cx = cx.as_ref(); - let delegate = delegate.upgrade(cx).unwrap(); - let delegate = delegate.read(cx); - let selected_ix = delegate.selected_index(); - range.end = cmp::min(range.end, delegate.match_count()); - items.extend(range.map(move |ix| { - EventHandler::new(delegate.render_match(ix, ix == selected_ix, cx)) - .on_mouse_down(move |cx| { - cx.dispatch_action(SelectIndex(ix)); - true - }) - .boxed() - })); - }, - ) - .contained() - .with_margin_top(6.0) - .named("matches") + pub fn with_max_size(mut self, width: f32, height: f32) -> Self { + self.max_size = vec2f(width, height); + self } fn on_query_editor_event( @@ -172,8 +176,14 @@ impl Picker { update.await; this.update(&mut cx, |this, cx| { if let Some(delegate) = this.delegate.upgrade(cx) { - let index = delegate.read(cx).selected_index(); - this.list_state.scroll_to(ScrollTarget::Show(index)); + let delegate = delegate.read(cx); + let index = delegate.selected_index(); + let target = if delegate.center_selection_after_match_updates() { + ScrollTarget::Center(index) + } else { + ScrollTarget::Show(index) + }; + this.list_state.scroll_to(target); cx.notify(); this.update_task.take(); } diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 2c048c4b7e..8e89951a00 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -57,18 +57,15 @@ impl View for ProjectSymbolsView { impl ProjectSymbolsView { fn new(project: ModelHandle, cx: &mut ViewContext) -> Self { let handle = cx.weak_handle(); - let picker = cx.add_view(|cx| Picker::new(handle, cx)); - let mut this = Self { - picker, + Self { project, + picker: cx.add_view(|cx| Picker::new(handle, cx)), selected_match_index: 0, symbols: Default::default(), match_candidates: Default::default(), matches: Default::default(), show_worktree_root_name: false, - }; - this.update_matches(String::new(), cx).detach(); - this + } } fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) {