Detailed changes
@@ -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::<ContactFinder>::init(cx);
+ Picker::<ContactFinderDelegate>::init(cx);
}
-pub struct ContactFinder {
- picker: ViewHandle<Picker<Self>>,
- potential_contacts: Arc<[Arc<User>]>,
- user_store: ModelHandle<UserStore>,
- selected_index: usize,
-}
+pub type ContactFinder = Picker<ContactFinderDelegate>;
-pub enum Event {
- Dismissed,
+pub fn build_contact_finder(
+ user_store: ModelHandle<UserStore>,
+ cx: &mut ViewContext<ContactFinder>,
+) -> ContactFinder {
+ Picker::new(
+ ContactFinderDelegate {
+ user_store,
+ potential_contacts: Arc::from([]),
+ selected_index: 0,
+ },
+ cx,
+ )
}
-impl Entity for ContactFinder {
- type Event = Event;
+pub struct ContactFinderDelegate {
+ potential_contacts: Arc<[Arc<User>]>,
+ user_store: ModelHandle<UserStore>,
+ selected_index: usize,
}
-impl View for ContactFinder {
- fn ui_name() -> &'static str {
- "ContactFinder"
- }
-
- fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
- ChildView::new(&self.picker, cx).boxed()
- }
-
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- if cx.is_self_focused() {
- cx.focus(&self.picker);
- }
+impl PickerDelegate for ContactFinderDelegate {
+ fn placeholder_text(&self) -> Arc<str> {
+ "Search collaborator by username...".into()
}
-}
-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<Self>) {
+ fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
- fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
+ fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> 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<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
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<Self>) {
- cx.emit(Event::Dismissed);
+ fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
+ cx.emit(PickerEvent::Dismiss);
}
fn render_match(
@@ -164,28 +156,3 @@ impl PickerDelegate for ContactFinder {
.boxed()
}
}
-
-impl ContactFinder {
- pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> 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 {
- self.picker
- .update(cx, |picker, cx| picker.set_query(editor_text, cx));
- self
- }
-}
@@ -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<Self>) {
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<ContactsPopover>) {
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();
@@ -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::<CommandPalette>::init(cx);
+ cx.add_action(toggle_command_palette);
+ Picker::<CommandPaletteDelegate>::init(cx);
}
actions!(command_palette, [Toggle]);
-pub struct CommandPalette {
- picker: ViewHandle<Picker<Self>>,
+pub type CommandPalette = Picker<CommandPaletteDelegate>;
+
+pub struct CommandPaletteDelegate {
actions: Vec<Command>,
matches: Vec<StringMatch>,
selected_ix: usize,
@@ -40,9 +41,19 @@ struct Command {
keystrokes: Vec<Keystroke>,
}
-impl CommandPalette {
- pub fn new(focused_view_id: usize, cx: &mut ViewContext<Self>) -> Self {
- let this = cx.weak_handle();
+fn toggle_command_palette(_: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+ 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<Picker<Self>>) -> 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<Workspace>) {
- 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<Self>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- 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"
- }
-
- fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
- ChildView::new(&self.picker, cx).boxed()
+impl PickerDelegate for CommandPaletteDelegate {
+ fn placeholder_text(&self) -> std::sync::Arc<str> {
+ "Execute a command...".into()
}
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- 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<Self>) {
+ fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
self.selected_ix = ix;
}
fn update_matches(
&mut self,
query: String,
- cx: &mut gpui::ViewContext<Self>,
+ cx: &mut ViewContext<Picker<Self>>,
) -> gpui::Task<()> {
let candidates = self
.actions
@@ -159,7 +117,7 @@ impl PickerDelegate for CommandPalette {
char_bag: command.name.chars().collect(),
})
.collect::<Vec<_>>();
- 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<Self>) {
- cx.emit(Event::Dismissed);
+ fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
+ cx.emit(PickerEvent::Dismiss);
}
- fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
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::<CommandPalette>().unwrap()
+ workspace.modal::<CommandPaletteDelegate>().unwrap()
});
palette
@@ -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<FileFinderDelegate>;
+
+pub struct FileFinderDelegate {
+ workspace: WeakViewHandle<Workspace>,
project: ModelHandle<Project>,
- picker: ViewHandle<Picker<Self>>,
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::<FileFinder>::init(cx);
+ cx.add_action(toggle_file_finder);
+ FileFinder::init(cx);
+}
+
+fn toggle_file_finder(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+ 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<Self>) -> Element<Self> {
- ChildView::new(&self.picker, cx).boxed()
- }
-
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- if cx.is_self_focused() {
- cx.focus(&self.picker);
- }
- }
-}
-
-impl FileFinder {
+impl FileFinderDelegate {
fn labels_for_match(&self, path_match: &PathMatch) -> (String, Vec<usize>, String, Vec<usize>) {
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>) {
- 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<FileFinder>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- 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<Workspace>,
project: ModelHandle<Project>,
relative_to: Option<Arc<Path>>,
- cx: &mut ViewContext<Self>,
+ cx: &mut ViewContext<FileFinder>,
) -> 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<Project>, cx: &mut ViewContext<Self>) {
- self.spawn_search(self.picker.read(cx).query(cx), cx)
- .detach();
- }
-
- fn spawn_search(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
+ fn spawn_search(&mut self, query: String, cx: &mut ViewContext<FileFinder>) -> 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<PathMatch>,
- cx: &mut ViewContext<Self>,
+ cx: &mut ViewContext<FileFinder>,
) {
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<str> {
+ "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<Self>) {
+ fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<FileFinder>) {
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<Self>) -> Task<()> {
+ fn update_matches(&mut self, query: String, cx: &mut ViewContext<FileFinder>) -> 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<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<FileFinder>) {
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<Self>) {
- cx.emit(Event::Dismissed);
- }
+ fn dismissed(&mut self, _: &mut ViewContext<FileFinder>) {}
fn render_match(
&self,
@@ -336,11 +315,11 @@ mod tests {
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().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;
@@ -163,7 +163,7 @@ impl PickerDelegate for LanguageSelector {
cx.emit(Event::Dismissed);
}
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+ fn dismissed(&mut self, cx: &mut ViewContext<Self>) {
cx.emit(Event::Dismissed);
}
@@ -229,7 +229,7 @@ impl PickerDelegate for OutlineView {
cx.emit(Event::Dismissed);
}
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+ fn dismissed(&mut self, cx: &mut ViewContext<Self>) {
self.restore_active_editor(cx);
cx.emit(Event::Dismissed);
}
@@ -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<D: PickerDelegate> {
- delegate: WeakViewHandle<D>,
+ delegate: D,
query_editor: ViewHandle<Editor>,
list_state: UniformListState,
max_size: Vector2F,
@@ -21,13 +26,14 @@ pub struct Picker<D: PickerDelegate> {
confirmed: bool,
}
-pub trait PickerDelegate: View {
+pub trait PickerDelegate: Sized + 'static {
+ fn placeholder_text(&self) -> Arc<str>;
fn match_count(&self) -> usize;
fn selected_index(&self) -> usize;
- fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>);
- fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()>;
- fn confirm(&mut self, cx: &mut ViewContext<Self>);
- fn dismiss(&mut self, cx: &mut ViewContext<Self>);
+ fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
+ fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
+ fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>);
+ fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
fn render_match(
&self,
ix: usize,
@@ -41,7 +47,7 @@ pub trait PickerDelegate: View {
}
impl<D: PickerDelegate> Entity for Picker<D> {
- type Event = ();
+ type Event = PickerEvent;
}
impl<D: PickerDelegate> View for Picker<D> {
@@ -52,12 +58,7 @@ impl<D: PickerDelegate> View for Picker<D> {
fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
let theme = (self.theme.lock())(&cx.global::<settings::Settings>().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<D: PickerDelegate> View for Picker<D> {
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::<D, _>::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<D: PickerDelegate> View for Picker<D> {
}
}
+impl<D: PickerDelegate> Modal for Picker<D> {
+ fn dismiss_on_event(event: &Self::Event) -> bool {
+ matches!(event, PickerEvent::Dismiss)
+ }
+}
+
impl<D: PickerDelegate> Picker<D> {
pub fn init(cx: &mut AppContext) {
cx.add_action(Self::select_first);
@@ -152,14 +156,12 @@ impl<D: PickerDelegate> Picker<D> {
cx.add_action(Self::cancel);
}
- pub fn new<P>(placeholder: P, delegate: WeakViewHandle<D>, cx: &mut ViewContext<Self>) -> Self
- where
- P: Into<Arc<str>>,
- {
+ pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
let theme = Arc::new(Mutex::new(
Box::new(|theme: &theme::Theme| theme.picker.clone())
as Box<dyn Fn(&theme::Theme) -> 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<D: PickerDelegate> Picker<D> {
})),
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<D: PickerDelegate> Picker<D> {
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<D: PickerDelegate> Picker<D> {
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<D: PickerDelegate> Picker<D> {
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<Self>) {
- 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<Self>) {
- 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<Self>) {
- 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<Self>) {
- 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<Self>) {
- 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<Self>) {
- 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<Self>) {
- 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<Self>) {
- 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<Self>) {
+ cx.emit(PickerEvent::Dismiss);
+ self.delegate.dismissed(cx);
}
}
@@ -176,7 +176,7 @@ impl PickerDelegate for ProjectSymbolsView {
}
}
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+ fn dismissed(&mut self, cx: &mut ViewContext<Self>) {
cx.emit(Event::Dismissed);
}
@@ -176,7 +176,7 @@ impl PickerDelegate for RecentProjectsView {
}
}
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+ fn dismissed(&mut self, cx: &mut ViewContext<Self>) {
cx.emit(Event::Dismissed);
}
@@ -156,7 +156,7 @@ impl PickerDelegate for ThemeSelector {
cx.emit(Event::Dismissed);
}
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+ fn dismissed(&mut self, cx: &mut ViewContext<Self>) {
if !self.selection_completed {
Self::set_theme(self.original_theme.clone(), cx);
self.selection_completed = true;
@@ -155,7 +155,7 @@ impl PickerDelegate for BaseKeymapSelector {
cx.emit(Event::Dismissed);
}
- fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+ fn dismissed(&mut self, cx: &mut ViewContext<Self>) {
cx.emit(Event::Dismissed)
}
@@ -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<ViewHandle<V>>
where
- V: 'static + View,
+ V: 'static + Modal,
F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
{
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