diff --git a/Cargo.lock b/Cargo.lock index 5e5c046ab9..d32a21d201 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4269,6 +4269,7 @@ dependencies = [ "log", "postage", "project", + "serde", "serde_json", "settings", "theme", @@ -5748,6 +5749,7 @@ dependencies = [ "language", "log", "project", + "serde", "settings", "util", "workspace", @@ -6133,6 +6135,7 @@ dependencies = [ "sha-1 0.9.6", "sqlx 0.5.5", "surf", + "theme", "tide", "tide-compress", "time 0.2.27", diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e10fe9c17c..4d3265f8fa 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -22,7 +22,7 @@ use gpui::{ executor, fonts::{self, HighlightStyle, TextStyle}, geometry::vector::{vec2f, Vector2F}, - impl_actions, + impl_actions, impl_internal_actions, keymap::Binding, platform::CursorStyle, text_layout, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity, @@ -66,8 +66,11 @@ const MAX_LINE_LEN: usize = 1024; const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10; const MAX_SELECTION_HISTORY_LEN: usize = 1024; -#[derive(Clone)] -pub struct SelectNext(pub bool); +#[derive(Clone, Deserialize)] +pub struct SelectNext { + #[serde(default)] + pub replace_newest: bool, +} #[derive(Clone)] pub struct GoToDiagnostic(pub Direction); @@ -81,44 +84,26 @@ pub struct Select(pub SelectPhase); #[derive(Clone)] pub struct Input(pub String); -#[derive(Clone)] -pub struct Tab(pub Direction); - -#[derive(Clone)] +#[derive(Clone, Deserialize)] pub struct SelectToBeginningOfLine { + #[serde(default)] stop_at_soft_wraps: bool, } -#[derive(Clone)] +#[derive(Clone, Deserialize)] pub struct SelectToEndOfLine { + #[serde(default)] stop_at_soft_wraps: bool, } -#[derive(Clone)] -pub struct ToggleCodeActions(pub bool); +#[derive(Clone, Deserialize)] +pub struct ToggleCodeActions(#[serde(default)] pub bool); -#[derive(Clone)] -pub struct ConfirmCompletion(pub Option); +#[derive(Clone, Deserialize)] +pub struct ConfirmCompletion(#[serde(default)] pub Option); -#[derive(Clone)] -pub struct ConfirmCodeAction(pub Option); - -impl_actions!( - editor, - [ - SelectNext, - GoToDiagnostic, - Scroll, - Select, - Input, - Tab, - SelectToBeginningOfLine, - SelectToEndOfLine, - ToggleCodeActions, - ConfirmCompletion, - ConfirmCodeAction, - ] -); +#[derive(Clone, Deserialize)] +pub struct ConfirmCodeAction(#[serde(default)] pub Option); actions!( editor, @@ -127,6 +112,8 @@ actions!( Backspace, Delete, Newline, + GoToNextDiagnostic, + GoToPrevDiagnostic, Indent, Outdent, DeleteLine, @@ -172,6 +159,8 @@ actions!( SplitSelectionIntoLines, AddSelectionAbove, AddSelectionBelow, + Tab, + TabPrev, ToggleComments, SelectLargerSyntaxNode, SelectSmallerSyntaxNode, @@ -193,6 +182,20 @@ actions!( ] ); +impl_actions!( + editor, + [ + SelectNext, + SelectToBeginningOfLine, + SelectToEndOfLine, + ToggleCodeActions, + ConfirmCompletion, + ConfirmCodeAction, + ] +); + +impl_internal_actions!(editor, [Scroll, Select, Input]); + enum DocumentHighlightRead {} enum DocumentHighlightWrite {} @@ -226,8 +229,8 @@ pub fn init(cx: &mut MutableAppContext) { Some("Editor && showing_code_actions"), ), Binding::new("enter", ConfirmRename, Some("Editor && renaming")), - Binding::new("tab", Tab(Direction::Next), Some("Editor")), - Binding::new("shift-tab", Tab(Direction::Prev), Some("Editor")), + Binding::new("tab", Tab, Some("Editor")), + Binding::new("shift-tab", TabPrev, Some("Editor")), Binding::new( "tab", ConfirmCompletion(None), @@ -346,8 +349,20 @@ pub fn init(cx: &mut MutableAppContext) { Binding::new("cmd-ctrl-p", AddSelectionAbove, Some("Editor")), Binding::new("cmd-alt-down", AddSelectionBelow, Some("Editor")), Binding::new("cmd-ctrl-n", AddSelectionBelow, Some("Editor")), - Binding::new("cmd-d", SelectNext(false), Some("Editor")), - Binding::new("cmd-k cmd-d", SelectNext(true), Some("Editor")), + Binding::new( + "cmd-d", + SelectNext { + replace_newest: false, + }, + Some("Editor"), + ), + Binding::new( + "cmd-k cmd-d", + SelectNext { + replace_newest: true, + }, + Some("Editor"), + ), Binding::new("cmd-/", ToggleComments, Some("Editor")), Binding::new("alt-up", SelectLargerSyntaxNode, Some("Editor")), Binding::new("ctrl-w", SelectLargerSyntaxNode, Some("Editor")), @@ -355,8 +370,8 @@ pub fn init(cx: &mut MutableAppContext) { Binding::new("ctrl-shift-W", SelectSmallerSyntaxNode, Some("Editor")), Binding::new("cmd-u", UndoSelection, Some("Editor")), Binding::new("cmd-shift-U", RedoSelection, Some("Editor")), - Binding::new("f8", GoToDiagnostic(Direction::Next), Some("Editor")), - Binding::new("shift-f8", GoToDiagnostic(Direction::Prev), Some("Editor")), + Binding::new("f8", GoToNextDiagnostic, Some("Editor")), + Binding::new("shift-f8", GoToPrevDiagnostic, Some("Editor")), Binding::new("f2", Rename, Some("Editor")), Binding::new("f12", GoToDefinition, Some("Editor")), Binding::new("alt-shift-f12", FindAllReferences, Some("Editor")), @@ -435,7 +450,8 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Editor::move_to_enclosing_bracket); cx.add_action(Editor::undo_selection); cx.add_action(Editor::redo_selection); - cx.add_action(Editor::go_to_diagnostic); + cx.add_action(Editor::go_to_next_diagnostic); + cx.add_action(Editor::go_to_prev_diagnostic); cx.add_action(Editor::go_to_definition); cx.add_action(Editor::page_up); cx.add_action(Editor::page_down); @@ -2940,8 +2956,8 @@ impl Editor { self.move_to_snippet_tabstop(Bias::Right, cx) } - pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext) { - self.move_to_snippet_tabstop(Bias::Left, cx); + pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext) -> bool { + self.move_to_snippet_tabstop(Bias::Left, cx) } pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext) -> bool { @@ -3046,54 +3062,46 @@ impl Editor { }); } - pub fn tab(&mut self, &Tab(direction): &Tab, cx: &mut ViewContext) { - match direction { - Direction::Prev => { - if !self.snippet_stack.is_empty() { - self.move_to_prev_snippet_tabstop(cx); - return; - } + pub fn tab_prev(&mut self, _: &TabPrev, cx: &mut ViewContext) { + if self.move_to_prev_snippet_tabstop(cx) { + return; + } - self.outdent(&Outdent, cx); - } - Direction::Next => { - if self.move_to_next_snippet_tabstop(cx) { - return; - } + self.outdent(&Outdent, cx); + } - let mut selections = self.local_selections::(cx); - if selections.iter().all(|s| s.is_empty()) { - self.transact(cx, |this, cx| { - this.buffer.update(cx, |buffer, cx| { - for selection in &mut selections { - let language_name = - buffer.language_at(selection.start, cx).map(|l| l.name()); - let tab_size = - cx.global::().tab_size(language_name.as_deref()); - let char_column = buffer - .read(cx) - .text_for_range( - Point::new(selection.start.row, 0)..selection.start, - ) - .flat_map(str::chars) - .count(); - let chars_to_next_tab_stop = - tab_size - (char_column as u32 % tab_size); - buffer.edit( - [selection.start..selection.start], - " ".repeat(chars_to_next_tab_stop as usize), - cx, - ); - selection.start.column += chars_to_next_tab_stop; - selection.end = selection.start; - } - }); - this.update_selections(selections, Some(Autoscroll::Fit), cx); - }); - } else { - self.indent(&Indent, cx); - } - } + pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext) { + if self.move_to_next_snippet_tabstop(cx) { + return; + } + + let mut selections = self.local_selections::(cx); + if selections.iter().all(|s| s.is_empty()) { + self.transact(cx, |this, cx| { + this.buffer.update(cx, |buffer, cx| { + for selection in &mut selections { + let language_name = + buffer.language_at(selection.start, cx).map(|l| l.name()); + let tab_size = cx.global::().tab_size(language_name.as_deref()); + let char_column = buffer + .read(cx) + .text_for_range(Point::new(selection.start.row, 0)..selection.start) + .flat_map(str::chars) + .count(); + let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size); + buffer.edit( + [selection.start..selection.start], + " ".repeat(chars_to_next_tab_stop as usize), + cx, + ); + selection.start.column += chars_to_next_tab_stop; + selection.end = selection.start; + } + }); + this.update_selections(selections, Some(Autoscroll::Fit), cx); + }); + } else { + self.indent(&Indent, cx); } } @@ -4237,7 +4245,6 @@ impl Editor { pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext) { self.push_to_selection_history(); - let replace_newest = action.0; let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = &display_map.buffer_snapshot; let mut selections = self.local_selections::(cx); @@ -4276,7 +4283,7 @@ impl Editor { } if let Some(next_selected_range) = next_selected_range { - if replace_newest { + if action.replace_newest { if let Some(newest_id) = selections.iter().max_by_key(|s| s.id).map(|s| s.id) { @@ -4547,11 +4554,15 @@ impl Editor { self.selection_history.mode = SelectionHistoryMode::Normal; } - pub fn go_to_diagnostic( - &mut self, - &GoToDiagnostic(direction): &GoToDiagnostic, - cx: &mut ViewContext, - ) { + fn go_to_next_diagnostic(&mut self, _: &GoToNextDiagnostic, cx: &mut ViewContext) { + self.go_to_diagnostic(Direction::Next, cx) + } + + fn go_to_prev_diagnostic(&mut self, _: &GoToNextDiagnostic, cx: &mut ViewContext) { + self.go_to_diagnostic(Direction::Prev, cx) + } + + pub fn go_to_diagnostic(&mut self, direction: Direction, cx: &mut ViewContext) { let buffer = self.buffer.read(cx).snapshot(cx); let selection = self.newest_selection_with_snapshot::(&buffer); let mut active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| { @@ -7771,7 +7782,7 @@ mod tests { ); // indent from mid-tabstop to full tabstop - view.tab(&Tab(Direction::Next), cx); + view.tab(&Tab, cx); assert_text_with_selections( view, indoc! {" @@ -7782,7 +7793,7 @@ mod tests { ); // outdent from 1 tabstop to 0 tabstops - view.tab(&Tab(Direction::Prev), cx); + view.tab_prev(&TabPrev, cx); assert_text_with_selections( view, indoc! {" @@ -7803,7 +7814,7 @@ mod tests { ); // indent and outdent affect only the preceding line - view.tab(&Tab(Direction::Next), cx); + view.tab(&Tab, cx); assert_text_with_selections( view, indoc! {" @@ -7812,7 +7823,7 @@ mod tests { ] four"}, cx, ); - view.tab(&Tab(Direction::Prev), cx); + view.tab_prev(&TabPrev, cx); assert_text_with_selections( view, indoc! {" @@ -7831,7 +7842,7 @@ mod tests { four"}, cx, ); - view.tab(&Tab(Direction::Next), cx); + view.tab(&Tab, cx); assert_text_with_selections( view, indoc! {" @@ -7849,7 +7860,7 @@ mod tests { four"}, cx, ); - view.tab(&Tab(Direction::Prev), cx); + view.tab_prev(&TabPrev, cx); assert_text_with_selections( view, indoc! {" @@ -7939,7 +7950,7 @@ mod tests { cx, ); - editor.tab(&Tab(Direction::Next), cx); + editor.tab(&Tab, cx); assert_text_with_selections( &mut editor, indoc! {" @@ -7950,7 +7961,7 @@ mod tests { "}, cx, ); - editor.tab(&Tab(Direction::Prev), cx); + editor.tab_prev(&TabPrev, cx); assert_text_with_selections( &mut editor, indoc! {" @@ -8693,10 +8704,20 @@ mod tests { view.update(cx, |view, cx| { view.select_ranges([ranges[1].start + 1..ranges[1].start + 1], None, cx); - view.select_next(&SelectNext(false), cx); + view.select_next( + &SelectNext { + replace_newest: false, + }, + cx, + ); assert_eq!(view.selected_ranges(cx), &ranges[1..2]); - view.select_next(&SelectNext(false), cx); + view.select_next( + &SelectNext { + replace_newest: false, + }, + cx, + ); assert_eq!(view.selected_ranges(cx), &ranges[1..3]); view.undo_selection(&UndoSelection, cx); @@ -8705,10 +8726,20 @@ mod tests { view.redo_selection(&RedoSelection, cx); assert_eq!(view.selected_ranges(cx), &ranges[1..3]); - view.select_next(&SelectNext(false), cx); + view.select_next( + &SelectNext { + replace_newest: false, + }, + cx, + ); assert_eq!(view.selected_ranges(cx), &ranges[1..4]); - view.select_next(&SelectNext(false), cx); + view.select_next( + &SelectNext { + replace_newest: false, + }, + cx, + ); assert_eq!(view.selected_ranges(cx), &ranges[0..4]); }); } diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index af9fd910b5..659dde7c5b 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -3,7 +3,7 @@ use fuzzy::PathMatch; use gpui::{ actions, elements::*, - impl_actions, + impl_internal_actions, keymap::{self, Binding}, AppContext, Axis, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -41,8 +41,8 @@ pub struct FileFinder { #[derive(Clone)] pub struct Select(pub ProjectPath); -impl_actions!(file_finder, [Select]); actions!(file_finder, [Toggle]); +impl_internal_actions!(file_finder, [Select]); pub fn init(cx: &mut MutableAppContext) { cx.add_action(FileFinder::toggle); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 3fc5652791..f7417f6663 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -715,12 +715,14 @@ type GlobalSubscriptionCallback = Box bool>; type GlobalObservationCallback = Box; type ReleaseObservationCallback = Box; +type DeserializeActionCallback = fn(json: &str) -> anyhow::Result>; pub struct MutableAppContext { weak_self: Option>>, foreground_platform: Rc, assets: Arc, cx: AppContext, + action_deserializers: HashMap<&'static str, DeserializeActionCallback>, capture_actions: HashMap>>>, actions: HashMap>>>, global_actions: HashMap>, @@ -773,6 +775,7 @@ impl MutableAppContext { font_cache, platform, }, + action_deserializers: HashMap::new(), capture_actions: HashMap::new(), actions: HashMap::new(), global_actions: HashMap::new(), @@ -857,6 +860,18 @@ impl MutableAppContext { .and_then(|(presenter, _)| presenter.borrow().debug_elements(self)) } + pub fn deserialize_action( + &self, + name: &str, + argument: Option<&str>, + ) -> Result> { + let callback = self + .action_deserializers + .get(name) + .ok_or_else(|| anyhow!("unknown action {}", name))?; + callback(argument.unwrap_or("{}")) + } + pub fn add_action(&mut self, handler: F) where A: Action, @@ -899,6 +914,10 @@ impl MutableAppContext { }, ); + self.action_deserializers + .entry(A::qualified_name()) + .or_insert(A::from_json_str); + let actions = if capture { &mut self.capture_actions } else { @@ -934,6 +953,10 @@ impl MutableAppContext { handler(action, cx); }); + self.action_deserializers + .entry(A::qualified_name()) + .or_insert(A::from_json_str); + if self .global_actions .insert(TypeId::of::(), handler) @@ -4575,7 +4598,8 @@ impl RefCounts { #[cfg(test)] mod tests { use super::*; - use crate::{elements::*, impl_actions}; + use crate::{actions, elements::*, impl_actions}; + use serde::Deserialize; use smol::future::poll_once; use std::{ cell::Cell, @@ -5683,6 +5707,42 @@ mod tests { ); } + #[crate::test(self)] + fn test_deserialize_actions(cx: &mut MutableAppContext) { + #[derive(Clone, Debug, Deserialize, PartialEq, Eq)] + pub struct ComplexAction { + arg: String, + count: usize, + } + + actions!(test::something, [SimpleAction]); + impl_actions!(test::something, [ComplexAction]); + + cx.add_global_action(move |_: &SimpleAction, _: &mut MutableAppContext| {}); + cx.add_global_action(move |_: &ComplexAction, _: &mut MutableAppContext| {}); + + let action1 = cx + .deserialize_action( + "test::something::ComplexAction", + Some(r#"{"arg": "a", "count": 5}"#), + ) + .unwrap(); + let action2 = cx + .deserialize_action("test::something::SimpleAction", None) + .unwrap(); + assert_eq!( + action1.as_any().downcast_ref::().unwrap(), + &ComplexAction { + arg: "a".to_string(), + count: 5, + } + ); + assert_eq!( + action2.as_any().downcast_ref::().unwrap(), + &SimpleAction + ); + } + #[crate::test(self)] fn test_dispatch_action(cx: &mut MutableAppContext) { struct ViewA { @@ -5721,32 +5781,32 @@ mod tests { } } - #[derive(Clone)] - pub struct Action(pub &'static str); + #[derive(Clone, Deserialize)] + pub struct Action(pub String); impl_actions!(test, [Action]); let actions = Rc::new(RefCell::new(Vec::new())); - { + cx.add_global_action({ let actions = actions.clone(); - cx.add_global_action(move |_: &Action, _: &mut MutableAppContext| { + move |_: &Action, _: &mut MutableAppContext| { actions.borrow_mut().push("global".to_string()); - }); - } + } + }); - { + cx.add_action({ let actions = actions.clone(); - cx.add_action(move |view: &mut ViewA, action: &Action, cx| { + move |view: &mut ViewA, action: &Action, cx| { assert_eq!(action.0, "bar"); cx.propagate_action(); actions.borrow_mut().push(format!("{} a", view.id)); - }); - } + } + }); - { + cx.add_action({ let actions = actions.clone(); - cx.add_action(move |view: &mut ViewA, _: &Action, cx| { + move |view: &mut ViewA, _: &Action, cx| { if view.id != 1 { cx.add_view(|cx| { cx.propagate_action(); // Still works on a nested ViewContext @@ -5754,32 +5814,32 @@ mod tests { }); } actions.borrow_mut().push(format!("{} b", view.id)); - }); - } + } + }); - { + cx.add_action({ let actions = actions.clone(); - cx.add_action(move |view: &mut ViewB, _: &Action, cx| { + move |view: &mut ViewB, _: &Action, cx| { cx.propagate_action(); actions.borrow_mut().push(format!("{} c", view.id)); - }); - } + } + }); - { + cx.add_action({ let actions = actions.clone(); - cx.add_action(move |view: &mut ViewB, _: &Action, cx| { + move |view: &mut ViewB, _: &Action, cx| { cx.propagate_action(); actions.borrow_mut().push(format!("{} d", view.id)); - }); - } + } + }); - { + cx.capture_action({ let actions = actions.clone(); - cx.capture_action(move |view: &mut ViewA, _: &Action, cx| { + move |view: &mut ViewA, _: &Action, cx| { cx.propagate_action(); actions.borrow_mut().push(format!("{} capture", view.id)); - }); - } + } + }); let (window_id, view_1) = cx.add_window(Default::default(), |_| ViewA { id: 1 }); let view_2 = cx.add_view(window_id, |_| ViewB { id: 2 }); @@ -5789,7 +5849,7 @@ mod tests { cx.dispatch_action( window_id, vec![view_1.id(), view_2.id(), view_3.id(), view_4.id()], - &Action("bar"), + &Action("bar".to_string()), ); assert_eq!( @@ -5812,7 +5872,7 @@ mod tests { cx.dispatch_action( window_id, vec![view_2.id(), view_3.id(), view_4.id()], - &Action("bar"), + &Action("bar".to_string()), ); assert_eq!( @@ -5832,8 +5892,8 @@ mod tests { #[crate::test(self)] fn test_dispatch_keystroke(cx: &mut MutableAppContext) { - #[derive(Clone)] - pub struct Action(pub &'static str); + #[derive(Clone, Deserialize)] + pub struct Action(String); impl_actions!(test, [Action]); @@ -5887,16 +5947,20 @@ mod tests { // "a" and "b" in its context, but not "c". cx.add_bindings(vec![keymap::Binding::new( "a", - Action("a"), + Action("a".to_string()), Some("a && b && !c"), )]); - cx.add_bindings(vec![keymap::Binding::new("b", Action("b"), None)]); + cx.add_bindings(vec![keymap::Binding::new( + "b", + Action("b".to_string()), + None, + )]); let actions = Rc::new(RefCell::new(Vec::new())); - { + cx.add_action({ let actions = actions.clone(); - cx.add_action(move |view: &mut View, action: &Action, cx| { + move |view: &mut View, action: &Action, cx| { if action.0 == "a" { actions.borrow_mut().push(format!("{} a", view.id)); } else { @@ -5905,14 +5969,15 @@ mod tests { .push(format!("{} {}", view.id, action.0)); cx.propagate_action(); } - }); - } - { + } + }); + + cx.add_global_action({ let actions = actions.clone(); - cx.add_global_action(move |action: &Action, _| { + move |action: &Action, _| { actions.borrow_mut().push(format!("global {}", action.0)); - }); - } + } + }); cx.dispatch_keystroke( window_id, diff --git a/crates/gpui/src/app/action.rs b/crates/gpui/src/app/action.rs index 79606feee7..fc5bd616ee 100644 --- a/crates/gpui/src/app/action.rs +++ b/crates/gpui/src/app/action.rs @@ -2,55 +2,108 @@ use std::any::{Any, TypeId}; pub trait Action: 'static { fn id(&self) -> TypeId; - fn namespace(&self) -> &'static str; fn name(&self) -> &'static str; fn as_any(&self) -> &dyn Any; fn boxed_clone(&self) -> Box; - fn boxed_clone_as_any(&self) -> Box; + + fn qualified_name() -> &'static str + where + Self: Sized; + fn from_json_str(json: &str) -> anyhow::Result> + where + Self: Sized; } +/// Define a set of unit struct types that all implement the `Action` trait. +/// +/// The first argument is a namespace that will be associated with each of +/// the given action types, to ensure that they have globally unique +/// qualified names for use in keymap files. #[macro_export] -macro_rules! impl_actions { +macro_rules! actions { ($namespace:path, [ $($name:ident),* $(,)? ]) => { $( - impl $crate::action::Action for $name { - fn id(&self) -> std::any::TypeId { - std::any::TypeId::of::<$name>() - } - - fn namespace(&self) -> &'static str { - stringify!($namespace) - } - - fn name(&self) -> &'static str { - stringify!($name) - } - - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn boxed_clone(&self) -> Box { - Box::new(self.clone()) - } - - fn boxed_clone_as_any(&self) -> Box { - Box::new(self.clone()) + #[derive(Clone, Debug, Default, PartialEq, Eq)] + pub struct $name; + $crate::__impl_action! { + $namespace, + $name, + fn from_json_str(_: &str) -> $crate::anyhow::Result> { + Ok(Box::new(Self)) } } )* }; } +/// Implement the `Action` trait for a set of existing types. +/// +/// The first argument is a namespace that will be associated with each of +/// the given action types, to ensure that they have globally unique +/// qualified names for use in keymap files. #[macro_export] -macro_rules! actions { +macro_rules! impl_actions { ($namespace:path, [ $($name:ident),* $(,)? ]) => { - $( - #[derive(Clone, Debug, Default, PartialEq, Eq)] - pub struct $name; + $crate::__impl_action! { + $namespace, + $name, + fn from_json_str(json: &str) -> $crate::anyhow::Result> { + Ok(Box::new($crate::serde_json::from_str::(json)?)) + } + } )* - - $crate::impl_actions!($namespace, [ $($name),* ]); + }; +} + +/// Implement the `Action` trait for a set of existing types that are +/// not intended to be constructed via a keymap file, but only dispatched +/// internally. +#[macro_export] +macro_rules! impl_internal_actions { + ($namespace:path, [ $($name:ident),* $(,)? ]) => { + $( + $crate::__impl_action! { + $namespace, + $name, + fn from_json_str(_: &str) -> $crate::anyhow::Result> { + Err($crate::anyhow::anyhow!("internal action")) + } + } + )* + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __impl_action { + ($namespace:path, $name:ident, $from_json_fn:item) => { + impl $crate::action::Action for $name { + fn name(&self) -> &'static str { + stringify!($name) + } + + fn qualified_name() -> &'static str { + concat!( + stringify!($namespace), + "::", + stringify!($name), + ) + } + + fn id(&self) -> std::any::TypeId { + std::any::TypeId::of::<$name>() + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn boxed_clone(&self) -> Box { + Box::new(self.clone()) + } + + $from_json_fn + } }; } diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index 9803a2aa2f..e58bbec1c6 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -33,3 +33,6 @@ pub use platform::{Event, NavigationDirection, PathPromptOptions, Platform, Prom pub use presenter::{ Axis, DebugContext, EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt, }; + +pub use anyhow; +pub use serde_json; diff --git a/crates/gpui/src/keymap.rs b/crates/gpui/src/keymap.rs index f286265964..a94abb8252 100644 --- a/crates/gpui/src/keymap.rs +++ b/crates/gpui/src/keymap.rs @@ -328,6 +328,8 @@ impl ContextPredicate { #[cfg(test)] mod tests { + use serde::Deserialize; + use crate::{actions, impl_actions}; use super::*; @@ -419,30 +421,18 @@ mod tests { #[test] fn test_matcher() -> anyhow::Result<()> { - #[derive(Clone)] - pub struct A(pub &'static str); + #[derive(Clone, Deserialize, PartialEq, Eq, Debug)] + pub struct A(pub String); impl_actions!(test, [A]); actions!(test, [B, Ab]); - impl PartialEq for A { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } - } - impl Eq for A {} - impl Debug for A { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "A({:?})", &self.0) - } - } - #[derive(Clone, Debug, Eq, PartialEq)] struct ActionArg { a: &'static str, } let keymap = Keymap(vec![ - Binding::new("a", A("x"), Some("a")), + Binding::new("a", A("x".to_string()), Some("a")), Binding::new("b", B, Some("a")), Binding::new("a b", Ab, Some("a || b")), ]); @@ -456,40 +446,54 @@ mod tests { let mut matcher = Matcher::new(keymap); // Basic match - assert_eq!(matcher.test_keystroke("a", 1, &ctx_a), Some(A("x"))); + assert_eq!( + downcast(&matcher.test_keystroke("a", 1, &ctx_a)), + Some(&A("x".to_string())) + ); // Multi-keystroke match - assert_eq!(matcher.test_keystroke::("a", 1, &ctx_b), None); - assert_eq!(matcher.test_keystroke("b", 1, &ctx_b), Some(Ab)); + assert!(matcher.test_keystroke("a", 1, &ctx_b).is_none()); + assert_eq!(downcast(&matcher.test_keystroke("b", 1, &ctx_b)), Some(&Ab)); // Failed matches don't interfere with matching subsequent keys - assert_eq!(matcher.test_keystroke::("x", 1, &ctx_a), None); - assert_eq!(matcher.test_keystroke("a", 1, &ctx_a), Some(A("x"))); + assert!(matcher.test_keystroke("x", 1, &ctx_a).is_none()); + assert_eq!( + downcast(&matcher.test_keystroke("a", 1, &ctx_a)), + Some(&A("x".to_string())) + ); // Pending keystrokes are cleared when the context changes - assert_eq!(matcher.test_keystroke::("a", 1, &ctx_b), None); - assert_eq!(matcher.test_keystroke("b", 1, &ctx_a), Some(B)); + assert!(&matcher.test_keystroke("a", 1, &ctx_b).is_none()); + assert_eq!(downcast(&matcher.test_keystroke("b", 1, &ctx_a)), Some(&B)); let mut ctx_c = Context::default(); ctx_c.set.insert("c".into()); // Pending keystrokes are maintained per-view - assert_eq!(matcher.test_keystroke::("a", 1, &ctx_b), None); - assert_eq!(matcher.test_keystroke::("a", 2, &ctx_c), None); - assert_eq!(matcher.test_keystroke("b", 1, &ctx_b), Some(Ab)); + assert!(matcher.test_keystroke("a", 1, &ctx_b).is_none()); + assert!(matcher.test_keystroke("a", 2, &ctx_c).is_none()); + assert_eq!(downcast(&matcher.test_keystroke("b", 1, &ctx_b)), Some(&Ab)); Ok(()) } + fn downcast<'a, A: Action>(action: &'a Option>) -> Option<&'a A> { + action + .as_ref() + .and_then(|action| action.as_any().downcast_ref()) + } + impl Matcher { - fn test_keystroke(&mut self, keystroke: &str, view_id: usize, cx: &Context) -> Option - where - A: Action + Debug + Eq, - { + fn test_keystroke( + &mut self, + keystroke: &str, + view_id: usize, + cx: &Context, + ) -> Option> { if let MatchResult::Action(action) = self.push_keystroke(Keystroke::parse(keystroke).unwrap(), view_id, cx) { - Some(*action.boxed_clone_as_any().downcast().unwrap()) + Some(action.boxed_clone()) } else { None } diff --git a/crates/gpui/src/views/select.rs b/crates/gpui/src/views/select.rs index 14b098c472..10cd0cd5a2 100644 --- a/crates/gpui/src/views/select.rs +++ b/crates/gpui/src/views/select.rs @@ -1,3 +1,5 @@ +use serde::Deserialize; + use crate::{ actions, elements::*, impl_actions, AppContext, Entity, MutableAppContext, RenderContext, View, ViewContext, WeakViewHandle, @@ -25,7 +27,7 @@ pub enum ItemType { Unselected, } -#[derive(Clone)] +#[derive(Clone, Deserialize)] pub struct SelectItem(pub usize); actions!(select, [ToggleSelect]); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 3534fa186f..a14e03d27d 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -4,7 +4,7 @@ use gpui::{ Align, ConstrainedBox, Empty, Flex, Label, MouseEventHandler, ParentElement, ScrollTarget, Svg, UniformList, UniformListState, }, - impl_actions, + impl_internal_actions, keymap::{self, Binding}, platform::CursorStyle, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, View, ViewContext, @@ -54,7 +54,7 @@ pub struct ToggleExpanded(pub ProjectEntryId); pub struct Open(pub ProjectEntryId); actions!(project_panel, [ExpandSelectedEntry, CollapseSelectedEntry]); -impl_actions!(project_panel, [Open, ToggleExpanded]); +impl_internal_actions!(project_panel, [Open, ToggleExpanded]); pub fn init(cx: &mut MutableAppContext) { cx.add_action(ProjectPanel::expand_selected_entry); diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 77961de01f..967b50c270 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -20,6 +20,7 @@ workspace = { path = "../workspace" } anyhow = "1.0" log = "0.4" postage = { version = "0.4.1", features = ["futures-traits"] } +serde = { version = "1", features = ["derive"] } [dev-dependencies] editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index ffaf8e368d..71e9a90970 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1,25 +1,32 @@ -use crate::{active_match_index, match_index_for_direction, Direction, SearchOption, SelectMatch}; +use crate::{ + active_match_index, match_index_for_direction, Direction, SearchOption, SelectNextMatch, + SelectPrevMatch, +}; use collections::HashMap; use editor::{display_map::ToDisplayPoint, Anchor, Autoscroll, Bias, Editor}; use gpui::{ - actions, elements::*, impl_actions, keymap::Binding, platform::CursorStyle, AppContext, Entity, - MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, - WeakViewHandle, + actions, elements::*, impl_actions, impl_internal_actions, keymap::Binding, + platform::CursorStyle, AppContext, Entity, MutableAppContext, RenderContext, Subscription, + Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use language::OffsetRangeExt; use project::search::SearchQuery; +use serde::Deserialize; use settings::Settings; use std::ops::Range; use workspace::{ItemHandle, Pane, ToolbarItemLocation, ToolbarItemView}; -#[derive(Clone)] -pub struct Deploy(pub bool); +#[derive(Clone, Deserialize)] +pub struct Deploy { + pub focus: bool, +} #[derive(Clone)] pub struct ToggleSearchOption(pub SearchOption); actions!(buffer_search, [Dismiss, FocusEditor]); -impl_actions!(buffer_search, [Deploy, ToggleSearchOption]); +impl_actions!(buffer_search, [Deploy]); +impl_internal_actions!(buffer_search, [ToggleSearchOption]); pub enum Event { UpdateLocation, @@ -27,29 +34,31 @@ pub enum Event { pub fn init(cx: &mut MutableAppContext) { cx.add_bindings([ - Binding::new("cmd-f", Deploy(true), Some("Editor && mode == full")), - Binding::new("cmd-e", Deploy(false), Some("Editor && mode == full")), + Binding::new( + "cmd-f", + Deploy { focus: true }, + Some("Editor && mode == full"), + ), + Binding::new( + "cmd-e", + Deploy { focus: false }, + Some("Editor && mode == full"), + ), Binding::new("escape", Dismiss, Some("BufferSearchBar")), Binding::new("cmd-f", FocusEditor, Some("BufferSearchBar")), - Binding::new( - "enter", - SelectMatch(Direction::Next), - Some("BufferSearchBar"), - ), - Binding::new( - "shift-enter", - SelectMatch(Direction::Prev), - Some("BufferSearchBar"), - ), - Binding::new("cmd-g", SelectMatch(Direction::Next), Some("Pane")), - Binding::new("cmd-shift-G", SelectMatch(Direction::Prev), Some("Pane")), + Binding::new("enter", SelectNextMatch, Some("BufferSearchBar")), + Binding::new("shift-enter", SelectPrevMatch, Some("BufferSearchBar")), + Binding::new("cmd-g", SelectNextMatch, Some("Pane")), + Binding::new("cmd-shift-G", SelectPrevMatch, Some("Pane")), ]); cx.add_action(BufferSearchBar::deploy); cx.add_action(BufferSearchBar::dismiss); cx.add_action(BufferSearchBar::focus_editor); cx.add_action(BufferSearchBar::toggle_search_option); - cx.add_action(BufferSearchBar::select_match); - cx.add_action(BufferSearchBar::select_match_on_pane); + cx.add_action(BufferSearchBar::select_next_match); + cx.add_action(BufferSearchBar::select_prev_match); + cx.add_action(BufferSearchBar::select_next_match_on_pane); + cx.add_action(BufferSearchBar::select_prev_match_on_pane); } pub struct BufferSearchBar { @@ -325,14 +334,17 @@ impl BufferSearchBar { .with_style(style.container) .boxed() }) - .on_click(move |cx| cx.dispatch_action(SelectMatch(direction))) + .on_click(move |cx| match direction { + Direction::Prev => cx.dispatch_action(SelectPrevMatch), + Direction::Next => cx.dispatch_action(SelectNextMatch), + }) .with_cursor_style(CursorStyle::PointingHand) .boxed() } - fn deploy(pane: &mut Pane, Deploy(focus): &Deploy, cx: &mut ViewContext) { + fn deploy(pane: &mut Pane, action: &Deploy, cx: &mut ViewContext) { if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { - if search_bar.update(cx, |search_bar, cx| search_bar.show(*focus, cx)) { + if search_bar.update(cx, |search_bar, cx| search_bar.show(action.focus, cx)) { return; } } @@ -368,7 +380,15 @@ impl BufferSearchBar { cx.notify(); } - fn select_match(&mut self, &SelectMatch(direction): &SelectMatch, cx: &mut ViewContext) { + fn select_next_match(&mut self, _: &SelectNextMatch, cx: &mut ViewContext) { + self.select_match(Direction::Next, cx); + } + + fn select_prev_match(&mut self, _: &SelectPrevMatch, cx: &mut ViewContext) { + self.select_match(Direction::Prev, cx); + } + + fn select_match(&mut self, direction: Direction, cx: &mut ViewContext) { if let Some(index) = self.active_match_index { if let Some(editor) = self.active_editor.as_ref() { editor.update(cx, |editor, cx| { @@ -389,9 +409,23 @@ impl BufferSearchBar { } } - fn select_match_on_pane(pane: &mut Pane, action: &SelectMatch, cx: &mut ViewContext) { + fn select_next_match_on_pane( + pane: &mut Pane, + action: &SelectNextMatch, + cx: &mut ViewContext, + ) { if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { - search_bar.update(cx, |search_bar, cx| search_bar.select_match(action, cx)); + search_bar.update(cx, |bar, cx| bar.select_next_match(action, cx)); + } + } + + fn select_prev_match_on_pane( + pane: &mut Pane, + action: &SelectPrevMatch, + cx: &mut ViewContext, + ) { + if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { + search_bar.update(cx, |bar, cx| bar.select_prev_match(action, cx)); } } @@ -699,7 +733,7 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { assert_eq!(search_bar.active_match_index, Some(0)); - search_bar.select_match(&SelectMatch(Direction::Next), cx); + search_bar.select_next_match(&SelectNextMatch, cx); assert_eq!( editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)] @@ -710,7 +744,7 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { - search_bar.select_match(&SelectMatch(Direction::Next), cx); + search_bar.select_next_match(&SelectNextMatch, cx); assert_eq!( editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)] @@ -721,7 +755,7 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { - search_bar.select_match(&SelectMatch(Direction::Next), cx); + search_bar.select_next_match(&SelectNextMatch, cx); assert_eq!( editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)] @@ -732,7 +766,7 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { - search_bar.select_match(&SelectMatch(Direction::Next), cx); + search_bar.select_next_match(&SelectNextMatch, cx); assert_eq!( editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)] @@ -743,7 +777,7 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { - search_bar.select_match(&SelectMatch(Direction::Prev), cx); + search_bar.select_prev_match(&SelectPrevMatch, cx); assert_eq!( editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)] @@ -754,7 +788,7 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { - search_bar.select_match(&SelectMatch(Direction::Prev), cx); + search_bar.select_prev_match(&SelectPrevMatch, cx); assert_eq!( editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)] @@ -765,7 +799,7 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { - search_bar.select_match(&SelectMatch(Direction::Prev), cx); + search_bar.select_prev_match(&SelectPrevMatch, cx); assert_eq!( editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)] @@ -782,7 +816,7 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { assert_eq!(search_bar.active_match_index, Some(1)); - search_bar.select_match(&SelectMatch(Direction::Prev), cx); + search_bar.select_prev_match(&SelectPrevMatch, cx); assert_eq!( editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)] @@ -799,7 +833,7 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { assert_eq!(search_bar.active_match_index, Some(1)); - search_bar.select_match(&SelectMatch(Direction::Next), cx); + search_bar.select_next_match(&SelectNextMatch, cx); assert_eq!( editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), [DisplayPoint::new(3, 11)..DisplayPoint::new(3, 13)] @@ -816,7 +850,7 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { assert_eq!(search_bar.active_match_index, Some(2)); - search_bar.select_match(&SelectMatch(Direction::Prev), cx); + search_bar.select_prev_match(&SelectPrevMatch, cx); assert_eq!( editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)] @@ -833,7 +867,7 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { assert_eq!(search_bar.active_match_index, Some(2)); - search_bar.select_match(&SelectMatch(Direction::Next), cx); + search_bar.select_next_match(&SelectNextMatch, cx); assert_eq!( editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), [DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43)] @@ -850,7 +884,7 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { assert_eq!(search_bar.active_match_index, Some(0)); - search_bar.select_match(&SelectMatch(Direction::Prev), cx); + search_bar.select_prev_match(&SelectPrevMatch, cx); assert_eq!( editor.update(cx, |editor, cx| editor.selected_display_ranges(cx)), [DisplayPoint::new(3, 56)..DisplayPoint::new(3, 58)] diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index f96e3c84d1..39ff1a09ea 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1,6 +1,6 @@ use crate::{ - active_match_index, match_index_for_direction, Direction, SearchOption, SelectMatch, - ToggleSearchOption, + active_match_index, match_index_for_direction, Direction, SearchOption, SelectNextMatch, + SelectPrevMatch, ToggleSearchOption, }; use collections::HashMap; use editor::{Anchor, Autoscroll, Editor, MultiBuffer, SelectAll}; @@ -34,14 +34,15 @@ pub fn init(cx: &mut MutableAppContext) { Binding::new("cmd-shift-F", Deploy, Some("Workspace")), Binding::new("enter", Search, Some("ProjectSearchBar")), Binding::new("cmd-enter", SearchInNew, Some("ProjectSearchBar")), - Binding::new("cmd-g", SelectMatch(Direction::Next), Some("Pane")), - Binding::new("cmd-shift-G", SelectMatch(Direction::Prev), Some("Pane")), + Binding::new("cmd-g", SelectNextMatch, Some("Pane")), + Binding::new("cmd-shift-G", SelectPrevMatch, Some("Pane")), ]); cx.add_action(ProjectSearchView::deploy); cx.add_action(ProjectSearchBar::search); cx.add_action(ProjectSearchBar::search_in_new); cx.add_action(ProjectSearchBar::toggle_search_option); - cx.add_action(ProjectSearchBar::select_match); + cx.add_action(ProjectSearchBar::select_next_match); + cx.add_action(ProjectSearchBar::select_prev_match); cx.add_action(ProjectSearchBar::toggle_focus); cx.capture_action(ProjectSearchBar::tab); } @@ -545,18 +546,23 @@ impl ProjectSearchBar { } } - fn select_match( - pane: &mut Pane, - &SelectMatch(direction): &SelectMatch, - cx: &mut ViewContext, - ) { + fn select_next_match(pane: &mut Pane, _: &SelectNextMatch, cx: &mut ViewContext) { if let Some(search_view) = pane .active_item() .and_then(|item| item.downcast::()) { - search_view.update(cx, |search_view, cx| { - search_view.select_match(direction, cx); - }); + search_view.update(cx, |view, cx| view.select_match(Direction::Next, cx)); + } else { + cx.propagate_action(); + } + } + + fn select_prev_match(pane: &mut Pane, _: &SelectPrevMatch, cx: &mut ViewContext) { + if let Some(search_view) = pane + .active_item() + .and_then(|item| item.downcast::()) + { + search_view.update(cx, |view, cx| view.select_match(Direction::Prev, cx)); } else { cx.propagate_action(); } @@ -635,7 +641,10 @@ impl ProjectSearchBar { .with_style(style.container) .boxed() }) - .on_click(move |cx| cx.dispatch_action(SelectMatch(direction))) + .on_click(move |cx| match direction { + Direction::Prev => cx.dispatch_action(SelectPrevMatch), + Direction::Next => cx.dispatch_action(SelectNextMatch), + }) .with_cursor_style(CursorStyle::PointingHand) .boxed() } diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index 83e5a259d2..48cf24b1f3 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -1,6 +1,6 @@ pub use buffer_search::BufferSearchBar; use editor::{Anchor, MultiBufferSnapshot}; -use gpui::{impl_actions, MutableAppContext}; +use gpui::{actions, impl_internal_actions, MutableAppContext}; pub use project_search::{ProjectSearchBar, ProjectSearchView}; use std::{ cmp::{self, Ordering}, @@ -18,10 +18,8 @@ pub fn init(cx: &mut MutableAppContext) { #[derive(Clone)] pub struct ToggleSearchOption(pub SearchOption); -#[derive(Clone)] -pub struct SelectMatch(pub Direction); - -impl_actions!(search, [ToggleSearchOption, SelectMatch]); +actions!(search, [SelectNextMatch, SelectPrevMatch]); +impl_internal_actions!(search, [ToggleSearchOption]); #[derive(Clone, Copy)] pub enum SearchOption { diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index fe1a63f5f1..8aafcd6a30 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -64,6 +64,7 @@ language = { path = "../language", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } project = { path = "../project", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"] } +theme = { path = "../theme" } workspace = { path = "../workspace", features = ["test-support"] } ctor = "0.1" env_logger = "0.8" diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index efa19653ae..5ebe36e824 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1117,6 +1117,7 @@ mod tests { }, time::Duration, }; + use theme::ThemeRegistry; use util::TryFutureExt; use workspace::{Item, SplitDirection, ToggleFollow, Workspace, WorkspaceParams}; @@ -5633,6 +5634,7 @@ mod tests { project: project.clone(), user_store: self.user_store.clone(), languages: self.language_registry.clone(), + themes: ThemeRegistry::new((), cx.font_cache().clone()), channel_list: cx.add_model(|cx| { ChannelList::new(self.user_store.clone(), self.client.clone(), cx) }), diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index b69552d02b..733df65b82 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -1,8 +1,8 @@ use editor::Editor; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use gpui::{ + actions, elements::*, - impl_actions, keymap::{self, Binding}, AppContext, Axis, Element, ElementBox, Entity, MutableAppContext, RenderContext, View, ViewContext, ViewHandle, @@ -25,15 +25,9 @@ pub struct ThemeSelector { selection_completed: bool, } -#[derive(Clone)] -pub struct Toggle(pub Arc); +actions!(theme_selector, [Toggle, Reload]); -#[derive(Clone)] -pub struct Reload(pub Arc); - -impl_actions!(theme_selector, [Toggle, Reload]); - -pub fn init(themes: Arc, cx: &mut MutableAppContext) { +pub fn init(cx: &mut MutableAppContext) { cx.add_action(ThemeSelector::confirm); cx.add_action(ThemeSelector::select_prev); cx.add_action(ThemeSelector::select_next); @@ -41,9 +35,9 @@ pub fn init(themes: Arc, cx: &mut MutableAppContext) { cx.add_action(ThemeSelector::reload); cx.add_bindings(vec![ - Binding::new("cmd-k cmd-t", Toggle(themes.clone()), None), - Binding::new("cmd-k t", Reload(themes.clone()), None), - Binding::new("escape", Toggle(themes.clone()), Some("ThemeSelector")), + Binding::new("cmd-k cmd-t", Toggle, None), + Binding::new("cmd-k t", Reload, None), + Binding::new("escape", Toggle, Some("ThemeSelector")), ]); } @@ -79,18 +73,20 @@ impl ThemeSelector { this } - fn toggle(workspace: &mut Workspace, action: &Toggle, cx: &mut ViewContext) { + 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(action.0.clone(), cx)); + let selector = cx.add_view(|cx| Self::new(themes, cx)); cx.subscribe(&selector, Self::on_event).detach(); selector }); } - fn reload(_: &mut Workspace, action: &Reload, cx: &mut ViewContext) { + fn reload(workspace: &mut Workspace, _: &Reload, cx: &mut ViewContext) { let current_theme_name = cx.global::().theme.name.clone(); - action.0.clear(); - match action.0.get(¤t_theme_name) { + let themes = workspace.themes(); + themes.clear(); + match themes.get(¤t_theme_name) { Ok(theme) => { Self::set_theme(theme, cx); log::info!("reloaded theme {}", current_theme_name); diff --git a/crates/vim/Cargo.toml b/crates/vim/Cargo.toml index 4ffa6a4363..f1413d7c51 100644 --- a/crates/vim/Cargo.toml +++ b/crates/vim/Cargo.toml @@ -12,6 +12,7 @@ collections = { path = "../collections" } editor = { path = "../editor" } gpui = { path = "../gpui" } language = { path = "../language" } +serde = { version = "1", features = ["derive"] } settings = { path = "../settings" } workspace = { path = "../workspace" } log = "0.4" diff --git a/crates/vim/src/mode.rs b/crates/vim/src/mode.rs index f00c14e2e8..ccebf0ad68 100644 --- a/crates/vim/src/mode.rs +++ b/crates/vim/src/mode.rs @@ -1,7 +1,8 @@ use editor::CursorShape; use gpui::keymap::Context; +use serde::Deserialize; -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)] pub enum Mode { Normal(NormalState), Insert, @@ -44,7 +45,7 @@ impl Default for Mode { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)] pub enum NormalState { None, GPrefix, diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index ce5305e8bf..0a49d2c8fd 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -4,15 +4,16 @@ use crate::{mode::NormalState, Mode, SwitchMode, VimState}; use editor::{char_kind, movement, Bias}; use gpui::{actions, impl_actions, keymap::Binding, MutableAppContext, ViewContext}; use language::SelectionGoal; +use serde::Deserialize; use workspace::Workspace; -#[derive(Clone)] +#[derive(Clone, Deserialize)] struct MoveToNextWordStart(pub bool); -#[derive(Clone)] +#[derive(Clone, Deserialize)] struct MoveToNextWordEnd(pub bool); -#[derive(Clone)] +#[derive(Clone, Deserialize)] struct MoveToPreviousWordStart(pub bool); impl_actions!( diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 2d76831076..adcf2fb130 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -8,12 +8,13 @@ mod vim_test_context; use collections::HashMap; use editor::{CursorShape, Editor}; use gpui::{impl_actions, MutableAppContext, ViewContext, WeakViewHandle}; +use serde::Deserialize; use mode::Mode; use settings::Settings; use workspace::{self, Workspace}; -#[derive(Clone)] +#[derive(Clone, Deserialize)] pub struct SwitchMode(pub Mode); impl_actions!(vim, [SwitchMode]); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 6459e46fca..a60e2ff9db 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -7,13 +7,14 @@ use gpui::{ actions, elements::*, geometry::{rect::RectF, vector::vec2f}, - impl_actions, + impl_actions, impl_internal_actions, keymap::Binding, platform::{CursorStyle, NavigationDirection}, AppContext, Entity, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use project::{ProjectEntryId, ProjectPath}; +use serde::Deserialize; use settings::Settings; use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc}; use util::ResultExt; @@ -28,13 +29,16 @@ actions!( ] ); -#[derive(Clone)] +#[derive(Clone, Deserialize)] pub struct Split(pub SplitDirection); #[derive(Clone)] -pub struct CloseItem(pub CloseItemParams); +pub struct CloseItem { + pub item_id: usize, + pub pane: WeakViewHandle, +} -#[derive(Clone)] +#[derive(Clone, Deserialize)] pub struct ActivateItem(pub usize); #[derive(Clone)] @@ -43,13 +47,8 @@ pub struct GoBack(pub Option>); #[derive(Clone)] pub struct GoForward(pub Option>); -impl_actions!(pane, [Split, CloseItem, ActivateItem, GoBack, GoForward,]); - -#[derive(Clone)] -pub struct CloseItemParams { - pub item_id: usize, - pub pane: WeakViewHandle, -} +impl_actions!(pane, [Split]); +impl_internal_actions!(pane, [CloseItem, ActivateItem, GoBack, GoForward]); const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; @@ -66,8 +65,8 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_async_action(Pane::close_active_item); cx.add_async_action(Pane::close_inactive_items); cx.add_async_action(|workspace: &mut Workspace, action: &CloseItem, cx| { - let pane = action.0.pane.upgrade(cx)?; - Some(Pane::close_item(workspace, pane, action.0.item_id, cx)) + let pane = action.pane.upgrade(cx)?; + Some(Pane::close_item(workspace, pane, action.item_id, cx)) }); cx.add_action(|pane: &mut Pane, action: &Split, cx| { pane.split(action.0, cx); @@ -747,10 +746,10 @@ impl Pane { .on_click({ let pane = pane.clone(); move |cx| { - cx.dispatch_action(CloseItem(CloseItemParams { + cx.dispatch_action(CloseItem { item_id, pane: pane.clone(), - })) + }) } }) .named("close-tab-icon") diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 258d644148..d5661ba2c9 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -4,6 +4,7 @@ use client::PeerId; use collections::HashMap; use gpui::{elements::*, Axis, Border, ViewHandle}; use project::Collaborator; +use serde::Deserialize; use theme::Theme; #[derive(Clone, Debug, Eq, PartialEq)] @@ -254,7 +255,8 @@ impl PaneAxis { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Deserialize)] +#[serde(rename_all = "snake_case")] pub enum SplitDirection { Up, Down, diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index 49852d16f2..78b1d4a6ea 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -1,5 +1,7 @@ use super::Workspace; -use gpui::{elements::*, impl_actions, platform::CursorStyle, AnyViewHandle, RenderContext}; +use gpui::{ + elements::*, impl_internal_actions, platform::CursorStyle, AnyViewHandle, RenderContext, +}; use std::{cell::RefCell, rc::Rc}; use theme::Theme; @@ -27,7 +29,7 @@ pub struct ToggleSidebarItem(pub SidebarItemId); #[derive(Clone)] pub struct ToggleSidebarItemFocus(pub SidebarItemId); -impl_actions!(workspace, [ToggleSidebarItem, ToggleSidebarItemFocus]); +impl_internal_actions!(workspace, [ToggleSidebarItem, ToggleSidebarItemFocus]); #[derive(Clone)] pub struct SidebarItemId { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6b6761b1f7..475913d9db 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -17,7 +17,7 @@ use gpui::{ color::Color, elements::*, geometry::{rect::RectF, vector::vec2f, PathBuilder}, - impl_actions, + impl_internal_actions, json::{self, to_string_pretty, ToJson}, keymap::Binding, platform::{CursorStyle, WindowOptions}, @@ -101,7 +101,7 @@ pub struct ToggleFollow(pub PeerId); #[derive(Clone)] pub struct JoinProject(pub JoinProjectParams); -impl_actions!( +impl_internal_actions!( workspace, [Open, OpenNew, OpenPaths, ToggleFollow, JoinProject] ); @@ -630,6 +630,7 @@ pub struct WorkspaceParams { pub client: Arc, pub fs: Arc, pub languages: Arc, + pub themes: Arc, pub user_store: ModelHandle, pub channel_list: ModelHandle, } @@ -659,6 +660,7 @@ impl WorkspaceParams { channel_list: cx .add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx)), client, + themes: ThemeRegistry::new((), cx.font_cache().clone()), fs, languages, user_store, @@ -677,6 +679,7 @@ impl WorkspaceParams { ), client: app_state.client.clone(), fs: app_state.fs.clone(), + themes: app_state.themes.clone(), languages: app_state.languages.clone(), user_store: app_state.user_store.clone(), channel_list: app_state.channel_list.clone(), @@ -694,6 +697,7 @@ pub struct Workspace { user_store: ModelHandle, remote_entity_subscription: Option, fs: Arc, + themes: Arc, modal: Option, center: PaneGroup, left_sidebar: Sidebar, @@ -802,6 +806,7 @@ impl Workspace { remote_entity_subscription: None, user_store: params.user_store.clone(), fs: params.fs.clone(), + themes: params.themes.clone(), left_sidebar: Sidebar::new(Side::Left), right_sidebar: Sidebar::new(Side::Right), project: params.project.clone(), @@ -834,6 +839,10 @@ impl Workspace { &self.project } + pub fn themes(&self) -> Arc { + self.themes.clone() + } + pub fn worktrees<'a>( &self, cx: &'a AppContext, diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 8633005899..abdf68e6ab 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -146,7 +146,7 @@ fn main() { }); journal::init(app_state.clone(), cx); zed::init(&app_state, cx); - theme_selector::init(app_state.themes.clone(), cx); + theme_selector::init(cx); cx.set_menus(menus::menus(&app_state.clone())); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 6fca5247fc..6465c9b253 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -14,7 +14,6 @@ pub use editor; use gpui::{ actions, geometry::vector::vec2f, - impl_actions, keymap::Binding, platform::{WindowBounds, WindowOptions}, ModelHandle, ViewContext, @@ -30,12 +29,16 @@ use std::{path::PathBuf, sync::Arc}; pub use workspace; use workspace::{AppState, Workspace, WorkspaceParams}; -actions!(zed, [About, Quit, OpenSettings]); - -#[derive(Clone)] -pub struct AdjustBufferFontSize(pub f32); - -impl_actions!(zed, [AdjustBufferFontSize]); +actions!( + zed, + [ + About, + Quit, + OpenSettings, + IncreaseBufferFontSize, + DecreaseBufferFontSize + ] +); const MIN_FONT_SIZE: f32 = 6.0; @@ -48,16 +51,18 @@ lazy_static! { pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { cx.add_global_action(quit); - cx.add_global_action({ - move |action: &AdjustBufferFontSize, cx| { - cx.update_global::(|settings, cx| { - settings.buffer_font_size = - (settings.buffer_font_size + action.0).max(MIN_FONT_SIZE); - cx.refresh_windows(); - }); - } + cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| { + cx.update_global::(|settings, cx| { + settings.buffer_font_size = (settings.buffer_font_size + 1.0).max(MIN_FONT_SIZE); + cx.refresh_windows(); + }); + }); + cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| { + cx.update_global::(|settings, cx| { + settings.buffer_font_size = (settings.buffer_font_size - 1.0).max(MIN_FONT_SIZE); + cx.refresh_windows(); + }); }); - cx.add_action({ let app_state = app_state.clone(); move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext| { @@ -100,8 +105,8 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { workspace::lsp_status::init(cx); cx.add_bindings(vec![ - Binding::new("cmd-=", AdjustBufferFontSize(1.), None), - Binding::new("cmd--", AdjustBufferFontSize(-1.), None), + Binding::new("cmd-=", IncreaseBufferFontSize, None), + Binding::new("cmd--", DecreaseBufferFontSize, None), Binding::new("cmd-,", OpenSettings, None), ]) } @@ -134,6 +139,7 @@ pub fn build_workspace( client: app_state.client.clone(), fs: app_state.fs.clone(), languages: app_state.languages.clone(), + themes: app_state.themes.clone(), user_store: app_state.user_store.clone(), channel_list: app_state.channel_list.clone(), };