Detailed changes
@@ -12,7 +12,7 @@ use workspace::Workspace;
pub fn init(cx: &mut AppContext) {
cx.add_action(toggle_command_palette);
- Picker::<CommandPaletteDelegate>::init(cx);
+ CommandPalette::init(cx);
}
actions!(command_palette, [Toggle]);
@@ -155,9 +155,7 @@ impl PickerDelegate for CommandPaletteDelegate {
})
}
- fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
- cx.emit(PickerEvent::Dismiss);
- }
+ fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
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::<CommandPaletteDelegate>().unwrap()
+ workspace.modal::<CommandPalette>().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())
+ });
}
}
@@ -94,8 +94,7 @@ impl FileFinderDelegate {
cx: &mut ViewContext<FileFinder>,
) -> 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);
});
}
}
@@ -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::<Editor>())
{
- 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<Self>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- match event {
- Event::Dismissed => workspace.dismiss_modal(cx),
- }
- }
-
fn on_line_editor_event(
&mut self,
_: ViewHandle<Editor>,
@@ -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)
+ }
+}
@@ -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<AppState>, cx: &mut AppContext) {
- Picker::<LanguageSelector>::init(cx);
+ Picker::<LanguageSelectorDelegate>::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<LanguageRegistry>,
+ cx: &mut ViewContext<Workspace>,
+) -> Option<()> {
+ let (_, buffer, _) = workspace
+ .active_item(cx)?
+ .act_as::<Editor>(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<Buffer>,
project: ModelHandle<Project>,
language_registry: Arc<LanguageRegistry>,
candidates: Vec<StringMatchCandidate>,
matches: Vec<StringMatch>,
- picker: ViewHandle<Picker<Self>>,
selected_index: usize,
}
-impl LanguageSelector {
+impl LanguageSelectorDelegate {
fn new(
buffer: ModelHandle<Buffer>,
project: ModelHandle<Project>,
language_registry: Arc<LanguageRegistry>,
- cx: &mut ViewContext<Self>,
) -> 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<LanguageRegistry>,
- cx: &mut ViewContext<Workspace>,
- ) {
- if let Some((_, buffer, _)) = workspace
- .active_item(cx)
- .and_then(|active_item| active_item.act_as::<Editor>(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<LanguageSelector>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- 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"
- }
-
- fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
- ChildView::new(&self.picker, cx).boxed()
+impl PickerDelegate for LanguageSelectorDelegate {
+ fn placeholder_text(&self) -> Arc<str> {
+ "Select a language...".into()
}
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- 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<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
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<Self>) {
- cx.emit(Event::Dismissed);
- }
+ fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
fn selected_index(&self) -> usize {
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>) -> gpui::Task<()> {
+ fn update_matches(
+ &mut self,
+ query: String,
+ cx: &mut ViewContext<Picker<Self>>,
+ ) -> 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();
@@ -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::<OutlineView>::init(cx);
+ cx.add_action(toggle);
+ OutlineView::init(cx);
+}
+
+fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+ if let Some(editor) = workspace
+ .active_item(cx)
+ .and_then(|item| item.downcast::<Editor>())
+ {
+ let outline = editor
+ .read(cx)
+ .buffer()
+ .read(cx)
+ .snapshot(cx)
+ .outline(Some(cx.global::<Settings>().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.)
+ })
+ });
+ }
+ }
}
-struct OutlineView {
- picker: ViewHandle<Picker<Self>>,
+type OutlineView = Picker<OutlineViewDelegate>;
+
+struct OutlineViewDelegate {
active_editor: ViewHandle<Editor>,
outline: Outline<Anchor>,
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<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 OutlineView {
+impl OutlineViewDelegate {
fn new(
outline: Outline<Anchor>,
editor: ViewHandle<Editor>,
- cx: &mut ViewContext<Self>,
+ cx: &mut ViewContext<OutlineView>,
) -> 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<Workspace>) {
- if let Some(editor) = workspace
- .active_item(cx)
- .and_then(|item| item.downcast::<Editor>())
- {
- let outline = editor
- .read(cx)
- .buffer()
- .read(cx)
- .snapshot(cx)
- .outline(Some(cx.global::<Settings>().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<Self>) {
+ fn set_selected_index(&mut self, ix: usize, navigate: bool, cx: &mut ViewContext<OutlineView>) {
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<Self>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- match event {
- Event::Dismissed => workspace.dismiss_modal(cx),
- }
+impl PickerDelegate for OutlineViewDelegate {
+ fn placeholder_text(&self) -> Arc<str> {
+ "Search buffer symbols...".into()
}
-}
-impl PickerDelegate for OutlineView {
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<Self>) {
+ fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<OutlineView>) {
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<Self>) -> Task<()> {
+ fn update_matches(&mut self, query: String, cx: &mut ViewContext<OutlineView>) -> 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<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<OutlineView>) {
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<Self>) {
+ fn dismissed(&mut self, cx: &mut ViewContext<OutlineView>) {
self.restore_active_editor(cx);
- cx.emit(Event::Dismissed);
}
fn render_match(
@@ -24,6 +24,7 @@ pub struct Picker<D: PickerDelegate> {
max_size: Vector2F,
theme: Arc<Mutex<Box<dyn Fn(&theme::Theme) -> theme::Picker>>>,
confirmed: bool,
+ pending_update_matches: Task<Option<()>>,
}
pub trait PickerDelegate: Sized + 'static {
@@ -184,6 +185,7 @@ impl<D: PickerDelegate> Picker<D> {
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<D: PickerDelegate> Picker<D> {
pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
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<Self>) {
+ 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<Self>) {
@@ -306,7 +310,7 @@ impl<D: PickerDelegate> Picker<D> {
cx.notify();
}
- fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
+ pub fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
self.confirmed = true;
self.delegate.confirm(cx);
}
@@ -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::<ProjectSymbolsView>::init(cx);
+ cx.add_action(toggle);
+ ProjectSymbols::init(cx);
}
-pub struct ProjectSymbolsView {
- picker: ViewHandle<Picker<Self>>,
+fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+ 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<ProjectSymbolsDelegate>;
+
+pub struct ProjectSymbolsDelegate {
+ workspace: WeakViewHandle<Workspace>,
project: ModelHandle<Project>,
selected_match_index: usize,
symbols: Vec<Symbol>,
visible_match_candidates: Vec<StringMatchCandidate>,
external_match_candidates: Vec<StringMatchCandidate>,
show_worktree_root_name: bool,
- pending_update: Task<()>,
matches: Vec<StringMatch>,
}
-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<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 ProjectSymbolsView {
- fn new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
- let handle = cx.weak_handle();
+impl ProjectSymbolsDelegate {
+ fn new(workspace: WeakViewHandle<Workspace>, project: ModelHandle<Project>) -> 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>) {
- 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<Self>) {
+ fn filter(&mut self, query: &str, cx: &mut ViewContext<ProjectSymbols>) {
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<Self>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- match event {
- Event::Dismissed => workspace.dismiss_modal(cx),
- Event::Selected(symbol) => {
- let buffer = workspace
- .project()
- .update(cx, |project, cx| project.open_buffer_for_symbol(symbol, cx));
-
- let symbol = symbol.clone();
- cx.spawn(|workspace, mut cx| async move {
- let buffer = buffer.await?;
- workspace.update(&mut cx, |workspace, cx| {
- let position = buffer
- .read(cx)
- .clip_point_utf16(symbol.range.start, Bias::Left);
-
- let editor = workspace.open_project_item::<Editor>(buffer, cx);
- editor.update(cx, |editor, cx| {
- editor.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 ProjectSymbolsDelegate {
+ fn placeholder_text(&self) -> Arc<str> {
+ "Search project symbols...".into()
}
-}
-impl PickerDelegate for ProjectSymbolsView {
- fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<ProjectSymbols>) {
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::<Editor>(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<Self>) {
- cx.emit(Event::Dismissed);
- }
+ fn dismissed(&mut self, _cx: &mut ViewContext<ProjectSymbols>) {}
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<Self>) {
+ fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<ProjectSymbols>) {
self.selected_match_index = ix;
- cx.notify();
}
- fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
+ fn update_matches(&mut self, query: String, cx: &mut ViewContext<ProjectSymbols>) -> 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);
});
}
@@ -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::<RecentProjectsView>::init(cx);
+ cx.add_async_action(toggle);
+ RecentProjects::init(cx);
}
-struct RecentProjectsView {
- picker: ViewHandle<Picker<Self>>,
+fn toggle(
+ _: &mut Workspace,
+ _: &OpenRecent,
+ cx: &mut ViewContext<Workspace>,
+) -> Option<Task<Result<()>>> {
+ 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<RecentProjectsDelegate>;
+
+struct RecentProjectsDelegate {
workspace_locations: Vec<WorkspaceLocation>,
selected_match_index: usize,
matches: Vec<StringMatch>,
}
-impl RecentProjectsView {
- fn new(workspace_locations: Vec<WorkspaceLocation>, cx: &mut ViewContext<Self>) -> Self {
- let handle = cx.weak_handle();
+impl RecentProjectsDelegate {
+ fn new(workspace_locations: Vec<WorkspaceLocation>) -> 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<Workspace>) {
- 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<Self>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- 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"
- }
-
- 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 RecentProjectsDelegate {
+ fn placeholder_text(&self) -> Arc<str> {
+ "Recent Projects...".into()
}
-}
-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<Self>) {
+ fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<RecentProjects>) {
self.selected_match_index = ix;
}
- fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
+ fn update_matches(
+ &mut self,
+ query: String,
+ cx: &mut ViewContext<RecentProjects>,
+ ) -> 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<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<RecentProjects>) {
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<Self>) {
- cx.emit(Event::Dismissed);
- }
+ fn dismissed(&mut self, _cx: &mut ViewContext<RecentProjects>) {}
fn render_match(
&self,
@@ -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 {
- registry: Arc<ThemeRegistry>,
- theme_data: Vec<ThemeMeta>,
- matches: Vec<StringMatch>,
- original_theme: Arc<Theme>,
- picker: ViewHandle<Picker<Self>>,
- selection_completed: bool,
- selected_index: usize,
-}
-
actions!(theme_selector, [Toggle, Reload]);
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
- Picker::<ThemeSelector>::init(cx);
cx.add_action({
let theme_registry = app_state.themes.clone();
- move |workspace, _: &Toggle, cx| {
- ThemeSelector::toggle(workspace, theme_registry.clone(), cx)
- }
+ move |workspace, _: &Toggle, cx| toggle(workspace, theme_registry.clone(), cx)
});
+ ThemeSelector::init(cx);
}
-pub enum Event {
- Dismissed,
+fn toggle(workspace: &mut Workspace, themes: Arc<ThemeRegistry>, cx: &mut ViewContext<Workspace>) {
+ workspace.toggle_modal(cx, |_, cx| {
+ cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(themes, cx), cx))
+ });
}
-impl ThemeSelector {
- fn new(registry: Arc<ThemeRegistry>, cx: &mut ViewContext<Self>) -> Self {
- let handle = cx.weak_handle();
- let picker = cx.add_view(|cx| Picker::new("Select Theme...", handle, cx));
+#[cfg(debug_assertions)]
+pub fn reload(themes: Arc<ThemeRegistry>, cx: &mut AppContext) {
+ let current_theme_name = cx.global::<Settings>().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<ThemeSelectorDelegate>;
+
+pub struct ThemeSelectorDelegate {
+ registry: Arc<ThemeRegistry>,
+ theme_data: Vec<ThemeMeta>,
+ matches: Vec<StringMatch>,
+ original_theme: Arc<Theme>,
+ selection_completed: bool,
+ selected_index: usize,
+}
+
+impl ThemeSelectorDelegate {
+ fn new(registry: Arc<ThemeRegistry>, cx: &mut ViewContext<ThemeSelector>) -> Self {
let settings = cx.global::<Settings>();
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<ThemeRegistry>,
- cx: &mut ViewContext<Workspace>,
- ) {
- 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<ThemeRegistry>, cx: &mut AppContext) {
- let current_theme_name = cx.global::<Settings>().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<Self>) {
+ fn show_selected_theme(&mut self, cx: &mut ViewContext<ThemeSelector>) {
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<ThemeSelector>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- match event {
- Event::Dismissed => {
- workspace.dismiss_modal(cx);
- }
- }
- }
-
fn set_theme(theme: Arc<Theme>, cx: &mut AppContext) {
cx.update_global::<Settings, _, _>(|settings, cx| {
settings.theme = theme;
@@ -140,12 +110,16 @@ impl ThemeSelector {
}
}
-impl PickerDelegate for ThemeSelector {
+impl PickerDelegate for ThemeSelectorDelegate {
+ fn placeholder_text(&self) -> Arc<str> {
+ "Select Theme...".into()
+ }
+
fn match_count(&self) -> usize {
self.matches.len()
}
- fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<ThemeSelector>) {
self.selection_completed = true;
let theme_name = cx.global::<Settings>().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<Self>) {
+ fn dismissed(&mut self, cx: &mut ViewContext<ThemeSelector>) {
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<Self>) {
+ fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<ThemeSelector>) {
self.selected_index = ix;
self.show_selected_theme(cx);
}
- fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
+ fn update_matches(
+ &mut self,
+ query: String,
+ cx: &mut ViewContext<ThemeSelector>,
+ ) -> 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<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);
- }
- }
-}
@@ -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<StringMatch>,
- picker: ViewHandle<Picker<Self>>,
- selected_index: usize,
-}
-
actions!(welcome, [ToggleBaseKeymapSelector]);
pub fn init(cx: &mut AppContext) {
- Picker::<BaseKeymapSelector>::init(cx);
- cx.add_action({
- move |workspace, _: &ToggleBaseKeymapSelector, cx| BaseKeymapSelector::toggle(workspace, cx)
- });
+ cx.add_action(toggle);
+ BaseKeymapSelector::init(cx);
}
-pub enum Event {
- Dismissed,
+fn toggle(
+ workspace: &mut Workspace,
+ _: &ToggleBaseKeymapSelector,
+ cx: &mut ViewContext<Workspace>,
+) {
+ workspace.toggle_modal(cx, |_, cx| {
+ cx.add_view(|cx| BaseKeymapSelector::new(BaseKeymapSelectorDelegate::new(cx), cx))
+ });
}
-impl BaseKeymapSelector {
- fn toggle(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
- workspace.toggle_modal(cx, |_, cx| {
- let this = cx.add_view(|cx| Self::new(cx));
- cx.subscribe(&this, Self::on_event).detach();
- this
- });
- }
+pub type BaseKeymapSelector = Picker<BaseKeymapSelectorDelegate>;
+
+pub struct BaseKeymapSelectorDelegate {
+ matches: Vec<StringMatch>,
+ selected_index: usize,
+}
- fn new(cx: &mut ViewContext<Self>) -> Self {
+impl BaseKeymapSelectorDelegate {
+ fn new(cx: &mut ViewContext<BaseKeymapSelector>) -> Self {
let base = cx.global::<Settings>().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<BaseKeymapSelector>,
- event: &Event,
- cx: &mut ViewContext<Workspace>,
- ) {
- 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<str> {
+ "Select a base keymap...".into()
}
- fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::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 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<Self>) {
+ fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<BaseKeymapSelector>) {
self.selected_index = ix;
}
- fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
+ fn update_matches(
+ &mut self,
+ query: String,
+ cx: &mut ViewContext<BaseKeymapSelector>,
+ ) -> 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<Self>) {
+ fn confirm(&mut self, cx: &mut ViewContext<BaseKeymapSelector>) {
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<Self>) {
- cx.emit(Event::Dismissed)
- }
+ fn dismissed(&mut self, _cx: &mut ViewContext<BaseKeymapSelector>) {}
fn render_match(
&self,
@@ -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 {}",