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