diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 5e47de01dd..f903d9e493 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -12,7 +12,7 @@ use workspace::Workspace; pub fn init(cx: &mut AppContext) { cx.add_action(toggle_command_palette); - Picker::::init(cx); + CommandPalette::init(cx); } actions!(command_palette, [Toggle]); @@ -155,9 +155,7 @@ impl PickerDelegate for CommandPaletteDelegate { }) } - fn dismissed(&mut self, cx: &mut ViewContext>) { - cx.emit(PickerEvent::Dismiss); - } + fn dismissed(&mut self, _cx: &mut ViewContext>) {} fn confirm(&mut self, cx: &mut ViewContext>) { if !self.matches.is_empty() { @@ -330,8 +328,8 @@ mod tests { .await; palette.update(cx, |palette, cx| { - assert_eq!(palette.matches[0].string, "editor: backspace"); - palette.confirm(cx); + assert_eq!(palette.delegate().matches[0].string, "editor: backspace"); + palette.confirm(&Default::default(), cx); }); editor.read_with(cx, |editor, cx| { @@ -346,20 +344,24 @@ mod tests { }); workspace.update(cx, |workspace, cx| { - CommandPaletteDelegate::toggle(workspace, &Toggle, cx); + toggle_command_palette(workspace, &Toggle, cx); }); // Assert editor command not present let palette = workspace.read_with(cx, |workspace, _| { - workspace.modal::().unwrap() + workspace.modal::().unwrap() }); palette .update(cx, |palette, cx| { - palette.update_matches("bcksp".to_string(), cx) + palette + .delegate_mut() + .update_matches("bcksp".to_string(), cx) }) .await; - palette.update(cx, |palette, _| assert!(palette.matches.is_empty())); + palette.update(cx, |palette, _| { + assert!(palette.delegate().matches.is_empty()) + }); } } diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 220b3f124a..4a5c733a2b 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -94,8 +94,7 @@ impl FileFinderDelegate { cx: &mut ViewContext, ) -> Self { cx.observe(&project, |picker, _, cx| { - let query = picker.query(cx); - picker.delegate_mut().spawn_search(query, cx).detach(); + picker.update_matches(picker.query(cx), cx); }) .detach(); Self { @@ -366,16 +365,21 @@ mod tests { let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let (_, finder) = cx.add_window(|cx| { Picker::new( - FileFinderDelegate::new(workspace.read(cx).project().clone(), None, cx), + FileFinderDelegate::new( + workspace.downgrade(), + workspace.read(cx).project().clone(), + None, + cx, + ), cx, ) }); let query = "hi".to_string(); finder - .update(cx, |f, cx| f.spawn_search(query.clone(), cx)) + .update(cx, |f, cx| f.delegate_mut().spawn_search(query.clone(), cx)) .await; - finder.read_with(cx, |f, _| assert_eq!(f.matches.len(), 5)); + finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 5)); finder.update(cx, |finder, cx| { let delegate = finder.delegate_mut(); @@ -385,7 +389,7 @@ mod tests { // returning only a subset of the matches that would have been found. drop(delegate.spawn_search(query.clone(), cx)); delegate.set_matches( - finder.delegate().latest_search_id, + delegate.latest_search_id, true, // did-cancel query.clone(), vec![matches[1].clone(), matches[3].clone()], @@ -445,14 +449,19 @@ mod tests { let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let (_, finder) = cx.add_window(|cx| { Picker::new( - FileFinderDelegate::new(workspace.read(cx).project().clone(), None, cx), + FileFinderDelegate::new( + workspace.downgrade(), + workspace.read(cx).project().clone(), + None, + cx, + ), cx, ) }); finder - .update(cx, |f, cx| f.spawn_search("hi".into(), cx)) + .update(cx, |f, cx| f.delegate_mut().spawn_search("hi".into(), cx)) .await; - finder.read_with(cx, |f, _| assert_eq!(f.matches.len(), 7)); + finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 7)); } #[gpui::test] @@ -472,20 +481,29 @@ mod tests { .await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let (_, finder) = cx.add_window(|cx| { - FileFinderDelegate::new(workspace.read(cx).project().clone(), None, cx) + Picker::new( + FileFinderDelegate::new( + workspace.downgrade(), + workspace.read(cx).project().clone(), + None, + cx, + ), + cx, + ) }); // Even though there is only one worktree, that worktree's filename // is included in the matching, because the worktree is a single file. finder - .update(cx, |f, cx| f.spawn_search("thf".into(), cx)) + .update(cx, |f, cx| f.delegate_mut().spawn_search("thf".into(), cx)) .await; cx.read(|cx| { let finder = finder.read(cx); - assert_eq!(finder.matches.len(), 1); + let delegate = finder.delegate(); + assert_eq!(delegate.matches.len(), 1); let (file_name, file_name_positions, full_path, full_path_positions) = - finder.labels_for_match(&finder.matches[0]); + delegate.labels_for_match(&delegate.matches[0]); assert_eq!(file_name, "the-file"); assert_eq!(file_name_positions, &[0, 1, 4]); assert_eq!(full_path, "the-file"); @@ -495,9 +513,9 @@ mod tests { // Since the worktree root is a file, searching for its name followed by a slash does // not match anything. finder - .update(cx, |f, cx| f.spawn_search("thf/".into(), cx)) + .update(cx, |f, cx| f.delegate_mut().spawn_search("thf/".into(), cx)) .await; - finder.read_with(cx, |f, _| assert_eq!(f.matches.len(), 0)); + finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 0)); } #[gpui::test] @@ -526,22 +544,31 @@ mod tests { let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let (_, finder) = cx.add_window(|cx| { - FileFinderDelegate::new(workspace.read(cx).project().clone(), None, cx) + Picker::new( + FileFinderDelegate::new( + workspace.downgrade(), + workspace.read(cx).project().clone(), + None, + cx, + ), + cx, + ) }); // Run a search that matches two files with the same relative path. finder - .update(cx, |f, cx| f.spawn_search("a.t".into(), cx)) + .update(cx, |f, cx| f.delegate_mut().spawn_search("a.t".into(), cx)) .await; // Can switch between different matches with the same relative path. - finder.update(cx, |f, cx| { - assert_eq!(f.matches.len(), 2); - assert_eq!(f.selected_index(), 0); - f.set_selected_index(1, cx); - assert_eq!(f.selected_index(), 1); - f.set_selected_index(0, cx); - assert_eq!(f.selected_index(), 0); + finder.update(cx, |finder, cx| { + let delegate = finder.delegate_mut(); + assert_eq!(delegate.matches.len(), 2); + assert_eq!(delegate.selected_index(), 0); + delegate.set_selected_index(1, cx); + assert_eq!(delegate.selected_index(), 1); + delegate.set_selected_index(0, cx); + assert_eq!(delegate.selected_index(), 0); }); } @@ -573,16 +600,27 @@ mod tests { // so that one should be sorted earlier let b_path = Some(Arc::from(Path::new("/root/dir2/b.txt"))); let (_, finder) = cx.add_window(|cx| { - FileFinderDelegate::new(workspace.read(cx).project().clone(), b_path, cx) + Picker::new( + FileFinderDelegate::new( + workspace.downgrade(), + workspace.read(cx).project().clone(), + b_path, + cx, + ), + cx, + ) }); finder - .update(cx, |f, cx| f.spawn_search("a.txt".into(), cx)) + .update(cx, |f, cx| { + f.delegate_mut().spawn_search("a.txt".into(), cx) + }) .await; finder.read_with(cx, |f, _| { - assert_eq!(f.matches[0].path.as_ref(), Path::new("dir2/a.txt")); - assert_eq!(f.matches[1].path.as_ref(), Path::new("dir1/a.txt")); + let delegate = f.delegate(); + assert_eq!(delegate.matches[0].path.as_ref(), Path::new("dir2/a.txt")); + assert_eq!(delegate.matches[1].path.as_ref(), Path::new("dir1/a.txt")); }); } @@ -606,14 +644,22 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let (_, finder) = cx.add_window(|cx| { - FileFinderDelegate::new(workspace.read(cx).project().clone(), None, cx) + Picker::new( + FileFinderDelegate::new( + workspace.downgrade(), + workspace.read(cx).project().clone(), + None, + cx, + ), + cx, + ) }); finder - .update(cx, |f, cx| f.spawn_search("dir".into(), cx)) + .update(cx, |f, cx| f.delegate_mut().spawn_search("dir".into(), cx)) .await; cx.read(|cx| { let finder = finder.read(cx); - assert_eq!(finder.matches.len(), 0); + assert_eq!(finder.delegate().matches.len(), 0); }); } } diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 720e2ec2e4..2827adb91f 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -8,7 +8,7 @@ use gpui::{ use menu::{Cancel, Confirm}; use settings::Settings; use text::{Bias, Point}; -use workspace::Workspace; +use workspace::{Modal, Workspace}; actions!(go_to_line, [Toggle]); @@ -65,11 +65,7 @@ impl GoToLine { .active_item(cx) .and_then(|active_item| active_item.downcast::()) { - workspace.toggle_modal(cx, |_, cx| { - let view = cx.add_view(|cx| GoToLine::new(editor, cx)); - cx.subscribe(&view, Self::on_event).detach(); - view - }); + workspace.toggle_modal(cx, |_, cx| cx.add_view(|cx| GoToLine::new(editor, cx))); } } @@ -91,17 +87,6 @@ impl GoToLine { cx.emit(Event::Dismissed); } - fn on_event( - workspace: &mut Workspace, - _: ViewHandle, - event: &Event, - cx: &mut ViewContext, - ) { - match event { - Event::Dismissed => workspace.dismiss_modal(cx), - } - } - fn on_line_editor_event( &mut self, _: ViewHandle, @@ -194,3 +179,9 @@ impl View for GoToLine { cx.focus(&self.line_editor); } } + +impl Modal for GoToLine { + fn dismiss_on_event(event: &Self::Event) -> bool { + matches!(event, Event::Dismissed) + } +} diff --git a/crates/language_selector/src/language_selector.rs b/crates/language_selector/src/language_selector.rs index cd7c294450..d39da99963 100644 --- a/crates/language_selector/src/language_selector.rs +++ b/crates/language_selector/src/language_selector.rs @@ -4,12 +4,9 @@ pub use active_buffer_language::ActiveBufferLanguage; use anyhow::anyhow; use editor::Editor; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; -use gpui::{ - actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState, View, - ViewContext, ViewHandle, -}; +use gpui::{actions, elements::*, AppContext, ModelHandle, MouseState, ViewContext}; use language::{Buffer, LanguageRegistry}; -use picker::{Picker, PickerDelegate}; +use picker::{Picker, PickerDelegate, PickerEvent}; use project::Project; use settings::Settings; use std::sync::Arc; @@ -19,39 +16,49 @@ use workspace::{AppState, Workspace}; actions!(language_selector, [Toggle]); pub fn init(app_state: Arc, cx: &mut AppContext) { - Picker::::init(cx); + Picker::::init(cx); cx.add_action({ let language_registry = app_state.languages.clone(); - move |workspace, _: &Toggle, cx| { - LanguageSelector::toggle(workspace, language_registry.clone(), cx) - } + move |workspace, _: &Toggle, cx| toggle(workspace, language_registry.clone(), cx) }); } -pub enum Event { - Dismissed, +fn toggle( + workspace: &mut Workspace, + registry: Arc, + cx: &mut ViewContext, +) -> Option<()> { + let (_, buffer, _) = workspace + .active_item(cx)? + .act_as::(cx)? + .read(cx) + .active_excerpt(cx)?; + workspace.toggle_modal(cx, |workspace, cx| { + cx.add_view(|cx| { + Picker::new( + LanguageSelectorDelegate::new(buffer, workspace.project().clone(), registry), + cx, + ) + }) + }); + Some(()) } -pub struct LanguageSelector { +pub struct LanguageSelectorDelegate { buffer: ModelHandle, project: ModelHandle, language_registry: Arc, candidates: Vec, matches: Vec, - picker: ViewHandle>, selected_index: usize, } -impl LanguageSelector { +impl LanguageSelectorDelegate { fn new( buffer: ModelHandle, project: ModelHandle, language_registry: Arc, - cx: &mut ViewContext, ) -> Self { - let handle = cx.weak_handle(); - let picker = cx.add_view(|cx| Picker::new("Select Language...", handle, cx)); - let candidates = language_registry .language_names() .into_iter() @@ -75,70 +82,21 @@ impl LanguageSelector { language_registry, candidates, matches, - picker, selected_index: 0, } } - - fn toggle( - workspace: &mut Workspace, - registry: Arc, - cx: &mut ViewContext, - ) { - if let Some((_, buffer, _)) = workspace - .active_item(cx) - .and_then(|active_item| active_item.act_as::(cx)) - .and_then(|editor| editor.read(cx).active_excerpt(cx)) - { - workspace.toggle_modal(cx, |workspace, cx| { - let project = workspace.project().clone(); - let this = cx.add_view(|cx| Self::new(buffer, project, registry, cx)); - cx.subscribe(&this, Self::on_event).detach(); - this - }); - } - } - - fn on_event( - workspace: &mut Workspace, - _: ViewHandle, - event: &Event, - cx: &mut ViewContext, - ) { - match event { - Event::Dismissed => { - workspace.dismiss_modal(cx); - } - } - } } -impl Entity for LanguageSelector { - type Event = Event; -} - -impl View for LanguageSelector { - fn ui_name() -> &'static str { - "LanguageSelector" +impl PickerDelegate for LanguageSelectorDelegate { + fn placeholder_text(&self) -> Arc { + "Select a language...".into() } - fn render(&mut self, cx: &mut ViewContext) -> Element { - ChildView::new(&self.picker, cx).boxed() - } - - fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - if cx.is_self_focused() { - cx.focus(&self.picker); - } - } -} - -impl PickerDelegate for LanguageSelector { fn match_count(&self) -> usize { self.matches.len() } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, cx: &mut ViewContext>) { if let Some(mat) = self.matches.get(self.selected_index) { let language_name = &self.candidates[mat.candidate_id].string; let language = self.language_registry.language_for_name(language_name); @@ -160,22 +118,24 @@ impl PickerDelegate for LanguageSelector { .detach_and_log_err(cx); } - cx.emit(Event::Dismissed); + cx.emit(PickerEvent::Dismiss); } - fn dismissed(&mut self, cx: &mut ViewContext) { - cx.emit(Event::Dismissed); - } + fn dismissed(&mut self, _cx: &mut ViewContext>) {} fn selected_index(&self) -> usize { self.selected_index } - fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext) { + fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext>) { self.selected_index = ix; } - fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> gpui::Task<()> { + fn update_matches( + &mut self, + query: String, + cx: &mut ViewContext>, + ) -> gpui::Task<()> { let background = cx.background().clone(); let candidates = self.candidates.clone(); cx.spawn_weak(|this, mut cx| async move { @@ -204,10 +164,11 @@ impl PickerDelegate for LanguageSelector { if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx| { - this.matches = matches; - this.selected_index = this + let delegate = this.delegate_mut(); + delegate.matches = matches; + delegate.selected_index = delegate .selected_index - .min(this.matches.len().saturating_sub(1)); + .min(delegate.matches.len().saturating_sub(1)); cx.notify(); }) .log_err(); diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index eaac215ffa..4dde620a35 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -4,25 +4,51 @@ use editor::{ }; use fuzzy::StringMatch; use gpui::{ - actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Entity, - MouseState, Task, View, ViewContext, ViewHandle, WindowContext, + actions, elements::*, geometry::vector::Vector2F, AppContext, MouseState, Task, ViewContext, + ViewHandle, WindowContext, }; use language::Outline; use ordered_float::OrderedFloat; -use picker::{Picker, PickerDelegate}; +use picker::{Picker, PickerDelegate, PickerEvent}; use settings::Settings; -use std::cmp::{self, Reverse}; +use std::{ + cmp::{self, Reverse}, + sync::Arc, +}; use workspace::Workspace; actions!(outline, [Toggle]); pub fn init(cx: &mut AppContext) { - cx.add_action(OutlineView::toggle); - Picker::::init(cx); + cx.add_action(toggle); + OutlineView::init(cx); } -struct OutlineView { - picker: ViewHandle>, +fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { + if let Some(editor) = workspace + .active_item(cx) + .and_then(|item| item.downcast::()) + { + let outline = editor + .read(cx) + .buffer() + .read(cx) + .snapshot(cx) + .outline(Some(cx.global::().theme.editor.syntax.as_ref())); + if let Some(outline) = outline { + workspace.toggle_modal(cx, |_, cx| { + cx.add_view(|cx| { + OutlineView::new(OutlineViewDelegate::new(outline, editor, cx), cx) + .with_max_size(800., 1200.) + }) + }); + } + } +} + +type OutlineView = Picker; + +struct OutlineViewDelegate { active_editor: ViewHandle, outline: Outline, selected_match_index: usize, @@ -31,47 +57,13 @@ struct OutlineView { last_query: String, } -pub enum Event { - Dismissed, -} - -impl Entity for OutlineView { - type Event = Event; - - fn release(&mut self, cx: &mut AppContext) { - cx.update_window(self.active_editor.window_id(), |cx| { - self.restore_active_editor(cx); - }); - } -} - -impl View for OutlineView { - fn ui_name() -> &'static str { - "OutlineView" - } - - fn render(&mut self, cx: &mut ViewContext) -> Element { - ChildView::new(&self.picker, cx).boxed() - } - - fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - if cx.is_self_focused() { - cx.focus(&self.picker); - } - } -} - -impl OutlineView { +impl OutlineViewDelegate { fn new( outline: Outline, editor: ViewHandle, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Self { - let handle = cx.weak_handle(); Self { - picker: cx.add_view(|cx| { - Picker::new("Search buffer symbols...", handle, cx).with_max_size(800., 1200.) - }), last_query: Default::default(), matches: Default::default(), selected_match_index: 0, @@ -81,27 +73,6 @@ impl OutlineView { } } - fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { - if let Some(editor) = workspace - .active_item(cx) - .and_then(|item| item.downcast::()) - { - let outline = editor - .read(cx) - .buffer() - .read(cx) - .snapshot(cx) - .outline(Some(cx.global::().theme.editor.syntax.as_ref())); - if let Some(outline) = outline { - workspace.toggle_modal(cx, |_, cx| { - let view = cx.add_view(|cx| OutlineView::new(outline, editor, cx)); - cx.subscribe(&view, Self::on_event).detach(); - view - }); - } - } - } - fn restore_active_editor(&mut self, cx: &mut WindowContext) { self.active_editor.update(cx, |editor, cx| { editor.highlight_rows(None); @@ -111,7 +82,7 @@ impl OutlineView { }) } - fn set_selected_index(&mut self, ix: usize, navigate: bool, cx: &mut ViewContext) { + 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]; @@ -127,22 +98,14 @@ impl OutlineView { active_editor.request_autoscroll(Autoscroll::center(), cx); }); } - cx.notify(); - } - - fn on_event( - workspace: &mut Workspace, - _: ViewHandle, - event: &Event, - cx: &mut ViewContext, - ) { - match event { - Event::Dismissed => workspace.dismiss_modal(cx), - } } } -impl PickerDelegate for OutlineView { +impl PickerDelegate for OutlineViewDelegate { + fn placeholder_text(&self) -> Arc { + "Search buffer symbols...".into() + } + fn match_count(&self) -> usize { self.matches.len() } @@ -151,7 +114,7 @@ impl PickerDelegate for OutlineView { self.selected_match_index } - fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext) { + fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext) { self.set_selected_index(ix, true, cx); } @@ -159,7 +122,7 @@ impl PickerDelegate for OutlineView { true } - fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { + fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { let selected_index; if query.is_empty() { self.restore_active_editor(cx); @@ -215,7 +178,7 @@ impl PickerDelegate for OutlineView { Task::ready(()) } - fn confirm(&mut self, cx: &mut ViewContext) { + 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() { @@ -226,12 +189,11 @@ impl PickerDelegate for OutlineView { }); } }); - cx.emit(Event::Dismissed); + cx.emit(PickerEvent::Dismiss); } - fn dismissed(&mut self, cx: &mut ViewContext) { + fn dismissed(&mut self, cx: &mut ViewContext) { self.restore_active_editor(cx); - cx.emit(Event::Dismissed); } fn render_match( diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 63d758c6ee..9d3061edd3 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -24,6 +24,7 @@ pub struct Picker { max_size: Vector2F, theme: Arc theme::Picker>>>, confirmed: bool, + pending_update_matches: Task>, } pub trait PickerDelegate: Sized + 'static { @@ -184,6 +185,7 @@ impl Picker { max_size: vec2f(540., 420.), theme, confirmed: false, + pending_update_matches: Task::ready(None), }; // TODO! How can the delegate notify the picker to update? // cx.observe(&delegate, |_, _, cx| cx.notify()).detach(); @@ -238,22 +240,24 @@ impl Picker { pub fn update_matches(&mut self, query: String, cx: &mut ViewContext) { let update = self.delegate.update_matches(query, cx); - cx.spawn_weak(|this, mut cx| async move { + self.matches_updated(cx); + self.pending_update_matches = cx.spawn_weak(|this, mut cx| async move { update.await; this.upgrade(&cx)? - .update(&mut cx, |this, cx| { - let index = this.delegate.selected_index(); - let target = if this.delegate.center_selection_after_match_updates() { - ScrollTarget::Center(index) - } else { - ScrollTarget::Show(index) - }; - this.list_state.scroll_to(target); - cx.notify(); - }) + .update(&mut cx, |this, cx| this.matches_updated(cx)) .log_err() - }) - .detach() + }); + } + + fn matches_updated(&mut self, cx: &mut ViewContext) { + let index = self.delegate.selected_index(); + let target = if self.delegate.center_selection_after_match_updates() { + ScrollTarget::Center(index) + } else { + ScrollTarget::Show(index) + }; + self.list_state.scroll_to(target); + cx.notify(); } pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext) { @@ -306,7 +310,7 @@ impl Picker { cx.notify(); } - fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { + pub fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { self.confirmed = true; self.delegate.confirm(cx); } diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 1c73fc5cdd..273b55754d 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -1,90 +1,63 @@ +use anyhow::anyhow; use editor::{ combine_syntax_and_fuzzy_match_highlights, scroll::autoscroll::Autoscroll, styled_runs_for_code_label, Bias, Editor, }; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState, Task, View, - ViewContext, ViewHandle, + actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle, }; use ordered_float::OrderedFloat; -use picker::{Picker, PickerDelegate}; +use picker::{Picker, PickerDelegate, PickerEvent}; use project::{Project, Symbol}; use settings::Settings; -use std::{borrow::Cow, cmp::Reverse}; +use std::{borrow::Cow, cmp::Reverse, sync::Arc}; use util::ResultExt; use workspace::Workspace; actions!(project_symbols, [Toggle]); pub fn init(cx: &mut AppContext) { - cx.add_action(ProjectSymbolsView::toggle); - Picker::::init(cx); + cx.add_action(toggle); + ProjectSymbols::init(cx); } -pub struct ProjectSymbolsView { - picker: ViewHandle>, +fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { + workspace.toggle_modal(cx, |workspace, cx| { + let project = workspace.project().clone(); + let workspace = cx.weak_handle(); + cx.add_view(|cx| ProjectSymbols::new(ProjectSymbolsDelegate::new(workspace, project), cx)) + }); +} + +pub type ProjectSymbols = Picker; + +pub struct ProjectSymbolsDelegate { + workspace: WeakViewHandle, project: ModelHandle, selected_match_index: usize, symbols: Vec, visible_match_candidates: Vec, external_match_candidates: Vec, show_worktree_root_name: bool, - pending_update: Task<()>, matches: Vec, } -pub enum Event { - Dismissed, - Selected(Symbol), -} - -impl Entity for ProjectSymbolsView { - type Event = Event; -} - -impl View for ProjectSymbolsView { - fn ui_name() -> &'static str { - "ProjectSymbolsView" - } - - fn render(&mut self, cx: &mut ViewContext) -> Element { - ChildView::new(&self.picker, cx).boxed() - } - - fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - if cx.is_self_focused() { - cx.focus(&self.picker); - } - } -} - -impl ProjectSymbolsView { - fn new(project: ModelHandle, cx: &mut ViewContext) -> Self { - let handle = cx.weak_handle(); +impl ProjectSymbolsDelegate { + fn new(workspace: WeakViewHandle, project: ModelHandle) -> Self { Self { + workspace, project, - picker: cx.add_view(|cx| Picker::new("Search project symbols...", handle, cx)), selected_match_index: 0, symbols: Default::default(), visible_match_candidates: Default::default(), external_match_candidates: Default::default(), matches: Default::default(), show_worktree_root_name: false, - pending_update: Task::ready(()), } } - fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { - workspace.toggle_modal(cx, |workspace, cx| { - let project = workspace.project().clone(); - let symbols = cx.add_view(|cx| Self::new(project, cx)); - cx.subscribe(&symbols, Self::on_event).detach(); - symbols - }); - } - - fn filter(&mut self, query: &str, cx: &mut ViewContext) { + fn filter(&mut self, query: &str, cx: &mut ViewContext) { const MAX_MATCHES: usize = 100; let mut visible_matches = cx.background_executor().block(fuzzy::match_strings( &self.visible_match_candidates, @@ -125,60 +98,50 @@ impl ProjectSymbolsView { self.matches = matches; self.set_selected_index(0, cx); - cx.notify(); - } - - fn on_event( - workspace: &mut Workspace, - _: ViewHandle, - event: &Event, - cx: &mut ViewContext, - ) { - 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::(buffer, cx); - editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::center()), cx, |s| { - s.select_ranges([position..position]) - }); - }); - })?; - Ok::<_, anyhow::Error>(()) - }) - .detach_and_log_err(cx); - workspace.dismiss_modal(cx); - } - } } } -impl PickerDelegate for ProjectSymbolsView { - fn confirm(&mut self, cx: &mut ViewContext) { +impl PickerDelegate for ProjectSymbolsDelegate { + fn placeholder_text(&self) -> Arc { + "Search project symbols...".into() + } + + fn confirm(&mut self, cx: &mut ViewContext) { if let Some(symbol) = self .matches .get(self.selected_match_index) .map(|mat| self.symbols[mat.candidate_id].clone()) { - cx.emit(Event::Selected(symbol)); + let buffer = self.project.update(cx, |project, cx| { + project.open_buffer_for_symbol(&symbol, cx) + }); + let symbol = symbol.clone(); + let workspace = self.workspace.clone(); + cx.spawn_weak(|_, mut cx| async move { + let buffer = buffer.await?; + let workspace = workspace + .upgrade(&cx) + .ok_or_else(|| anyhow!("workspace was dropped"))?; + 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::(buffer, cx); + editor.update(cx, |editor, cx| { + editor.change_selections(Some(Autoscroll::center()), cx, |s| { + s.select_ranges([position..position]) + }); + }); + })?; + Ok::<_, anyhow::Error>(()) + }) + .detach_and_log_err(cx); + cx.emit(PickerEvent::Dismiss); } } - fn dismissed(&mut self, cx: &mut ViewContext) { - cx.emit(Event::Dismissed); - } + fn dismissed(&mut self, _cx: &mut ViewContext) {} fn match_count(&self) -> usize { self.matches.len() @@ -188,23 +151,23 @@ impl PickerDelegate for ProjectSymbolsView { self.selected_match_index } - fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext) { + fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext) { self.selected_match_index = ix; - cx.notify(); } - fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { + fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> 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)); - self.pending_update = cx.spawn_weak(|this, mut cx| async move { + 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| { - let project = this.project.read(cx); + let delegate = this.delegate_mut(); + let project = delegate.project.read(cx); let (visible_match_candidates, external_match_candidates) = symbols .iter() .enumerate() @@ -221,16 +184,15 @@ impl PickerDelegate for ProjectSymbolsView { .map_or(false, |e| !e.is_ignored) }); - this.visible_match_candidates = visible_match_candidates; - this.external_match_candidates = external_match_candidates; - this.symbols = symbols; - this.filter(&query, cx); + delegate.visible_match_candidates = visible_match_candidates; + delegate.external_match_candidates = external_match_candidates; + delegate.symbols = symbols; + delegate.filter(&query, cx); }) .log_err(); } } - }); - Task::ready(()) + }) } fn render_match( @@ -364,46 +326,53 @@ mod tests { }, ); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + // Create the project symbols view. - let (_, symbols_view) = cx.add_window(|cx| ProjectSymbolsView::new(project.clone(), cx)); - let picker = symbols_view.read_with(cx, |symbols_view, _| symbols_view.picker.clone()); + let symbols = cx.add_view(&workspace, |cx| { + ProjectSymbols::new( + ProjectSymbolsDelegate::new(workspace.downgrade(), project.clone()), + cx, + ) + }); // Spawn multiples updates before the first update completes, // such that in the end, there are no matches. Testing for regression: // https://github.com/zed-industries/zed/issues/861 - picker.update(cx, |p, cx| { + symbols.update(cx, |p, cx| { p.update_matches("o".to_string(), cx); p.update_matches("on".to_string(), cx); p.update_matches("onex".to_string(), cx); }); cx.foreground().run_until_parked(); - symbols_view.read_with(cx, |symbols_view, _| { - assert_eq!(symbols_view.matches.len(), 0); + symbols.read_with(cx, |symbols, _| { + assert_eq!(symbols.delegate().matches.len(), 0); }); // Spawn more updates such that in the end, there are matches. - picker.update(cx, |p, cx| { + symbols.update(cx, |p, cx| { p.update_matches("one".to_string(), cx); p.update_matches("on".to_string(), cx); }); cx.foreground().run_until_parked(); - symbols_view.read_with(cx, |symbols_view, _| { - assert_eq!(symbols_view.matches.len(), 2); - assert_eq!(symbols_view.matches[0].string, "ton"); - assert_eq!(symbols_view.matches[1].string, "one"); + symbols.read_with(cx, |symbols, _| { + let delegate = symbols.delegate(); + assert_eq!(delegate.matches.len(), 2); + assert_eq!(delegate.matches[0].string, "ton"); + assert_eq!(delegate.matches[1].string, "one"); }); // Spawn more updates such that in the end, there are again no matches. - picker.update(cx, |p, cx| { + symbols.update(cx, |p, cx| { p.update_matches("o".to_string(), cx); p.update_matches("".to_string(), cx); }); cx.foreground().run_until_parked(); - symbols_view.read_with(cx, |symbols_view, _| { - assert_eq!(symbols_view.matches.len(), 0); + symbols.read_with(cx, |symbols, _| { + assert_eq!(symbols.delegate().matches.len(), 0); }); } diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index e19e80bca8..afa04f414d 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -3,14 +3,15 @@ mod highlighted_workspace_location; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ actions, - elements::{ChildView, Flex, ParentElement}, - AnyViewHandle, AppContext, Drawable, Element, Entity, Task, View, ViewContext, ViewHandle, + anyhow::Result, + elements::{Flex, ParentElement}, + AppContext, Drawable, Element, Task, ViewContext, }; use highlighted_workspace_location::HighlightedWorkspaceLocation; use ordered_float::OrderedFloat; -use picker::{Picker, PickerDelegate}; +use picker::{Picker, PickerDelegate, PickerEvent}; use settings::Settings; -use util::ResultExt; +use std::sync::Arc; use workspace::{ notifications::simple_message_notification::MessageNotification, OpenPaths, Workspace, WorkspaceLocation, WORKSPACE_DB, @@ -19,103 +20,70 @@ use workspace::{ actions!(projects, [OpenRecent]); pub fn init(cx: &mut AppContext) { - cx.add_action(RecentProjectsView::toggle); - Picker::::init(cx); + cx.add_async_action(toggle); + RecentProjects::init(cx); } -struct RecentProjectsView { - picker: ViewHandle>, +fn toggle( + _: &mut Workspace, + _: &OpenRecent, + cx: &mut ViewContext, +) -> Option>> { + Some(cx.spawn(|workspace, mut cx| async move { + let workspace_locations: Vec<_> = cx + .background() + .spawn(async { + WORKSPACE_DB + .recent_workspaces_on_disk() + .await + .unwrap_or_default() + .into_iter() + .map(|(_, location)| location) + .collect() + }) + .await; + + workspace.update(&mut cx, |workspace, cx| { + if !workspace_locations.is_empty() { + workspace.toggle_modal(cx, |_, cx| { + cx.add_view(|cx| { + RecentProjects::new(RecentProjectsDelegate::new(workspace_locations), cx) + .with_max_size(800., 1200.) + }) + }); + } else { + workspace.show_notification(0, cx, |cx| { + cx.add_view(|_| MessageNotification::new_message("No recent projects to open.")) + }) + } + })?; + Ok(()) + })) +} + +type RecentProjects = Picker; + +struct RecentProjectsDelegate { workspace_locations: Vec, selected_match_index: usize, matches: Vec, } -impl RecentProjectsView { - fn new(workspace_locations: Vec, cx: &mut ViewContext) -> Self { - let handle = cx.weak_handle(); +impl RecentProjectsDelegate { + fn new(workspace_locations: Vec) -> Self { Self { - picker: cx.add_view(|cx| { - Picker::new("Recent Projects...", handle, cx).with_max_size(800., 1200.) - }), workspace_locations, selected_match_index: 0, matches: Default::default(), } } - - fn toggle(_: &mut Workspace, _: &OpenRecent, cx: &mut ViewContext) { - cx.spawn(|workspace, mut cx| async move { - let workspace_locations: Vec<_> = cx - .background() - .spawn(async { - WORKSPACE_DB - .recent_workspaces_on_disk() - .await - .unwrap_or_default() - .into_iter() - .map(|(_, location)| location) - .collect() - }) - .await; - - workspace - .update(&mut cx, |workspace, cx| { - if !workspace_locations.is_empty() { - workspace.toggle_modal(cx, |_, cx| { - let view = cx.add_view(|cx| Self::new(workspace_locations, cx)); - cx.subscribe(&view, Self::on_event).detach(); - view - }); - } else { - workspace.show_notification(0, cx, |cx| { - cx.add_view(|_| { - MessageNotification::new_message("No recent projects to open.") - }) - }) - } - }) - .log_err(); - }) - .detach(); - } - - fn on_event( - workspace: &mut Workspace, - _: ViewHandle, - event: &Event, - cx: &mut ViewContext, - ) { - match event { - Event::Dismissed => workspace.dismiss_modal(cx), - } - } } -pub enum Event { - Dismissed, -} - -impl Entity for RecentProjectsView { - type Event = Event; -} - -impl View for RecentProjectsView { - fn ui_name() -> &'static str { - "RecentProjectsView" +impl PickerDelegate for RecentProjectsDelegate { + fn placeholder_text(&self) -> Arc { + "Recent Projects...".into() } - fn render(&mut self, cx: &mut ViewContext) -> Element { - ChildView::new(&self.picker, cx).boxed() - } - - fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - if cx.is_self_focused() { - cx.focus(&self.picker); - } - } -} - -impl PickerDelegate for RecentProjectsView { fn match_count(&self) -> usize { self.matches.len() } @@ -124,11 +92,15 @@ impl PickerDelegate for RecentProjectsView { self.selected_match_index } - fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext) { + fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext) { self.selected_match_index = ix; } - fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> gpui::Task<()> { + fn update_matches( + &mut self, + query: String, + cx: &mut ViewContext, + ) -> gpui::Task<()> { let query = query.trim_start(); let smart_case = query.chars().any(|c| c.is_uppercase()); let candidates = self @@ -166,19 +138,17 @@ impl PickerDelegate for RecentProjectsView { Task::ready(()) } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, cx: &mut ViewContext) { if let Some(selected_match) = &self.matches.get(self.selected_index()) { let workspace_location = &self.workspace_locations[selected_match.candidate_id]; cx.dispatch_global_action(OpenPaths { paths: workspace_location.paths().as_ref().clone(), }); - cx.emit(Event::Dismissed); + cx.emit(PickerEvent::Dismiss); } } - fn dismissed(&mut self, cx: &mut ViewContext) { - cx.emit(Event::Dismissed); - } + fn dismissed(&mut self, _cx: &mut ViewContext) {} fn render_match( &self, diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index b70ae7b040..c1c1dbc676 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -1,9 +1,6 @@ use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; -use gpui::{ - actions, elements::*, AnyViewHandle, AppContext, Drawable, Element, Entity, MouseState, View, - ViewContext, ViewHandle, -}; -use picker::{Picker, PickerDelegate}; +use gpui::{actions, elements::*, AppContext, Drawable, Element, MouseState, ViewContext}; +use picker::{Picker, PickerDelegate, PickerEvent}; use settings::{settings_file::SettingsFile, Settings}; use staff_mode::StaffMode; use std::sync::Arc; @@ -11,36 +8,50 @@ use theme::{Theme, ThemeMeta, ThemeRegistry}; use util::ResultExt; use workspace::{AppState, Workspace}; -pub struct ThemeSelector { +actions!(theme_selector, [Toggle, Reload]); + +pub fn init(app_state: Arc, cx: &mut AppContext) { + cx.add_action({ + let theme_registry = app_state.themes.clone(); + move |workspace, _: &Toggle, cx| toggle(workspace, theme_registry.clone(), cx) + }); + ThemeSelector::init(cx); +} + +fn toggle(workspace: &mut Workspace, themes: Arc, cx: &mut ViewContext) { + workspace.toggle_modal(cx, |_, cx| { + cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(themes, cx), cx)) + }); +} + +#[cfg(debug_assertions)] +pub fn reload(themes: Arc, cx: &mut AppContext) { + let current_theme_name = cx.global::().theme.meta.name.clone(); + themes.clear(); + match themes.get(¤t_theme_name) { + Ok(theme) => { + ThemeSelectorDelegate::set_theme(theme, cx); + log::info!("reloaded theme {}", current_theme_name); + } + Err(error) => { + log::error!("failed to load theme {}: {:?}", current_theme_name, error) + } + } +} + +pub type ThemeSelector = Picker; + +pub struct ThemeSelectorDelegate { registry: Arc, theme_data: Vec, matches: Vec, original_theme: Arc, - picker: ViewHandle>, selection_completed: bool, selected_index: usize, } -actions!(theme_selector, [Toggle, Reload]); - -pub fn init(app_state: Arc, cx: &mut AppContext) { - Picker::::init(cx); - cx.add_action({ - let theme_registry = app_state.themes.clone(); - move |workspace, _: &Toggle, cx| { - ThemeSelector::toggle(workspace, theme_registry.clone(), cx) - } - }); -} - -pub enum Event { - Dismissed, -} - -impl ThemeSelector { - fn new(registry: Arc, cx: &mut ViewContext) -> Self { - let handle = cx.weak_handle(); - let picker = cx.add_view(|cx| Picker::new("Select Theme...", handle, cx)); +impl ThemeSelectorDelegate { + fn new(registry: Arc, cx: &mut ViewContext) -> Self { let settings = cx.global::(); let original_theme = settings.theme.clone(); @@ -62,7 +73,6 @@ impl ThemeSelector { registry, theme_data: theme_names, matches, - picker, original_theme: original_theme.clone(), selected_index: 0, selection_completed: false, @@ -71,34 +81,7 @@ impl ThemeSelector { this } - fn toggle( - workspace: &mut Workspace, - themes: Arc, - cx: &mut ViewContext, - ) { - workspace.toggle_modal(cx, |_, cx| { - let this = cx.add_view(|cx| Self::new(themes, cx)); - cx.subscribe(&this, Self::on_event).detach(); - this - }); - } - - #[cfg(debug_assertions)] - pub fn reload(themes: Arc, cx: &mut AppContext) { - let current_theme_name = cx.global::().theme.meta.name.clone(); - themes.clear(); - match themes.get(¤t_theme_name) { - Ok(theme) => { - Self::set_theme(theme, cx); - log::info!("reloaded theme {}", current_theme_name); - } - Err(error) => { - log::error!("failed to load theme {}: {:?}", current_theme_name, error) - } - } - } - - fn show_selected_theme(&mut self, cx: &mut ViewContext) { + fn show_selected_theme(&mut self, cx: &mut ViewContext) { if let Some(mat) = self.matches.get(self.selected_index) { match self.registry.get(&mat.string) { Ok(theme) => { @@ -119,19 +102,6 @@ impl ThemeSelector { .unwrap_or(self.selected_index); } - fn on_event( - workspace: &mut Workspace, - _: ViewHandle, - event: &Event, - cx: &mut ViewContext, - ) { - match event { - Event::Dismissed => { - workspace.dismiss_modal(cx); - } - } - } - fn set_theme(theme: Arc, cx: &mut AppContext) { cx.update_global::(|settings, cx| { settings.theme = theme; @@ -140,12 +110,16 @@ impl ThemeSelector { } } -impl PickerDelegate for ThemeSelector { +impl PickerDelegate for ThemeSelectorDelegate { + fn placeholder_text(&self) -> Arc { + "Select Theme...".into() + } + fn match_count(&self) -> usize { self.matches.len() } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, cx: &mut ViewContext) { self.selection_completed = true; let theme_name = cx.global::().theme.meta.name.clone(); @@ -153,27 +127,30 @@ impl PickerDelegate for ThemeSelector { settings_content.theme = Some(theme_name); }); - cx.emit(Event::Dismissed); + cx.emit(PickerEvent::Dismiss); } - fn dismissed(&mut self, cx: &mut ViewContext) { + fn dismissed(&mut self, cx: &mut ViewContext) { if !self.selection_completed { Self::set_theme(self.original_theme.clone(), cx); self.selection_completed = true; } - cx.emit(Event::Dismissed); } fn selected_index(&self) -> usize { self.selected_index } - fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext) { + fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext) { self.selected_index = ix; self.show_selected_theme(cx); } - fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> gpui::Task<()> { + fn update_matches( + &mut self, + query: String, + cx: &mut ViewContext, + ) -> gpui::Task<()> { let background = cx.background().clone(); let candidates = self .theme_data @@ -212,12 +189,12 @@ impl PickerDelegate for ThemeSelector { if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx| { - this.matches = matches; - this.selected_index = this + let delegate = this.delegate_mut(); + delegate.matches = matches; + delegate.selected_index = delegate .selected_index - .min(this.matches.len().saturating_sub(1)); - this.show_selected_theme(cx); - cx.notify(); + .min(delegate.matches.len().saturating_sub(1)); + delegate.show_selected_theme(cx); }) .log_err(); } @@ -243,29 +220,3 @@ impl PickerDelegate for ThemeSelector { .boxed() } } - -impl Entity for ThemeSelector { - type Event = Event; - - fn release(&mut self, cx: &mut AppContext) { - if !self.selection_completed { - Self::set_theme(self.original_theme.clone(), cx); - } - } -} - -impl View for ThemeSelector { - fn ui_name() -> &'static str { - "ThemeSelector" - } - - fn render(&mut self, cx: &mut ViewContext) -> Element { - ChildView::new(&self.picker, cx).boxed() - } - - fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - if cx.is_self_focused() { - cx.focus(&self.picker); - } - } -} diff --git a/crates/welcome/src/base_keymap_picker.rs b/crates/welcome/src/base_keymap_picker.rs index bdb4e7603f..e79598ef4d 100644 --- a/crates/welcome/src/base_keymap_picker.rs +++ b/crates/welcome/src/base_keymap_picker.rs @@ -1,92 +1,59 @@ +use std::sync::Arc; + use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use gpui::{ actions, - elements::{ChildView, Drawable as _, Label}, - AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle, + elements::{Drawable as _, Label}, + AppContext, Task, ViewContext, }; -use picker::{Picker, PickerDelegate}; +use picker::{Picker, PickerDelegate, PickerEvent}; use settings::{settings_file::SettingsFile, BaseKeymap, Settings}; use util::ResultExt; use workspace::Workspace; -pub struct BaseKeymapSelector { - matches: Vec, - picker: ViewHandle>, - selected_index: usize, -} - actions!(welcome, [ToggleBaseKeymapSelector]); pub fn init(cx: &mut AppContext) { - Picker::::init(cx); - cx.add_action({ - move |workspace, _: &ToggleBaseKeymapSelector, cx| BaseKeymapSelector::toggle(workspace, cx) + cx.add_action(toggle); + BaseKeymapSelector::init(cx); +} + +fn toggle( + workspace: &mut Workspace, + _: &ToggleBaseKeymapSelector, + cx: &mut ViewContext, +) { + workspace.toggle_modal(cx, |_, cx| { + cx.add_view(|cx| BaseKeymapSelector::new(BaseKeymapSelectorDelegate::new(cx), cx)) }); } -pub enum Event { - Dismissed, +pub type BaseKeymapSelector = Picker; + +pub struct BaseKeymapSelectorDelegate { + matches: Vec, + selected_index: usize, } -impl BaseKeymapSelector { - fn toggle(workspace: &mut Workspace, cx: &mut ViewContext) { - workspace.toggle_modal(cx, |_, cx| { - let this = cx.add_view(|cx| Self::new(cx)); - cx.subscribe(&this, Self::on_event).detach(); - this - }); - } - - fn new(cx: &mut ViewContext) -> Self { +impl BaseKeymapSelectorDelegate { + fn new(cx: &mut ViewContext) -> Self { let base = cx.global::().base_keymap; let selected_index = BaseKeymap::OPTIONS .iter() .position(|(_, value)| *value == base) .unwrap_or(0); - - let this = cx.weak_handle(); Self { - picker: cx.add_view(|cx| Picker::new("Select a base keymap", this, cx)), matches: Vec::new(), selected_index, } } - - fn on_event( - workspace: &mut Workspace, - _: ViewHandle, - event: &Event, - cx: &mut ViewContext, - ) { - match event { - Event::Dismissed => { - workspace.dismiss_modal(cx); - } - } - } } -impl Entity for BaseKeymapSelector { - type Event = Event; -} - -impl View for BaseKeymapSelector { - fn ui_name() -> &'static str { - "BaseKeymapSelector" +impl PickerDelegate for BaseKeymapSelectorDelegate { + fn placeholder_text(&self) -> Arc { + "Select a base keymap...".into() } - fn render(&mut self, cx: &mut gpui::ViewContext) -> gpui::Element { - ChildView::new(&self.picker, cx).boxed() - } - - fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - if cx.is_self_focused() { - cx.focus(&self.picker); - } - } -} - -impl PickerDelegate for BaseKeymapSelector { fn match_count(&self) -> usize { self.matches.len() } @@ -95,11 +62,15 @@ impl PickerDelegate for BaseKeymapSelector { self.selected_index } - fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext) { + fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext) { self.selected_index = ix; } - fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> gpui::Task<()> { + fn update_matches( + &mut self, + query: String, + cx: &mut ViewContext, + ) -> Task<()> { let background = cx.background().clone(); let candidates = BaseKeymap::names() .enumerate() @@ -135,29 +106,27 @@ impl PickerDelegate for BaseKeymapSelector { }; if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, cx| { - this.matches = matches; - this.selected_index = this + this.update(&mut cx, |this, _| { + let delegate = this.delegate_mut(); + delegate.matches = matches; + delegate.selected_index = delegate .selected_index - .min(this.matches.len().saturating_sub(1)); - cx.notify(); + .min(delegate.matches.len().saturating_sub(1)); }) .log_err(); } }) } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, cx: &mut ViewContext) { if let Some(selection) = self.matches.get(self.selected_index) { let base_keymap = BaseKeymap::from_names(&selection.string); SettingsFile::update(cx, move |settings| settings.base_keymap = Some(base_keymap)); } - cx.emit(Event::Dismissed); + cx.emit(PickerEvent::Dismiss); } - fn dismissed(&mut self, cx: &mut ViewContext) { - cx.emit(Event::Dismissed) - } + fn dismissed(&mut self, _cx: &mut ViewContext) {} fn render_match( &self, diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index a7db5dd8c8..4cd7ae32df 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -520,7 +520,7 @@ async fn watch_themes( .await .log_err()?; if output.status.success() { - cx.update(|cx| theme_selector::ThemeSelector::reload(themes.clone(), cx)) + cx.update(|cx| theme_selector::reload(themes.clone(), cx)) } else { eprintln!( "build script failed {}",