diff --git a/Cargo.lock b/Cargo.lock index d501ed4b7e..0b5a802c52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5310,6 +5310,7 @@ dependencies = [ "gpui", "log", "parking_lot", + "picker", "postage", "settings", "smol", diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 18773aaf9c..ddefe4d830 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -18,7 +18,7 @@ pub fn init(cx: &mut MutableAppContext) { actions!(command_palette, [Toggle]); pub struct CommandPalette { - selector: ViewHandle>, + picker: ViewHandle>, actions: Vec, matches: Vec, selected_ix: usize, @@ -48,9 +48,9 @@ impl CommandPalette { .map_or(Vec::new(), |binding| binding.keystrokes().to_vec()), }) .collect(); - let selector = cx.add_view(|cx| Picker::new(this, cx)); + let picker = cx.add_view(|cx| Picker::new(this, cx)); Self { - selector, + picker, actions, matches: vec![], selected_ix: 0, @@ -98,11 +98,11 @@ impl View for CommandPalette { } fn render(&mut self, _: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox { - ChildView::new(self.selector.clone()).boxed() + ChildView::new(self.picker.clone()).boxed() } fn on_focus(&mut self, cx: &mut ViewContext) { - cx.focus(&self.selector); + cx.focus(&self.picker); } } @@ -115,7 +115,7 @@ impl PickerDelegate for CommandPalette { self.selected_ix } - fn set_selected_index(&mut self, ix: usize) { + fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext) { self.selected_ix = ix; } diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 797f681d42..913d182aa5 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -20,7 +20,7 @@ pub struct Picker { pub trait PickerDelegate: View { fn match_count(&self) -> usize; fn selected_index(&self) -> usize; - fn set_selected_index(&mut self, ix: 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); @@ -159,7 +159,7 @@ impl Picker { fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext) { if let Some(delegate) = self.delegate.upgrade(cx) { let index = 0; - delegate.update(cx, |delegate, _| delegate.set_selected_index(0)); + delegate.update(cx, |delegate, cx| delegate.set_selected_index(0, cx)); self.list_state.scroll_to(ScrollTarget::Show(index)); cx.notify(); } @@ -167,10 +167,10 @@ impl Picker { fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext) { if let Some(delegate) = self.delegate.upgrade(cx) { - let index = delegate.update(cx, |delegate, _| { + let index = delegate.update(cx, |delegate, cx| { let match_count = delegate.match_count(); let index = if match_count > 0 { match_count - 1 } else { 0 }; - delegate.set_selected_index(index); + delegate.set_selected_index(index, cx); index }); self.list_state.scroll_to(ScrollTarget::Show(index)); @@ -180,11 +180,11 @@ impl Picker { fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext) { if let Some(delegate) = self.delegate.upgrade(cx) { - let index = delegate.update(cx, |delegate, _| { + let index = delegate.update(cx, |delegate, cx| { let mut selected_index = delegate.selected_index(); if selected_index + 1 < delegate.match_count() { selected_index += 1; - delegate.set_selected_index(selected_index); + delegate.set_selected_index(selected_index, cx); } selected_index }); @@ -195,11 +195,11 @@ impl Picker { fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext) { if let Some(delegate) = self.delegate.upgrade(cx) { - let index = delegate.update(cx, |delegate, _| { + let index = 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); + delegate.set_selected_index(selected_index, cx); } selected_index }); diff --git a/crates/theme_selector/Cargo.toml b/crates/theme_selector/Cargo.toml index 9993287c85..804eff2c7a 100644 --- a/crates/theme_selector/Cargo.toml +++ b/crates/theme_selector/Cargo.toml @@ -11,6 +11,7 @@ doctest = false editor = { path = "../editor" } fuzzy = { path = "../fuzzy" } gpui = { path = "../gpui" } +picker = { path = "../picker" } theme = { path = "../theme" } settings = { path = "../settings" } workspace = { path = "../workspace" } diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 37099eb510..f1e933774f 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -1,35 +1,30 @@ -use editor::Editor; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use gpui::{ - actions, elements::*, keymap, AppContext, Axis, Element, ElementBox, Entity, MutableAppContext, + actions, elements::*, AppContext, Element, ElementBox, Entity, MutableAppContext, RenderContext, View, ViewContext, ViewHandle, }; +use picker::{Picker, PickerDelegate}; use settings::Settings; -use std::{cmp, sync::Arc}; +use std::sync::Arc; use theme::{Theme, ThemeRegistry}; -use workspace::{ - menu::{Confirm, SelectNext, SelectPrev}, - Workspace, -}; +use workspace::Workspace; pub struct ThemeSelector { - themes: Arc, + registry: Arc, + theme_names: Vec, matches: Vec, - query_editor: ViewHandle, - list_state: UniformListState, - selected_index: usize, original_theme: Arc, + picker: ViewHandle>, selection_completed: bool, + selected_index: usize, } actions!(theme_selector, [Toggle, Reload]); pub fn init(cx: &mut MutableAppContext) { - cx.add_action(ThemeSelector::confirm); - cx.add_action(ThemeSelector::select_prev); - cx.add_action(ThemeSelector::select_next); cx.add_action(ThemeSelector::toggle); cx.add_action(ThemeSelector::reload); + Picker::::init(cx); } pub enum Event { @@ -38,38 +33,38 @@ pub enum Event { impl ThemeSelector { fn new(registry: Arc, cx: &mut ViewContext) -> Self { - let query_editor = cx.add_view(|cx| { - Editor::single_line(Some(|theme| theme.selector.input_editor.clone()), cx) - }); - - cx.subscribe(&query_editor, Self::on_query_editor_event) - .detach(); - + let handle = cx.weak_handle(); + let picker = cx.add_view(|cx| Picker::new(handle, cx)); let original_theme = cx.global::().theme.clone(); - + let theme_names = registry.list().collect::>(); + let matches = theme_names + .iter() + .map(|name| StringMatch { + candidate_id: 0, + score: 0.0, + positions: Default::default(), + string: name.clone(), + }) + .collect(); let mut this = Self { - themes: registry, - query_editor, - matches: Vec::new(), - list_state: Default::default(), - selected_index: 0, // Default index for now + registry, + theme_names, + matches, + picker, original_theme: original_theme.clone(), + selected_index: 0, selection_completed: false, }; - this.update_matches(cx); - - // Set selected index to current theme this.select_if_matching(&original_theme.name); - this } fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { let themes = workspace.themes(); workspace.toggle_modal(cx, |cx, _| { - let selector = cx.add_view(|cx| Self::new(themes, cx)); - cx.subscribe(&selector, Self::on_event).detach(); - selector + let this = cx.add_view(|cx| Self::new(themes, cx)); + cx.subscribe(&this, Self::on_event).detach(); + this }); } @@ -88,36 +83,9 @@ impl ThemeSelector { } } - fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { - self.selection_completed = true; - cx.emit(Event::Dismissed); - } - - fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext) { - if self.selected_index > 0 { - self.selected_index -= 1; - } - self.list_state - .scroll_to(ScrollTarget::Show(self.selected_index)); - - self.show_selected_theme(cx); - cx.notify(); - } - - fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext) { - if self.selected_index + 1 < self.matches.len() { - self.selected_index += 1; - } - self.list_state - .scroll_to(ScrollTarget::Show(self.selected_index)); - - self.show_selected_theme(cx); - cx.notify(); - } - fn show_selected_theme(&mut self, cx: &mut ViewContext) { if let Some(mat) = self.matches.get(self.selected_index) { - match self.themes.get(&mat.string) { + match self.registry.get(&mat.string) { Ok(theme) => Self::set_theme(theme, cx), Err(error) => { log::error!("error loading theme {}: {}", mat.string, error) @@ -134,49 +102,6 @@ impl ThemeSelector { .unwrap_or(self.selected_index); } - fn update_matches(&mut self, cx: &mut ViewContext) { - let background = cx.background().clone(); - let candidates = self - .themes - .list() - .enumerate() - .map(|(id, name)| StringMatchCandidate { - id, - char_bag: name.as_str().into(), - string: name, - }) - .collect::>(); - let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx)); - - self.matches = if query.is_empty() { - candidates - .into_iter() - .enumerate() - .map(|(index, candidate)| StringMatch { - candidate_id: index, - string: candidate.string, - positions: Vec::new(), - score: 0.0, - }) - .collect() - } else { - smol::block_on(match_strings( - &candidates, - &query, - false, - 100, - &Default::default(), - background, - )) - }; - - self.selected_index = self - .selected_index - .min(self.matches.len().saturating_sub(1)); - - cx.notify(); - } - fn on_event( workspace: &mut Workspace, _: ViewHandle, @@ -190,84 +115,6 @@ impl ThemeSelector { } } - fn on_query_editor_event( - &mut self, - _: ViewHandle, - event: &editor::Event, - cx: &mut ViewContext, - ) { - match event { - editor::Event::BufferEdited { .. } => { - self.update_matches(cx); - self.select_if_matching(&cx.global::().theme.name); - self.show_selected_theme(cx); - } - editor::Event::Blurred => cx.emit(Event::Dismissed), - _ => {} - } - } - - fn render_matches(&self, cx: &mut RenderContext) -> ElementBox { - if self.matches.is_empty() { - let settings = cx.global::(); - return Container::new( - Label::new( - "No matches".into(), - settings.theme.selector.empty.label.clone(), - ) - .boxed(), - ) - .with_style(settings.theme.selector.empty.container) - .named("empty matches"); - } - - let handle = cx.handle(); - let list = - UniformList::new( - self.list_state.clone(), - self.matches.len(), - move |mut range, items, cx| { - let cx = cx.as_ref(); - let selector = handle.upgrade(cx).unwrap(); - let selector = selector.read(cx); - let start = range.start; - range.end = cmp::min(range.end, selector.matches.len()); - items.extend(selector.matches[range].iter().enumerate().map( - move |(i, path_match)| selector.render_match(path_match, start + i, cx), - )); - }, - ); - - Container::new(list.boxed()) - .with_margin_top(6.0) - .named("matches") - } - - fn render_match(&self, theme_match: &StringMatch, index: usize, cx: &AppContext) -> ElementBox { - let settings = cx.global::(); - let theme = &settings.theme; - - let container = Container::new( - Label::new( - theme_match.string.clone(), - if index == self.selected_index { - theme.selector.active_item.label.clone() - } else { - theme.selector.item.label.clone() - }, - ) - .with_highlights(theme_match.positions.clone()) - .boxed(), - ) - .with_style(if index == self.selected_index { - theme.selector.active_item.container - } else { - theme.selector.item.container - }); - - container.boxed() - } - fn set_theme(theme: Arc, cx: &mut MutableAppContext) { cx.update_global::(|settings, cx| { settings.theme = theme; @@ -276,6 +123,99 @@ impl ThemeSelector { } } +impl PickerDelegate for ThemeSelector { + fn match_count(&self) -> usize { + self.matches.len() + } + + fn confirm(&mut self, cx: &mut ViewContext) { + self.selection_completed = true; + cx.emit(Event::Dismissed); + } + + fn dismiss(&mut self, cx: &mut ViewContext) { + if !self.selection_completed { + Self::set_theme(self.original_theme.clone(), cx); + self.selection_completed = true; + } + cx.emit(Event::Dismissed); + } + + fn selected_index(&self) -> usize { + self.selected_index + } + + fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext) { + self.selected_index = ix; + self.show_selected_theme(cx); + } + + fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> gpui::Task<()> { + let background = cx.background().clone(); + let candidates = self + .theme_names + .iter() + .enumerate() + .map(|(id, name)| StringMatchCandidate { + id, + char_bag: name.as_str().into(), + string: name.clone(), + }) + .collect::>(); + + cx.spawn(|this, mut cx| async move { + let matches = if query.is_empty() { + candidates + .into_iter() + .enumerate() + .map(|(index, candidate)| StringMatch { + candidate_id: index, + string: candidate.string, + positions: Vec::new(), + score: 0.0, + }) + .collect() + } else { + match_strings( + &candidates, + &query, + false, + 100, + &Default::default(), + background, + ) + .await + }; + + this.update(&mut cx, |this, cx| { + this.matches = matches; + this.selected_index = this + .selected_index + .min(this.matches.len().saturating_sub(1)); + this.show_selected_theme(cx); + cx.notify(); + }); + }) + } + + fn render_match(&self, ix: usize, selected: bool, cx: &AppContext) -> ElementBox { + let settings = cx.global::(); + let theme = &settings.theme; + let theme_match = &self.matches[ix]; + let style = if selected { + &theme.selector.active_item + } else { + &theme.selector.item + }; + + Label::new(theme_match.string.clone(), style.label.clone()) + .with_highlights(theme_match.positions.clone()) + .contained() + .with_style(style.container) + .boxed() + } +} + impl Entity for ThemeSelector { type Event = Event; @@ -291,43 +231,11 @@ impl View for ThemeSelector { "ThemeSelector" } - fn render(&mut self, cx: &mut RenderContext) -> ElementBox { - let theme = cx.global::().theme.clone(); - Align::new( - ConstrainedBox::new( - Container::new( - Flex::new(Axis::Vertical) - .with_child( - ChildView::new(&self.query_editor) - .contained() - .with_style(theme.selector.input_editor.container) - .boxed(), - ) - .with_child( - FlexItem::new(self.render_matches(cx)) - .flex(1., false) - .boxed(), - ) - .boxed(), - ) - .with_style(theme.selector.container) - .boxed(), - ) - .with_max_width(600.0) - .with_max_height(400.0) - .boxed(), - ) - .top() - .named("theme selector") + fn render(&mut self, _: &mut RenderContext) -> ElementBox { + ChildView::new(self.picker.clone()).boxed() } fn on_focus(&mut self, cx: &mut ViewContext) { - cx.focus(&self.query_editor); - } - - fn keymap_context(&self, _: &AppContext) -> keymap::Context { - let mut cx = Self::default_keymap_context(); - cx.set.insert("menu".into()); - cx + cx.focus(&self.picker); } }