diff --git a/crates/collab_ui/src/contact_finder.rs b/crates/collab_ui/src/contact_finder.rs index aaaa701234..0d4e3aff3d 100644 --- a/crates/collab_ui/src/contact_finder.rs +++ b/crates/collab_ui/src/contact_finder.rs @@ -1,49 +1,41 @@ use client::{ContactRequestStatus, User, UserStore}; -use gpui::{ - elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState, Task, View, - ViewContext, ViewHandle, -}; -use picker::{Picker, PickerDelegate}; +use gpui::{elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext}; +use picker::{Picker, PickerDelegate, PickerEvent}; use settings::Settings; use std::sync::Arc; use util::TryFutureExt; pub fn init(cx: &mut AppContext) { - Picker::::init(cx); + Picker::::init(cx); } -pub struct ContactFinder { - picker: ViewHandle>, +pub type ContactFinder = Picker; + +pub fn build_contact_finder( + user_store: ModelHandle, + cx: &mut ViewContext, +) -> ContactFinder { + Picker::new( + ContactFinderDelegate { + user_store, + potential_contacts: Arc::from([]), + selected_index: 0, + }, + cx, + ) +} + +pub struct ContactFinderDelegate { potential_contacts: Arc<[Arc]>, user_store: ModelHandle, selected_index: usize, } -pub enum Event { - Dismissed, -} - -impl Entity for ContactFinder { - type Event = Event; -} - -impl View for ContactFinder { - fn ui_name() -> &'static str { - "ContactFinder" +impl PickerDelegate for ContactFinderDelegate { + fn placeholder_text(&self) -> Arc { + "Search collaborator by username...".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 ContactFinder { fn match_count(&self) -> usize { self.potential_contacts.len() } @@ -52,20 +44,20 @@ impl PickerDelegate for ContactFinder { 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) -> Task<()> { + fn update_matches(&mut self, query: String, cx: &mut ViewContext>) -> Task<()> { let search_users = self .user_store .update(cx, |store, cx| store.fuzzy_search_users(query, cx)); - cx.spawn(|this, mut cx| async move { + cx.spawn(|picker, mut cx| async move { async { let potential_contacts = search_users.await?; - this.update(&mut cx, |this, cx| { - this.potential_contacts = potential_contacts.into(); + picker.update(&mut cx, |picker, cx| { + picker.delegate_mut().potential_contacts = potential_contacts.into(); cx.notify(); })?; anyhow::Ok(()) @@ -75,7 +67,7 @@ impl PickerDelegate for ContactFinder { }) } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, cx: &mut ViewContext>) { if let Some(user) = self.potential_contacts.get(self.selected_index) { let user_store = self.user_store.read(cx); match user_store.contact_request_status(user) { @@ -94,8 +86,8 @@ impl PickerDelegate for ContactFinder { } } - fn dismiss(&mut self, cx: &mut ViewContext) { - cx.emit(Event::Dismissed); + fn dismissed(&mut self, cx: &mut ViewContext>) { + cx.emit(PickerEvent::Dismiss); } fn render_match( @@ -164,28 +156,3 @@ impl PickerDelegate for ContactFinder { .boxed() } } - -impl ContactFinder { - pub fn new(user_store: ModelHandle, cx: &mut ViewContext) -> Self { - let this = cx.weak_handle(); - Self { - picker: cx.add_view(|cx| { - Picker::new("Search collaborator by username...", this, cx) - .with_theme(|theme| theme.contact_finder.picker.clone()) - }), - potential_contacts: Arc::from([]), - user_store, - selected_index: 0, - } - } - - pub fn editor_text(&self, cx: &AppContext) -> String { - self.picker.read(cx).query(cx) - } - - pub fn with_editor_text(self, editor_text: String, cx: &mut ViewContext) -> Self { - self.picker - .update(cx, |picker, cx| picker.set_query(editor_text, cx)); - self - } -} diff --git a/crates/collab_ui/src/contacts_popover.rs b/crates/collab_ui/src/contacts_popover.rs index bc7088fbab..6cfe173a8d 100644 --- a/crates/collab_ui/src/contacts_popover.rs +++ b/crates/collab_ui/src/contacts_popover.rs @@ -1,9 +1,14 @@ -use crate::{contact_finder::ContactFinder, contact_list::ContactList, ToggleContactsMenu}; +use crate::{ + contact_finder::{build_contact_finder, ContactFinder}, + contact_list::ContactList, + ToggleContactsMenu, +}; use client::UserStore; use gpui::{ actions, elements::*, platform::MouseButton, AppContext, Entity, ModelHandle, View, ViewContext, ViewHandle, }; +use picker::PickerEvent; use project::Project; use settings::Settings; @@ -50,19 +55,19 @@ impl ContactsPopover { fn toggle_contact_finder(&mut self, _: &ToggleContactFinder, cx: &mut ViewContext) { match &self.child { Child::ContactList(list) => self.show_contact_finder(list.read(cx).editor_text(cx), cx), - Child::ContactFinder(finder) => { - self.show_contact_list(finder.read(cx).editor_text(cx), cx) - } + Child::ContactFinder(finder) => self.show_contact_list(finder.read(cx).query(cx), cx), } } fn show_contact_finder(&mut self, editor_text: String, cx: &mut ViewContext) { let child = cx.add_view(|cx| { - ContactFinder::new(self.user_store.clone(), cx).with_editor_text(editor_text, cx) + let finder = build_contact_finder(self.user_store.clone(), cx); + finder.set_query(editor_text, cx); + finder }); cx.focus(&child); self._subscription = Some(cx.subscribe(&child, |_, _, event, cx| match event { - crate::contact_finder::Event::Dismissed => cx.emit(Event::Dismissed), + PickerEvent::Dismiss => cx.emit(Event::Dismissed), })); self.child = Child::ContactFinder(child); cx.notify(); diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index cecf793825..5e47de01dd 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -1,24 +1,25 @@ use collections::CommandPaletteFilter; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - actions, elements::*, keymap_matcher::Keystroke, Action, AnyViewHandle, AppContext, Drawable, - Entity, MouseState, View, ViewContext, ViewHandle, + actions, elements::*, keymap_matcher::Keystroke, Action, AppContext, Drawable, MouseState, + ViewContext, }; -use picker::{Picker, PickerDelegate}; +use picker::{Picker, PickerDelegate, PickerEvent}; use settings::Settings; use std::cmp; use util::ResultExt; use workspace::Workspace; pub fn init(cx: &mut AppContext) { - cx.add_action(CommandPalette::toggle); - Picker::::init(cx); + cx.add_action(toggle_command_palette); + Picker::::init(cx); } actions!(command_palette, [Toggle]); -pub struct CommandPalette { - picker: ViewHandle>, +pub type CommandPalette = Picker; + +pub struct CommandPaletteDelegate { actions: Vec, matches: Vec, selected_ix: usize, @@ -40,9 +41,19 @@ struct Command { keystrokes: Vec, } -impl CommandPalette { - pub fn new(focused_view_id: usize, cx: &mut ViewContext) -> Self { - let this = cx.weak_handle(); +fn toggle_command_palette(_: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { + let workspace = cx.handle(); + let focused_view_id = cx.focused_view_id().unwrap_or_else(|| workspace.id()); + + cx.defer(move |workspace, cx| { + workspace.toggle_modal(cx, |_, cx| { + cx.add_view(|cx| Picker::new(CommandPaletteDelegate::new(focused_view_id, cx), cx)) + }); + }); +} + +impl CommandPaletteDelegate { + pub fn new(focused_view_id: usize, cx: &mut ViewContext>) -> Self { let actions = cx .available_actions(focused_view_id) .filter_map(|(name, action, bindings)| { @@ -65,73 +76,20 @@ impl CommandPalette { }) .collect(); - let picker = cx.add_view(|cx| Picker::new("Execute a command...", this, cx)); Self { - picker, actions, matches: vec![], selected_ix: 0, focused_view_id, } } - - fn toggle(_: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { - let workspace = cx.handle(); - let focused_view_id = cx.focused_view_id().unwrap_or_else(|| workspace.id()); - - cx.defer(move |workspace, cx| { - let this = cx.add_view(|cx| Self::new(focused_view_id, cx)); - workspace.toggle_modal(cx, |_, 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), - Event::Confirmed { - window_id, - focused_view_id, - action, - } => { - let window_id = *window_id; - let focused_view_id = *focused_view_id; - let action = action.boxed_clone(); - workspace.dismiss_modal(cx); - cx.defer(move |_, cx| cx.dispatch_any_action_at(window_id, focused_view_id, action)) - } - } - } } -impl Entity for CommandPalette { - type Event = Event; -} - -impl View for CommandPalette { - fn ui_name() -> &'static str { - "CommandPalette" +impl PickerDelegate for CommandPaletteDelegate { + fn placeholder_text(&self) -> std::sync::Arc { + "Execute a command...".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 CommandPalette { fn match_count(&self) -> usize { self.matches.len() } @@ -140,14 +98,14 @@ impl PickerDelegate for CommandPalette { self.selected_ix } - fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext) { + fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext>) { self.selected_ix = ix; } fn update_matches( &mut self, query: String, - cx: &mut gpui::ViewContext, + cx: &mut ViewContext>, ) -> gpui::Task<()> { let candidates = self .actions @@ -159,7 +117,7 @@ impl PickerDelegate for CommandPalette { char_bag: command.name.chars().collect(), }) .collect::>(); - cx.spawn(move |this, mut cx| async move { + cx.spawn(move |picker, mut cx| async move { let matches = if query.is_empty() { candidates .into_iter() @@ -182,33 +140,36 @@ impl PickerDelegate for CommandPalette { ) .await }; - this.update(&mut cx, |this, _| { - this.matches = matches; - if this.matches.is_empty() { - this.selected_ix = 0; - } else { - this.selected_ix = cmp::min(this.selected_ix, this.matches.len() - 1); - } - }) - .log_err(); + picker + .update(&mut cx, |picker, _| { + let delegate = picker.delegate_mut(); + delegate.matches = matches; + if delegate.matches.is_empty() { + delegate.selected_ix = 0; + } else { + delegate.selected_ix = + cmp::min(delegate.selected_ix, delegate.matches.len() - 1); + } + }) + .log_err(); }) } - fn dismiss(&mut self, cx: &mut ViewContext) { - cx.emit(Event::Dismissed); + fn dismissed(&mut self, cx: &mut ViewContext>) { + cx.emit(PickerEvent::Dismiss); } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, cx: &mut ViewContext>) { if !self.matches.is_empty() { + let window_id = cx.window_id(); + let focused_view_id = self.focused_view_id; let action_ix = self.matches[self.selected_ix].candidate_id; - cx.emit(Event::Confirmed { - window_id: cx.window_id(), - focused_view_id: self.focused_view_id, - action: self.actions.remove(action_ix).action, + let action = self.actions.remove(action_ix).action; + cx.defer(move |_, cx| { + cx.dispatch_any_action_at(window_id, focused_view_id, action); }); - } else { - cx.emit(Event::Dismissed); } + cx.emit(PickerEvent::Dismiss); } fn render_match( @@ -353,7 +314,7 @@ mod tests { }); workspace.update(cx, |workspace, cx| { - CommandPalette::toggle(workspace, &Toggle, cx) + toggle_command_palette(workspace, &Toggle, cx); }); let palette = workspace.read_with(cx, |workspace, _| { @@ -362,7 +323,9 @@ mod tests { palette .update(cx, |palette, cx| { - palette.update_matches("bcksp".to_string(), cx) + palette + .delegate_mut() + .update_matches("bcksp".to_string(), cx) }) .await; @@ -383,12 +346,12 @@ mod tests { }); workspace.update(cx, |workspace, cx| { - CommandPalette::toggle(workspace, &Toggle, cx); + CommandPaletteDelegate::toggle(workspace, &Toggle, cx); }); // Assert editor command not present let palette = workspace.read_with(cx, |workspace, _| { - workspace.modal::().unwrap() + workspace.modal::().unwrap() }); palette diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 6738ba11b9..220b3f124a 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -1,7 +1,6 @@ use fuzzy::PathMatch; use gpui::{ - actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState, Task, View, - ViewContext, ViewHandle, + actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle, }; use picker::{Picker, PickerDelegate}; use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId}; @@ -16,9 +15,11 @@ use std::{ use util::{post_inc, ResultExt}; use workspace::Workspace; -pub struct FileFinder { +pub type FileFinder = Picker; + +pub struct FileFinderDelegate { + workspace: WeakViewHandle, project: ModelHandle, - picker: ViewHandle>, search_count: usize, latest_search_id: usize, latest_search_did_cancel: bool, @@ -32,8 +33,26 @@ pub struct FileFinder { actions!(file_finder, [Toggle]); pub fn init(cx: &mut AppContext) { - cx.add_action(FileFinder::toggle); - Picker::::init(cx); + cx.add_action(toggle_file_finder); + FileFinder::init(cx); +} + +fn toggle_file_finder(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { + workspace.toggle_modal(cx, |workspace, cx| { + let relative_to = workspace + .active_item(cx) + .and_then(|item| item.project_path(cx)) + .map(|project_path| project_path.path.clone()); + let project = workspace.project().clone(); + let workspace = cx.handle().downgrade(); + let finder = cx.add_view(|cx| { + Picker::new( + FileFinderDelegate::new(workspace, project, relative_to, cx), + cx, + ) + }); + finder + }); } pub enum Event { @@ -41,27 +60,7 @@ pub enum Event { Dismissed, } -impl Entity for FileFinder { - type Event = Event; -} - -impl View for FileFinder { - fn ui_name() -> &'static str { - "FileFinder" - } - - 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 FileFinder { +impl FileFinderDelegate { fn labels_for_match(&self, path_match: &PathMatch) -> (String, Vec, String, Vec) { let path = &path_match.path; let path_string = path.to_string_lossy(); @@ -88,48 +87,20 @@ impl FileFinder { (file_name, file_name_positions, full_path, path_positions) } - fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { - workspace.toggle_modal(cx, |workspace, cx| { - let project = workspace.project().clone(); - let relative_to = workspace - .active_item(cx) - .and_then(|item| item.project_path(cx)) - .map(|project_path| project_path.path.clone()); - let finder = cx.add_view(|cx| Self::new(project, relative_to, cx)); - cx.subscribe(&finder, Self::on_event).detach(); - finder - }); - } - - fn on_event( - workspace: &mut Workspace, - _: ViewHandle, - event: &Event, - cx: &mut ViewContext, - ) { - match event { - Event::Selected(project_path) => { - workspace - .open_path(project_path.clone(), None, true, cx) - .detach_and_log_err(cx); - workspace.dismiss_modal(cx); - } - Event::Dismissed => { - workspace.dismiss_modal(cx); - } - } - } - pub fn new( + workspace: WeakViewHandle, project: ModelHandle, relative_to: Option>, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Self { - let handle = cx.weak_handle(); - cx.observe(&project, Self::project_updated).detach(); + cx.observe(&project, |picker, _, cx| { + let query = picker.query(cx); + picker.delegate_mut().spawn_search(query, cx).detach(); + }) + .detach(); Self { + workspace, project, - picker: cx.add_view(|cx| Picker::new("Search project files...", handle, cx)), search_count: 0, latest_search_id: 0, latest_search_did_cancel: false, @@ -141,12 +112,7 @@ impl FileFinder { } } - fn project_updated(&mut self, _: ModelHandle, cx: &mut ViewContext) { - self.spawn_search(self.picker.read(cx).query(cx), cx) - .detach(); - } - - fn spawn_search(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { + fn spawn_search(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { let relative_to = self.relative_to.clone(); let worktrees = self .project @@ -172,7 +138,7 @@ impl FileFinder { self.cancel_flag.store(true, atomic::Ordering::Relaxed); self.cancel_flag = Arc::new(AtomicBool::new(false)); let cancel_flag = self.cancel_flag.clone(); - cx.spawn(|this, mut cx| async move { + cx.spawn(|picker, mut cx| async move { let matches = fuzzy::match_path_sets( candidate_sets.as_slice(), &query, @@ -184,10 +150,13 @@ impl FileFinder { ) .await; let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed); - this.update(&mut cx, |this, cx| { - this.set_matches(search_id, did_cancel, query, matches, cx) - }) - .log_err(); + picker + .update(&mut cx, |picker, cx| { + picker + .delegate_mut() + .set_matches(search_id, did_cancel, query, matches, cx) + }) + .log_err(); }) } @@ -197,7 +166,7 @@ impl FileFinder { did_cancel: bool, query: String, matches: Vec, - cx: &mut ViewContext, + cx: &mut ViewContext, ) { if search_id >= self.latest_search_id { self.latest_search_id = search_id; @@ -209,12 +178,15 @@ impl FileFinder { self.latest_search_query = query; self.latest_search_did_cancel = did_cancel; cx.notify(); - self.picker.update(cx, |_, cx| cx.notify()); } } } -impl PickerDelegate for FileFinder { +impl PickerDelegate for FileFinderDelegate { + fn placeholder_text(&self) -> Arc { + "Search project files...".into() + } + fn match_count(&self) -> usize { self.matches.len() } @@ -232,13 +204,13 @@ impl PickerDelegate for FileFinder { 0 } - fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext) { + fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext) { let mat = &self.matches[ix]; self.selected = Some((mat.worktree_id, mat.path.clone())); cx.notify(); } - fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { + fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { if query.is_empty() { self.latest_search_id = post_inc(&mut self.search_count); self.matches.clear(); @@ -249,18 +221,25 @@ impl PickerDelegate for FileFinder { } } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, cx: &mut ViewContext) { if let Some(m) = self.matches.get(self.selected_index()) { - cx.emit(Event::Selected(ProjectPath { - worktree_id: WorktreeId::from_usize(m.worktree_id), - path: m.path.clone(), - })); + if let Some(workspace) = self.workspace.upgrade(cx) { + let project_path = ProjectPath { + worktree_id: WorktreeId::from_usize(m.worktree_id), + path: m.path.clone(), + }; + + workspace.update(cx, |workspace, cx| { + workspace + .open_path(project_path.clone(), None, true, cx) + .detach_and_log_err(cx); + workspace.dismiss_modal(cx); + }) + } } } - fn dismiss(&mut self, cx: &mut ViewContext) { - cx.emit(Event::Dismissed); - } + fn dismissed(&mut self, _: &mut ViewContext) {} fn render_match( &self, @@ -336,11 +315,11 @@ mod tests { let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); finder .update(cx, |finder, cx| { - finder.update_matches("bna".to_string(), cx) + finder.delegate_mut().update_matches("bna".to_string(), cx) }) .await; finder.read_with(cx, |finder, _| { - assert_eq!(finder.matches.len(), 2); + assert_eq!(finder.delegate().matches.len(), 2); }); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); @@ -385,8 +364,12 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); - let (_, finder) = - cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); + let (_, finder) = cx.add_window(|cx| { + Picker::new( + FileFinderDelegate::new(workspace.read(cx).project().clone(), None, cx), + cx, + ) + }); let query = "hi".to_string(); finder @@ -395,13 +378,14 @@ mod tests { finder.read_with(cx, |f, _| assert_eq!(f.matches.len(), 5)); finder.update(cx, |finder, cx| { - let matches = finder.matches.clone(); + let delegate = finder.delegate_mut(); + let matches = delegate.matches.clone(); // Simulate a search being cancelled after the time limit, // returning only a subset of the matches that would have been found. - drop(finder.spawn_search(query.clone(), cx)); - finder.set_matches( - finder.latest_search_id, + drop(delegate.spawn_search(query.clone(), cx)); + delegate.set_matches( + finder.delegate().latest_search_id, true, // did-cancel query.clone(), vec![matches[1].clone(), matches[3].clone()], @@ -409,16 +393,16 @@ mod tests { ); // Simulate another cancellation. - drop(finder.spawn_search(query.clone(), cx)); - finder.set_matches( - finder.latest_search_id, + drop(delegate.spawn_search(query.clone(), cx)); + delegate.set_matches( + delegate.latest_search_id, true, // did-cancel query.clone(), vec![matches[0].clone(), matches[2].clone(), matches[3].clone()], cx, ); - assert_eq!(finder.matches, matches[0..4]) + assert_eq!(delegate.matches, matches[0..4]) }); } @@ -459,8 +443,12 @@ mod tests { ) .await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); - let (_, finder) = - cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); + let (_, finder) = cx.add_window(|cx| { + Picker::new( + FileFinderDelegate::new(workspace.read(cx).project().clone(), None, cx), + cx, + ) + }); finder .update(cx, |f, cx| f.spawn_search("hi".into(), cx)) .await; @@ -483,8 +471,9 @@ mod tests { ) .await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); - let (_, finder) = - cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); + let (_, finder) = cx.add_window(|cx| { + FileFinderDelegate::new(workspace.read(cx).project().clone(), None, cx) + }); // Even though there is only one worktree, that worktree's filename // is included in the matching, because the worktree is a single file. @@ -536,8 +525,9 @@ mod tests { .await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); - let (_, finder) = - cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); + let (_, finder) = cx.add_window(|cx| { + FileFinderDelegate::new(workspace.read(cx).project().clone(), None, cx) + }); // Run a search that matches two files with the same relative path. finder @@ -582,8 +572,9 @@ mod tests { // first when they have the same name. In this case, b.txt is closer to dir2's a.txt // 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| FileFinder::new(workspace.read(cx).project().clone(), b_path, cx)); + let (_, finder) = cx.add_window(|cx| { + FileFinderDelegate::new(workspace.read(cx).project().clone(), b_path, cx) + }); finder .update(cx, |f, cx| f.spawn_search("a.txt".into(), cx)) @@ -614,8 +605,9 @@ 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| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); + let (_, finder) = cx.add_window(|cx| { + FileFinderDelegate::new(workspace.read(cx).project().clone(), None, cx) + }); finder .update(cx, |f, cx| f.spawn_search("dir".into(), cx)) .await; diff --git a/crates/language_selector/src/language_selector.rs b/crates/language_selector/src/language_selector.rs index c88f46c5d0..cd7c294450 100644 --- a/crates/language_selector/src/language_selector.rs +++ b/crates/language_selector/src/language_selector.rs @@ -163,7 +163,7 @@ impl PickerDelegate for LanguageSelector { cx.emit(Event::Dismissed); } - fn dismiss(&mut self, cx: &mut ViewContext) { + fn dismissed(&mut self, cx: &mut ViewContext) { cx.emit(Event::Dismissed); } diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 7172eb5035..eaac215ffa 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -229,7 +229,7 @@ impl PickerDelegate for OutlineView { cx.emit(Event::Dismissed); } - fn dismiss(&mut self, cx: &mut ViewContext) { + fn dismissed(&mut self, cx: &mut ViewContext) { self.restore_active_editor(cx); cx.emit(Event::Dismissed); } diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index c17daa891f..63d758c6ee 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -5,15 +5,20 @@ use gpui::{ keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton}, AnyViewHandle, AppContext, Axis, Element, Entity, MouseState, Task, View, ViewContext, - ViewHandle, WeakViewHandle, + ViewHandle, }; use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev}; use parking_lot::Mutex; use std::{cmp, sync::Arc}; use util::ResultExt; +use workspace::Modal; + +pub enum PickerEvent { + Dismiss, +} pub struct Picker { - delegate: WeakViewHandle, + delegate: D, query_editor: ViewHandle, list_state: UniformListState, max_size: Vector2F, @@ -21,13 +26,14 @@ pub struct Picker { confirmed: bool, } -pub trait PickerDelegate: View { +pub trait PickerDelegate: Sized + 'static { + fn placeholder_text(&self) -> Arc; fn match_count(&self) -> usize; fn selected_index(&self) -> usize; - fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext); - fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> Task<()>; - fn confirm(&mut self, cx: &mut ViewContext); - fn dismiss(&mut self, cx: &mut ViewContext); + fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext>); + fn update_matches(&mut self, query: String, cx: &mut ViewContext>) -> Task<()>; + fn confirm(&mut self, cx: &mut ViewContext>); + fn dismissed(&mut self, cx: &mut ViewContext>); fn render_match( &self, ix: usize, @@ -41,7 +47,7 @@ pub trait PickerDelegate: View { } impl Entity for Picker { - type Event = (); + type Event = PickerEvent; } impl View for Picker { @@ -52,12 +58,7 @@ impl View for Picker { fn render(&mut self, cx: &mut ViewContext) -> Element { let theme = (self.theme.lock())(&cx.global::().theme); let query = self.query(cx); - let delegate = self.delegate.clone(); - let match_count = if let Some(delegate) = delegate.upgrade(cx) { - delegate.read(cx).match_count() - } else { - 0 - }; + let match_count = self.delegate.match_count(); let container_style; let editor_style; @@ -94,14 +95,11 @@ impl View for Picker { match_count, cx, move |this, mut range, items, cx| { - let delegate = this.delegate.upgrade(cx).unwrap(); - let selected_ix = delegate.read(cx).selected_index(); - range.end = cmp::min(range.end, delegate.read(cx).match_count()); + let selected_ix = this.delegate.selected_index(); + range.end = cmp::min(range.end, this.delegate.match_count()); items.extend(range.map(move |ix| { MouseEventHandler::::new(ix, cx, |state, cx| { - delegate - .read(cx) - .render_match(ix, state, ix == selected_ix, cx) + this.delegate.render_match(ix, state, ix == selected_ix, cx) }) // Capture mouse events .on_down(MouseButton::Left, |_, _, _| {}) @@ -141,6 +139,12 @@ impl View for Picker { } } +impl Modal for Picker { + fn dismiss_on_event(event: &Self::Event) -> bool { + matches!(event, PickerEvent::Dismiss) + } +} + impl Picker { pub fn init(cx: &mut AppContext) { cx.add_action(Self::select_first); @@ -152,14 +156,12 @@ impl Picker { cx.add_action(Self::cancel); } - pub fn new

(placeholder: P, delegate: WeakViewHandle, cx: &mut ViewContext) -> Self - where - P: Into>, - { + pub fn new(delegate: D, cx: &mut ViewContext) -> Self { let theme = Arc::new(Mutex::new( Box::new(|theme: &theme::Theme| theme.picker.clone()) as Box theme::Picker>, )); + let placeholder_text = delegate.placeholder_text(); let query_editor = cx.add_view({ let picker_theme = theme.clone(); |cx| { @@ -169,13 +171,13 @@ impl Picker { })), cx, ); - editor.set_placeholder_text(placeholder, cx); + editor.set_placeholder_text(placeholder_text, cx); editor } }); cx.subscribe(&query_editor, Self::on_query_editor_event) .detach(); - let this = Self { + let mut this = Self { query_editor, list_state: Default::default(), delegate, @@ -183,12 +185,9 @@ impl Picker { theme, confirmed: false, }; - cx.defer(|this, cx| { - if let Some(delegate) = this.delegate.upgrade(cx) { - cx.observe(&delegate, |_, _, cx| cx.notify()).detach(); - this.update_matches(String::new(), cx) - } - }); + // TODO! How can the delegate notify the picker to update? + // cx.observe(&delegate, |_, _, cx| cx.notify()).detach(); + this.update_matches(String::new(), cx); this } @@ -205,6 +204,14 @@ impl Picker { self } + pub fn delegate(&self) -> &D { + &self.delegate + } + + pub fn delegate_mut(&mut self) -> &mut D { + &mut self.delegate + } + pub fn query(&self, cx: &AppContext) -> String { self.query_editor.read(cx).text(cx) } @@ -223,121 +230,93 @@ impl Picker { match event { editor::Event::BufferEdited { .. } => self.update_matches(self.query(cx), cx), editor::Event::Blurred if !self.confirmed => { - if let Some(delegate) = self.delegate.upgrade(cx) { - delegate.update(cx, |delegate, cx| { - delegate.dismiss(cx); - }) - } + self.dismiss(cx); } _ => {} } } pub fn update_matches(&mut self, query: String, cx: &mut ViewContext) { - if let Some(delegate) = self.delegate.upgrade(cx) { - let update = delegate.update(cx, |d, cx| d.update_matches(query, cx)); - cx.spawn_weak(|this, mut cx| async move { - update.await; - this.upgrade(&cx)? - .update(&mut cx, |this, cx| { - if let Some(delegate) = this.delegate.upgrade(cx) { - 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(); - } - }) - .log_err() - }) - .detach() - } + let update = self.delegate.update_matches(query, cx); + 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(); + }) + .log_err() + }) + .detach() } pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext) { - if let Some(delegate) = self.delegate.upgrade(cx) { - delegate.update(cx, |delegate, cx| { - if delegate.match_count() > 0 { - delegate.set_selected_index(0, cx); - self.list_state.scroll_to(ScrollTarget::Show(0)); - } - }); - - cx.notify(); + if self.delegate.match_count() > 0 { + self.delegate.set_selected_index(0, cx); + self.list_state.scroll_to(ScrollTarget::Show(0)); } + + cx.notify(); } pub fn select_index(&mut self, action: &SelectIndex, cx: &mut ViewContext) { - if let Some(delegate) = self.delegate.upgrade(cx) { - let index = action.0; - delegate.update(cx, |delegate, cx| { - if delegate.match_count() > 0 { - self.confirmed = true; - delegate.set_selected_index(index, cx); - delegate.confirm(cx); - } - }); + let index = action.0; + if self.delegate.match_count() > 0 { + self.confirmed = true; + self.delegate.set_selected_index(index, cx); + self.delegate.confirm(cx); } } pub fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext) { - if let Some(delegate) = self.delegate.upgrade(cx) { - delegate.update(cx, |delegate, cx| { - let match_count = delegate.match_count(); - if match_count > 0 { - let index = match_count - 1; - delegate.set_selected_index(index, cx); - self.list_state.scroll_to(ScrollTarget::Show(index)); - } - }); - cx.notify(); + let match_count = self.delegate.match_count(); + if match_count > 0 { + let index = match_count - 1; + self.delegate.set_selected_index(index, cx); + self.list_state.scroll_to(ScrollTarget::Show(index)); } + cx.notify(); } pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext) { - if let Some(delegate) = self.delegate.upgrade(cx) { - delegate.update(cx, |delegate, cx| { - let next_index = delegate.selected_index() + 1; - if next_index < delegate.match_count() { - delegate.set_selected_index(next_index, cx); - self.list_state.scroll_to(ScrollTarget::Show(next_index)); - } - }); - - cx.notify(); + let next_index = self.delegate.selected_index() + 1; + if next_index < self.delegate.match_count() { + self.delegate.set_selected_index(next_index, cx); + self.list_state.scroll_to(ScrollTarget::Show(next_index)); } + + cx.notify(); } pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext) { - if let Some(delegate) = self.delegate.upgrade(cx) { - delegate.update(cx, |delegate, cx| { - let mut selected_index = delegate.selected_index(); - if selected_index > 0 { - selected_index -= 1; - delegate.set_selected_index(selected_index, cx); - self.list_state - .scroll_to(ScrollTarget::Show(selected_index)); - } - }); - - cx.notify(); + let mut selected_index = self.delegate.selected_index(); + if selected_index > 0 { + selected_index -= 1; + self.delegate.set_selected_index(selected_index, cx); + self.list_state + .scroll_to(ScrollTarget::Show(selected_index)); } + + cx.notify(); } fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { - if let Some(delegate) = self.delegate.upgrade(cx) { - self.confirmed = true; - delegate.update(cx, |delegate, cx| delegate.confirm(cx)); - } + self.confirmed = true; + self.delegate.confirm(cx); } fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { - if let Some(delegate) = self.delegate.upgrade(cx) { - delegate.update(cx, |delegate, cx| delegate.dismiss(cx)); - } + self.dismiss(cx); + } + + fn dismiss(&mut self, cx: &mut ViewContext) { + cx.emit(PickerEvent::Dismiss); + self.delegate.dismissed(cx); } } diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 62511d9dd7..1c73fc5cdd 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -176,7 +176,7 @@ impl PickerDelegate for ProjectSymbolsView { } } - fn dismiss(&mut self, cx: &mut ViewContext) { + fn dismissed(&mut self, cx: &mut ViewContext) { cx.emit(Event::Dismissed); } diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 81ff50e458..e19e80bca8 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -176,7 +176,7 @@ impl PickerDelegate for RecentProjectsView { } } - fn dismiss(&mut self, cx: &mut ViewContext) { + fn dismissed(&mut self, cx: &mut ViewContext) { cx.emit(Event::Dismissed); } diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index f03496c82b..b70ae7b040 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -156,7 +156,7 @@ impl PickerDelegate for ThemeSelector { cx.emit(Event::Dismissed); } - fn dismiss(&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; diff --git a/crates/welcome/src/base_keymap_picker.rs b/crates/welcome/src/base_keymap_picker.rs index d2a6f6557f..bdb4e7603f 100644 --- a/crates/welcome/src/base_keymap_picker.rs +++ b/crates/welcome/src/base_keymap_picker.rs @@ -155,7 +155,7 @@ impl PickerDelegate for BaseKeymapSelector { cx.emit(Event::Dismissed); } - fn dismiss(&mut self, cx: &mut ViewContext) { + fn dismissed(&mut self, cx: &mut ViewContext) { cx.emit(Event::Dismissed) } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 308db5dc6c..90fc3f2f07 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -96,6 +96,10 @@ lazy_static! { .and_then(parse_pixel_position_env_var); } +pub trait Modal: View { + fn dismiss_on_event(event: &Self::Event) -> bool; +} + #[derive(Clone, PartialEq)] pub struct RemoveWorktreeFromProject(pub WorktreeId); @@ -1335,7 +1339,7 @@ impl Workspace { add_view: F, ) -> Option> where - V: 'static + View, + V: 'static + Modal, F: FnOnce(&mut Self, &mut ViewContext) -> ViewHandle, { cx.notify(); @@ -1347,6 +1351,12 @@ impl Workspace { Some(already_open_modal) } else { let modal = add_view(self, cx); + cx.subscribe(&modal, |this, _, event, cx| { + if V::dismiss_on_event(event) { + this.dismiss_modal(cx); + } + }) + .detach(); cx.focus(&modal); self.modal = Some(modal.into_any()); None