Merge pull request #786 from zed-industries/load-keymaps

Allow key bindings to be customized via a JSON file
This commit is contained in:
Max Brunsfeld 2022-04-11 17:31:22 -07:00 committed by GitHub
commit 25e1e3d2df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
74 changed files with 1092 additions and 729 deletions

14
crates/assets/Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "assets"
version = "0.1.0"
edition = "2021"
[lib]
path = "src/assets.rs"
doctest = false
[dependencies]
gpui = { path = "../gpui" }
anyhow = "1.0.38"
rust-embed = { version = "6.3", features = ["include-exclude"] }

View file

@ -3,7 +3,7 @@ use gpui::AssetSource;
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
#[folder = "assets"]
#[folder = "../../assets"]
#[exclude = "*.DS_Store"]
pub struct Assets;

View file

@ -6,7 +6,6 @@ use editor::Editor;
use gpui::{
actions,
elements::*,
keymap::Binding,
platform::CursorStyle,
views::{ItemType, Select, SelectStyle},
AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View,
@ -38,8 +37,6 @@ actions!(chat_panel, [Send, LoadMoreMessages]);
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(ChatPanel::send);
cx.add_action(ChatPanel::load_more_messages);
cx.add_bindings(vec![Binding::new("enter", Send, Some("ChatPanel"))]);
}
impl ChatPanel {

View file

@ -8,9 +8,8 @@ use editor::{
highlight_diagnostic_message, Editor, ExcerptId, MultiBuffer, ToOffset,
};
use gpui::{
actions, elements::*, fonts::TextStyle, keymap::Binding, AnyViewHandle, AppContext, Entity,
ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle,
WeakViewHandle,
actions, elements::*, fonts::TextStyle, AnyViewHandle, AppContext, Entity, ModelHandle,
MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use language::{
Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal,
@ -33,7 +32,6 @@ actions!(diagnostics, [Deploy]);
const CONTEXT_LINE_COUNT: u32 = 1;
pub fn init(cx: &mut MutableAppContext) {
cx.add_bindings([Binding::new("alt-shift-D", Deploy, Some("Workspace"))]);
cx.add_action(ProjectDiagnosticsEditor::deploy);
}

View file

@ -22,8 +22,7 @@ use gpui::{
executor,
fonts::{self, HighlightStyle, TextStyle},
geometry::vector::{vec2f, Vector2F},
impl_actions,
keymap::Binding,
impl_actions, impl_internal_actions,
platform::CursorStyle,
text_layout, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity,
ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle,
@ -66,8 +65,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);
@ -78,47 +80,38 @@ pub struct Scroll(pub Vector2F);
#[derive(Clone)]
pub struct Select(pub SelectPhase);
#[derive(Clone)]
#[derive(Clone, Deserialize)]
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 deployed_from_indicator: bool,
}
#[derive(Clone)]
pub struct ConfirmCompletion(pub Option<usize>);
#[derive(Clone, Default, Deserialize)]
pub struct ConfirmCompletion {
#[serde(default)]
pub item_ix: Option<usize>,
}
#[derive(Clone)]
pub struct ConfirmCodeAction(pub Option<usize>);
impl_actions!(
editor,
[
SelectNext,
GoToDiagnostic,
Scroll,
Select,
Input,
Tab,
SelectToBeginningOfLine,
SelectToEndOfLine,
ToggleCodeActions,
ConfirmCompletion,
ConfirmCodeAction,
]
);
#[derive(Clone, Default, Deserialize)]
pub struct ConfirmCodeAction {
#[serde(default)]
pub item_ix: Option<usize>,
}
actions!(
editor,
@ -127,6 +120,8 @@ actions!(
Backspace,
Delete,
Newline,
GoToNextDiagnostic,
GoToPrevDiagnostic,
Indent,
Outdent,
DeleteLine,
@ -172,6 +167,8 @@ actions!(
SplitSelectionIntoLines,
AddSelectionAbove,
AddSelectionBelow,
Tab,
TabPrev,
ToggleComments,
SelectLargerSyntaxNode,
SelectSmallerSyntaxNode,
@ -193,6 +190,21 @@ actions!(
]
);
impl_actions!(
editor,
[
Input,
SelectNext,
SelectToBeginningOfLine,
SelectToEndOfLine,
ToggleCodeActions,
ConfirmCompletion,
ConfirmCodeAction,
]
);
impl_internal_actions!(editor, [Scroll, Select]);
enum DocumentHighlightRead {}
enum DocumentHighlightWrite {}
@ -203,175 +215,6 @@ pub enum Direction {
}
pub fn init(cx: &mut MutableAppContext) {
cx.add_bindings(vec![
Binding::new("escape", Cancel, Some("Editor")),
Binding::new("backspace", Backspace, Some("Editor")),
Binding::new("ctrl-h", Backspace, Some("Editor")),
Binding::new("delete", Delete, Some("Editor")),
Binding::new("ctrl-d", Delete, Some("Editor")),
Binding::new("enter", Newline, Some("Editor && mode == full")),
Binding::new(
"alt-enter",
Input("\n".into()),
Some("Editor && mode == auto_height"),
),
Binding::new(
"enter",
ConfirmCompletion(None),
Some("Editor && showing_completions"),
),
Binding::new(
"enter",
ConfirmCodeAction(None),
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",
ConfirmCompletion(None),
Some("Editor && showing_completions"),
),
Binding::new("cmd-[", Outdent, Some("Editor")),
Binding::new("cmd-]", Indent, Some("Editor")),
Binding::new("ctrl-shift-K", DeleteLine, Some("Editor")),
Binding::new("alt-backspace", DeleteToPreviousWordStart, Some("Editor")),
Binding::new("alt-h", DeleteToPreviousWordStart, Some("Editor")),
Binding::new(
"ctrl-alt-backspace",
DeleteToPreviousSubwordStart,
Some("Editor"),
),
Binding::new("ctrl-alt-h", DeleteToPreviousSubwordStart, Some("Editor")),
Binding::new("alt-delete", DeleteToNextWordEnd, Some("Editor")),
Binding::new("alt-d", DeleteToNextWordEnd, Some("Editor")),
Binding::new("ctrl-alt-delete", DeleteToNextSubwordEnd, Some("Editor")),
Binding::new("ctrl-alt-d", DeleteToNextSubwordEnd, Some("Editor")),
Binding::new("cmd-backspace", DeleteToBeginningOfLine, Some("Editor")),
Binding::new("cmd-delete", DeleteToEndOfLine, Some("Editor")),
Binding::new("ctrl-k", CutToEndOfLine, Some("Editor")),
Binding::new("cmd-shift-D", DuplicateLine, Some("Editor")),
Binding::new("ctrl-cmd-up", MoveLineUp, Some("Editor")),
Binding::new("ctrl-cmd-down", MoveLineDown, Some("Editor")),
Binding::new("cmd-x", Cut, Some("Editor")),
Binding::new("cmd-c", Copy, Some("Editor")),
Binding::new("cmd-v", Paste, Some("Editor")),
Binding::new("cmd-z", Undo, Some("Editor")),
Binding::new("cmd-shift-Z", Redo, Some("Editor")),
Binding::new("up", MoveUp, Some("Editor")),
Binding::new("down", MoveDown, Some("Editor")),
Binding::new("left", MoveLeft, Some("Editor")),
Binding::new("right", MoveRight, Some("Editor")),
Binding::new("ctrl-p", MoveUp, Some("Editor")),
Binding::new("ctrl-n", MoveDown, Some("Editor")),
Binding::new("ctrl-b", MoveLeft, Some("Editor")),
Binding::new("ctrl-f", MoveRight, Some("Editor")),
Binding::new("alt-left", MoveToPreviousWordStart, Some("Editor")),
Binding::new("alt-b", MoveToPreviousWordStart, Some("Editor")),
Binding::new("ctrl-alt-left", MoveToPreviousSubwordStart, Some("Editor")),
Binding::new("ctrl-alt-b", MoveToPreviousSubwordStart, Some("Editor")),
Binding::new("alt-right", MoveToNextWordEnd, Some("Editor")),
Binding::new("alt-f", MoveToNextWordEnd, Some("Editor")),
Binding::new("ctrl-alt-right", MoveToNextSubwordEnd, Some("Editor")),
Binding::new("ctrl-alt-f", MoveToNextSubwordEnd, Some("Editor")),
Binding::new("cmd-left", MoveToBeginningOfLine, Some("Editor")),
Binding::new("ctrl-a", MoveToBeginningOfLine, Some("Editor")),
Binding::new("cmd-right", MoveToEndOfLine, Some("Editor")),
Binding::new("ctrl-e", MoveToEndOfLine, Some("Editor")),
Binding::new("cmd-up", MoveToBeginning, Some("Editor")),
Binding::new("cmd-down", MoveToEnd, Some("Editor")),
Binding::new("shift-up", SelectUp, Some("Editor")),
Binding::new("ctrl-shift-P", SelectUp, Some("Editor")),
Binding::new("shift-down", SelectDown, Some("Editor")),
Binding::new("ctrl-shift-N", SelectDown, Some("Editor")),
Binding::new("shift-left", SelectLeft, Some("Editor")),
Binding::new("ctrl-shift-B", SelectLeft, Some("Editor")),
Binding::new("shift-right", SelectRight, Some("Editor")),
Binding::new("ctrl-shift-F", SelectRight, Some("Editor")),
Binding::new("alt-shift-left", SelectToPreviousWordStart, Some("Editor")),
Binding::new("alt-shift-B", SelectToPreviousWordStart, Some("Editor")),
Binding::new(
"ctrl-alt-shift-left",
SelectToPreviousSubwordStart,
Some("Editor"),
),
Binding::new(
"ctrl-alt-shift-B",
SelectToPreviousSubwordStart,
Some("Editor"),
),
Binding::new("alt-shift-right", SelectToNextWordEnd, Some("Editor")),
Binding::new("alt-shift-F", SelectToNextWordEnd, Some("Editor")),
Binding::new(
"cmd-shift-left",
SelectToBeginningOfLine {
stop_at_soft_wraps: true,
},
Some("Editor"),
),
Binding::new(
"ctrl-alt-shift-right",
SelectToNextSubwordEnd,
Some("Editor"),
),
Binding::new("ctrl-alt-shift-F", SelectToNextSubwordEnd, Some("Editor")),
Binding::new(
"ctrl-shift-A",
SelectToBeginningOfLine {
stop_at_soft_wraps: true,
},
Some("Editor"),
),
Binding::new(
"cmd-shift-right",
SelectToEndOfLine {
stop_at_soft_wraps: true,
},
Some("Editor"),
),
Binding::new(
"ctrl-shift-E",
SelectToEndOfLine {
stop_at_soft_wraps: true,
},
Some("Editor"),
),
Binding::new("cmd-shift-up", SelectToBeginning, Some("Editor")),
Binding::new("cmd-shift-down", SelectToEnd, Some("Editor")),
Binding::new("cmd-a", SelectAll, Some("Editor")),
Binding::new("cmd-l", SelectLine, Some("Editor")),
Binding::new("cmd-shift-L", SplitSelectionIntoLines, Some("Editor")),
Binding::new("cmd-alt-up", AddSelectionAbove, Some("Editor")),
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-/", ToggleComments, Some("Editor")),
Binding::new("alt-up", SelectLargerSyntaxNode, Some("Editor")),
Binding::new("ctrl-w", SelectLargerSyntaxNode, Some("Editor")),
Binding::new("alt-down", SelectSmallerSyntaxNode, Some("Editor")),
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("f2", Rename, Some("Editor")),
Binding::new("f12", GoToDefinition, Some("Editor")),
Binding::new("alt-shift-f12", FindAllReferences, Some("Editor")),
Binding::new("ctrl-m", MoveToEnclosingBracket, Some("Editor")),
Binding::new("pageup", PageUp, Some("Editor")),
Binding::new("pagedown", PageDown, Some("Editor")),
Binding::new("alt-cmd-[", Fold, Some("Editor")),
Binding::new("alt-cmd-]", UnfoldLines, Some("Editor")),
Binding::new("alt-cmd-f", FoldSelectedRanges, Some("Editor")),
Binding::new("ctrl-space", ShowCompletions, Some("Editor")),
Binding::new("cmd-.", ToggleCodeActions(false), Some("Editor")),
Binding::new("alt-enter", OpenExcerpts, Some("Editor")),
Binding::new("cmd-f10", RestartLanguageServer, Some("Editor")),
]);
cx.add_action(Editor::open_new);
cx.add_action(|this: &mut Editor, action: &Scroll, cx| this.set_scroll_position(action.0, cx));
cx.add_action(Editor::select);
@ -381,6 +224,7 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Editor::backspace);
cx.add_action(Editor::delete);
cx.add_action(Editor::tab);
cx.add_action(Editor::tab_prev);
cx.add_action(Editor::indent);
cx.add_action(Editor::outdent);
cx.add_action(Editor::delete_line);
@ -435,7 +279,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);
@ -833,7 +678,9 @@ impl CompletionsMenu {
)
.with_cursor_style(CursorStyle::PointingHand)
.on_mouse_down(move |cx| {
cx.dispatch_action(ConfirmCompletion(Some(item_ix)));
cx.dispatch_action(ConfirmCompletion {
item_ix: Some(item_ix),
});
})
.boxed(),
);
@ -959,7 +806,9 @@ impl CodeActionsMenu {
})
.with_cursor_style(CursorStyle::PointingHand)
.on_mouse_down(move |cx| {
cx.dispatch_action(ConfirmCodeAction(Some(item_ix)));
cx.dispatch_action(ConfirmCodeAction {
item_ix: Some(item_ix),
});
})
.boxed(),
);
@ -2457,7 +2306,7 @@ impl Editor {
pub fn confirm_completion(
&mut self,
ConfirmCompletion(completion_ix): &ConfirmCompletion,
action: &ConfirmCompletion,
cx: &mut ViewContext<Self>,
) -> Option<Task<Result<()>>> {
use language::ToOffset as _;
@ -2470,7 +2319,7 @@ impl Editor {
let mat = completions_menu
.matches
.get(completion_ix.unwrap_or(completions_menu.selected_item))?;
.get(action.item_ix.unwrap_or(completions_menu.selected_item))?;
let buffer_handle = completions_menu.buffer;
let completion = completions_menu.completions.get(mat.candidate_id)?;
@ -2560,11 +2409,7 @@ impl Editor {
}))
}
pub fn toggle_code_actions(
&mut self,
&ToggleCodeActions(deployed_from_indicator): &ToggleCodeActions,
cx: &mut ViewContext<Self>,
) {
pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext<Self>) {
if matches!(
self.context_menu.as_ref(),
Some(ContextMenu::CodeActions(_))
@ -2574,6 +2419,7 @@ impl Editor {
return;
}
let deployed_from_indicator = action.deployed_from_indicator;
let mut task = self.code_actions_task.take();
cx.spawn_weak(|this, mut cx| async move {
while let Some(prev_task) = task {
@ -2608,7 +2454,7 @@ impl Editor {
pub fn confirm_code_action(
workspace: &mut Workspace,
ConfirmCodeAction(action_ix): &ConfirmCodeAction,
action: &ConfirmCodeAction,
cx: &mut ViewContext<Workspace>,
) -> Option<Task<Result<()>>> {
let editor = workspace.active_item(cx)?.act_as::<Editor>(cx)?;
@ -2619,7 +2465,7 @@ impl Editor {
} else {
return None;
};
let action_ix = action_ix.unwrap_or(actions_menu.selected_item);
let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
let action = actions_menu.actions.get(action_ix)?.clone();
let title = action.lsp_action.title.clone();
let buffer = actions_menu.buffer;
@ -2846,7 +2692,9 @@ impl Editor {
.with_cursor_style(CursorStyle::PointingHand)
.with_padding(Padding::uniform(3.))
.on_mouse_down(|cx| {
cx.dispatch_action(ToggleCodeActions(true));
cx.dispatch_action(ToggleCodeActions {
deployed_from_indicator: true,
});
})
.boxed(),
)
@ -2940,8 +2788,8 @@ impl Editor {
self.move_to_snippet_tabstop(Bias::Right, cx)
}
pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) {
self.move_to_snippet_tabstop(Bias::Left, cx);
pub fn move_to_prev_snippet_tabstop(&mut self, cx: &mut ViewContext<Self>) -> bool {
self.move_to_snippet_tabstop(Bias::Left, cx)
}
pub fn move_to_snippet_tabstop(&mut self, bias: Bias, cx: &mut ViewContext<Self>) -> bool {
@ -3046,54 +2894,46 @@ impl Editor {
});
}
pub fn tab(&mut self, &Tab(direction): &Tab, cx: &mut ViewContext<Self>) {
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<Self>) {
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::<Point>(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::<Settings>().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<Self>) {
if self.move_to_next_snippet_tabstop(cx) {
return;
}
let mut selections = self.local_selections::<Point>(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::<Settings>().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 +4077,6 @@ impl Editor {
pub fn select_next(&mut self, action: &SelectNext, cx: &mut ViewContext<Self>) {
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::<usize>(cx);
@ -4276,7 +4115,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 +4386,15 @@ impl Editor {
self.selection_history.mode = SelectionHistoryMode::Normal;
}
pub fn go_to_diagnostic(
&mut self,
&GoToDiagnostic(direction): &GoToDiagnostic,
cx: &mut ViewContext<Self>,
) {
fn go_to_next_diagnostic(&mut self, _: &GoToNextDiagnostic, cx: &mut ViewContext<Self>) {
self.go_to_diagnostic(Direction::Next, cx)
}
fn go_to_prev_diagnostic(&mut self, _: &GoToPrevDiagnostic, 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 selection = self.newest_selection_with_snapshot::<usize>(&buffer);
let mut active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
@ -7771,7 +7614,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 +7625,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 +7646,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 +7655,7 @@ mod tests {
] four"},
cx,
);
view.tab(&Tab(Direction::Prev), cx);
view.tab_prev(&TabPrev, cx);
assert_text_with_selections(
view,
indoc! {"
@ -7831,7 +7674,7 @@ mod tests {
four"},
cx,
);
view.tab(&Tab(Direction::Next), cx);
view.tab(&Tab, cx);
assert_text_with_selections(
view,
indoc! {"
@ -7849,7 +7692,7 @@ mod tests {
four"},
cx,
);
view.tab(&Tab(Direction::Prev), cx);
view.tab_prev(&TabPrev, cx);
assert_text_with_selections(
view,
indoc! {"
@ -7939,7 +7782,7 @@ mod tests {
cx,
);
editor.tab(&Tab(Direction::Next), cx);
editor.tab(&Tab, cx);
assert_text_with_selections(
&mut editor,
indoc! {"
@ -7950,7 +7793,7 @@ mod tests {
"},
cx,
);
editor.tab(&Tab(Direction::Prev), cx);
editor.tab_prev(&TabPrev, cx);
assert_text_with_selections(
&mut editor,
indoc! {"
@ -8693,10 +8536,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 +8558,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]);
});
}
@ -9363,7 +9226,7 @@ mod tests {
let apply_additional_edits = editor.update(cx, |editor, cx| {
editor.move_down(&MoveDown, cx);
let apply_additional_edits = editor
.confirm_completion(&ConfirmCompletion(None), cx)
.confirm_completion(&ConfirmCompletion::default(), cx)
.unwrap();
assert_eq!(
editor.text(cx),
@ -9446,7 +9309,7 @@ mod tests {
let apply_additional_edits = editor.update(cx, |editor, cx| {
let apply_additional_edits = editor
.confirm_completion(&ConfirmCompletion(None), cx)
.confirm_completion(&ConfirmCompletion::default(), cx)
.unwrap();
assert_eq!(
editor.text(cx),

View file

@ -1,12 +1,8 @@
use editor::Editor;
use fuzzy::PathMatch;
use gpui::{
actions,
elements::*,
impl_actions,
keymap::{self, Binding},
AppContext, Axis, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View,
ViewContext, ViewHandle, WeakViewHandle,
actions, elements::*, impl_internal_actions, keymap, AppContext, Axis, Entity, ModelHandle,
MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use project::{Project, ProjectPath, WorktreeId};
use settings::Settings;
@ -41,8 +37,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);
@ -50,11 +46,6 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(FileFinder::select);
cx.add_action(FileFinder::select_prev);
cx.add_action(FileFinder::select_next);
cx.add_bindings(vec![
Binding::new("cmd-p", Toggle, None),
Binding::new("escape", Toggle, Some("FileFinder")),
]);
}
pub enum Event {

View file

@ -1,7 +1,7 @@
use editor::{display_map::ToDisplayPoint, Autoscroll, DisplayPoint, Editor};
use gpui::{
actions, elements::*, geometry::vector::Vector2F, keymap::Binding, Axis, Entity,
MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
actions, elements::*, geometry::vector::Vector2F, Axis, Entity, MutableAppContext,
RenderContext, View, ViewContext, ViewHandle,
};
use settings::Settings;
use text::{Bias, Point};
@ -10,11 +10,6 @@ use workspace::Workspace;
actions!(go_to_line, [Toggle, Confirm]);
pub fn init(cx: &mut MutableAppContext) {
cx.add_bindings([
Binding::new("ctrl-g", Toggle, Some("Editor")),
Binding::new("escape", Toggle, Some("GoToLine")),
Binding::new("enter", Confirm, Some("GoToLine")),
]);
cx.add_action(GoToLine::toggle);
cx.add_action(GoToLine::confirm);
}

View file

@ -10,7 +10,7 @@ use crate::{
AssetCache, AssetSource, ClipboardItem, FontCache, PathPromptOptions, TextLayoutCache,
};
pub use action::*;
use anyhow::{anyhow, Result};
use anyhow::{anyhow, Context, Result};
use collections::btree_map;
use keymap::MatchResult;
use lazy_static::lazy_static;
@ -715,12 +715,14 @@ type GlobalSubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext
type ObservationCallback = Box<dyn FnMut(&mut MutableAppContext) -> bool>;
type GlobalObservationCallback = 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 {
weak_self: Option<rc::Weak<RefCell<Self>>>,
foreground_platform: Rc<dyn platform::ForegroundPlatform>,
assets: Arc<AssetCache>,
cx: AppContext,
action_deserializers: HashMap<&'static str, DeserializeActionCallback>,
capture_actions: HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>>,
actions: HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>>,
global_actions: HashMap<TypeId, Box<GlobalActionCallback>>,
@ -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,19 @@ impl MutableAppContext {
.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("{}"))
.with_context(|| format!("invalid data for action {}", name))
}
pub fn add_action<A, V, F>(&mut self, handler: F)
where
A: Action,
@ -899,6 +915,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 +954,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::<A>(), handler)
@ -1334,6 +1358,10 @@ impl MutableAppContext {
self.keystroke_matcher.add_bindings(bindings);
}
pub fn clear_bindings(&mut self) {
self.keystroke_matcher.clear_bindings();
}
pub fn dispatch_keystroke(
&mut self,
window_id: usize,
@ -4575,7 +4603,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 +5712,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)]
fn test_dispatch_action(cx: &mut MutableAppContext) {
struct ViewA {
@ -5721,32 +5786,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 +5819,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 +5854,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 +5877,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 +5897,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 +5952,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 +5974,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,

View file

@ -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<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_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<dyn $crate::action::Action> {
Box::new(self.clone())
}
fn boxed_clone_as_any(&self) -> Box<dyn std::any::Any> {
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<Box<dyn $crate::Action>> {
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<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
}
};
}

View file

@ -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;

View file

@ -1,5 +1,5 @@
use crate::Action;
use anyhow::anyhow;
use anyhow::{anyhow, Result};
use std::{
any::Any,
collections::{HashMap, HashSet},
@ -106,6 +106,11 @@ impl Matcher {
self.keymap.add_bindings(bindings);
}
pub fn clear_bindings(&mut self) {
self.pending.clear();
self.keymap.clear();
}
pub fn clear_pending(&mut self) {
self.pending.clear();
}
@ -164,24 +169,34 @@ impl Keymap {
fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
self.0.extend(bindings.into_iter());
}
fn clear(&mut self) {
self.0.clear();
}
}
impl Binding {
pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
Self::load(keystrokes, Box::new(action), context).unwrap()
}
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
let context = if let Some(context) = context {
Some(ContextPredicate::parse(context).unwrap())
Some(ContextPredicate::parse(context)?)
} else {
None
};
Self {
keystrokes: keystrokes
.split_whitespace()
.map(|key| Keystroke::parse(key).unwrap())
.collect(),
action: Box::new(action),
let keystrokes = keystrokes
.split_whitespace()
.map(|key| Keystroke::parse(key))
.collect::<Result<_>>()?;
Ok(Self {
keystrokes,
action,
context,
}
})
}
}
@ -328,6 +343,8 @@ impl ContextPredicate {
#[cfg(test)]
mod tests {
use serde::Deserialize;
use crate::{actions, impl_actions};
use super::*;
@ -419,30 +436,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 +461,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>("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::<A>("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>("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>("a", 1, &ctx_b), None);
assert_eq!(matcher.test_keystroke::<A>("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<Box<dyn Action>>) -> Option<&'a A> {
action
.as_ref()
.and_then(|action| action.as_any().downcast_ref())
}
impl Matcher {
fn test_keystroke<A>(&mut self, keystroke: &str, view_id: usize, cx: &Context) -> Option<A>
where
A: Action + Debug + Eq,
{
fn test_keystroke(
&mut self,
keystroke: &str,
view_id: usize,
cx: &Context,
) -> Option<Box<dyn Action>> {
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
}

View file

@ -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]);

View file

@ -1,6 +1,6 @@
use chrono::{Datelike, Local, Timelike};
use editor::{Autoscroll, Editor};
use gpui::{actions, keymap::Binding, MutableAppContext};
use gpui::{actions, MutableAppContext};
use std::{fs::OpenOptions, sync::Arc};
use util::TryFutureExt as _;
use workspace::AppState;
@ -8,7 +8,6 @@ use workspace::AppState;
actions!(journal, [NewJournalEntry]);
pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
cx.add_bindings(vec![Binding::new("ctrl-alt-cmd-j", NewJournalEntry, None)]);
cx.add_global_action(move |_: &NewJournalEntry, cx| new_journal_entry(app_state.clone(), cx));
}

View file

@ -4,12 +4,8 @@ use editor::{
};
use fuzzy::StringMatch;
use gpui::{
actions,
elements::*,
geometry::vector::Vector2F,
keymap::{self, Binding},
AppContext, Axis, Entity, MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
WeakViewHandle,
actions, elements::*, geometry::vector::Vector2F, keymap, AppContext, Axis, Entity,
MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle,
};
use language::Outline;
use ordered_float::OrderedFloat;
@ -23,10 +19,6 @@ use workspace::{
actions!(outline, [Toggle]);
pub fn init(cx: &mut MutableAppContext) {
cx.add_bindings([
Binding::new("cmd-shift-O", Toggle, Some("Editor")),
Binding::new("escape", Toggle, Some("OutlineView")),
]);
cx.add_action(OutlineView::toggle);
cx.add_action(OutlineView::confirm);
cx.add_action(OutlineView::select_prev);

View file

@ -4,8 +4,7 @@ use gpui::{
Align, ConstrainedBox, Empty, Flex, Label, MouseEventHandler, ParentElement, ScrollTarget,
Svg, UniformList, UniformListState,
},
impl_actions,
keymap::{self, Binding},
impl_internal_actions, keymap,
platform::CursorStyle,
AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, View, ViewContext,
ViewHandle, WeakViewHandle,
@ -54,7 +53,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);
@ -63,10 +62,6 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(ProjectPanel::select_prev);
cx.add_action(ProjectPanel::select_next);
cx.add_action(ProjectPanel::open_entry);
cx.add_bindings([
Binding::new("right", ExpandSelectedEntry, Some("ProjectPanel")),
Binding::new("left", CollapseSelectedEntry, Some("ProjectPanel")),
]);
}
pub enum Event {

View file

@ -3,11 +3,8 @@ use editor::{
};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
actions,
elements::*,
keymap::{self, Binding},
AppContext, Axis, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View,
ViewContext, ViewHandle, WeakViewHandle,
actions, elements::*, keymap, AppContext, Axis, Entity, ModelHandle, MutableAppContext,
RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use ordered_float::OrderedFloat;
use project::{Project, Symbol};
@ -25,10 +22,6 @@ use workspace::{
actions!(project_symbols, [Toggle]);
pub fn init(cx: &mut MutableAppContext) {
cx.add_bindings([
Binding::new("cmd-t", Toggle, None),
Binding::new("escape", Toggle, Some("ProjectSymbolsView")),
]);
cx.add_action(ProjectSymbolsView::toggle);
cx.add_action(ProjectSymbolsView::confirm);
cx.add_action(ProjectSymbolsView::select_prev);

View file

@ -20,6 +20,7 @@ workspace = { path = "../workspace" }
anyhow = "1.0"
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
postage = { version = "0.4.1", features = ["futures-traits"] }
serde = { version = "1", features = ["derive"] }
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }

View file

@ -1,55 +1,46 @@
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,
actions, elements::*, impl_actions, impl_internal_actions, 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,
}
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("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")),
]);
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 +316,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<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 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 +362,15 @@ impl BufferSearchBar {
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(editor) = self.active_editor.as_ref() {
editor.update(cx, |editor, cx| {
@ -389,9 +391,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>() {
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 +715,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 +726,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 +737,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 +748,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 +759,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 +770,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 +781,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 +798,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 +815,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 +832,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 +849,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 +866,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)]

View file

@ -1,13 +1,13 @@
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};
use gpui::{
actions, elements::*, keymap::Binding, platform::CursorStyle, AppContext, ElementBox, Entity,
ModelContext, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View,
ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
actions, elements::*, platform::CursorStyle, AppContext, ElementBox, Entity, ModelContext,
ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext,
ViewHandle, WeakModelHandle, WeakViewHandle,
};
use project::{search::SearchQuery, Project};
use settings::Settings;
@ -28,20 +28,12 @@ struct ActiveSearches(HashMap<WeakModelHandle<Project>, WeakViewHandle<ProjectSe
pub fn init(cx: &mut MutableAppContext) {
cx.set_global(ActiveSearches::default());
cx.add_bindings([
Binding::new("cmd-shift-F", ToggleFocus, Some("Pane")),
Binding::new("cmd-f", ToggleFocus, Some("Pane")),
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")),
]);
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);
}
@ -561,18 +553,23 @@ impl ProjectSearchBar {
}
}
fn select_match(
pane: &mut Pane,
&SelectMatch(direction): &SelectMatch,
cx: &mut ViewContext<Pane>,
) {
fn select_next_match(pane: &mut Pane, _: &SelectNextMatch, cx: &mut ViewContext<Pane>) {
if let Some(search_view) = pane
.active_item()
.and_then(|item| item.downcast::<ProjectSearchView>())
{
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<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 {
cx.propagate_action();
}
@ -651,7 +648,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()
}

View file

@ -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 {

View file

@ -66,6 +66,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"

View file

@ -1117,6 +1117,7 @@ mod tests {
},
time::Duration,
};
use theme::ThemeRegistry;
use util::TryFutureExt;
use workspace::{Item, SplitDirection, ToggleFollow, Workspace, WorkspaceParams};
@ -2418,7 +2419,7 @@ mod tests {
.condition(&cx_b, |editor, _| editor.context_menu_visible())
.await;
editor_b.update(cx_b, |editor, cx| {
editor.confirm_completion(&ConfirmCompletion(Some(0)), cx);
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
});
@ -3607,7 +3608,12 @@ mod tests {
// Toggle code actions and wait for them to display.
editor_b.update(cx_b, |editor, cx| {
editor.toggle_code_actions(&ToggleCodeActions(false), cx);
editor.toggle_code_actions(
&ToggleCodeActions {
deployed_from_indicator: false,
},
cx,
);
});
editor_b
.condition(&cx_b, |editor, _| editor.context_menu_visible())
@ -3618,7 +3624,7 @@ mod tests {
// Confirming the code action will trigger a resolve request.
let confirm_action = workspace_b
.update(cx_b, |workspace, cx| {
Editor::confirm_code_action(workspace, &ConfirmCodeAction(Some(0)), cx)
Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
})
.unwrap();
fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
@ -5633,6 +5639,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)
}),

View file

@ -11,6 +11,8 @@ doctest = false
test-support = []
[dependencies]
assets = { path = "../assets" }
collections = { path = "../collections" }
gpui = { path = "../gpui" }
theme = { path = "../theme" }
util = { path = "../util" }

View file

@ -0,0 +1,62 @@
use anyhow::{Context, Result};
use assets::Assets;
use collections::BTreeMap;
use gpui::{keymap::Binding, MutableAppContext};
use serde::Deserialize;
use serde_json::value::RawValue;
#[derive(Deserialize, Default, Clone)]
#[serde(transparent)]
pub struct KeymapFile(BTreeMap<String, ActionsByKeystroke>);
type ActionsByKeystroke = BTreeMap<String, Box<RawValue>>;
#[derive(Deserialize)]
struct ActionWithData<'a>(#[serde(borrow)] &'a str, #[serde(borrow)] &'a RawValue);
impl KeymapFile {
pub fn load_defaults(cx: &mut MutableAppContext) {
for path in ["keymaps/default.json", "keymaps/vim.json"] {
Self::load(path, cx).unwrap();
}
}
pub fn load(asset_path: &str, cx: &mut MutableAppContext) -> Result<()> {
let content = Assets::get(asset_path).unwrap().data;
let content_str = std::str::from_utf8(content.as_ref()).unwrap();
Ok(serde_json::from_str::<Self>(content_str)?.add(cx)?)
}
pub fn add(self, cx: &mut MutableAppContext) -> Result<()> {
for (context, actions) in self.0 {
let context = if context == "*" { None } else { Some(context) };
cx.add_bindings(
actions
.into_iter()
.map(|(keystroke, action)| {
let action = action.get();
// This is a workaround for a limitation in serde: serde-rs/json#497
// We want to deserialize the action data as a `RawValue` so that we can
// deserialize the action itself dynamically directly from the JSON
// string. But `RawValue` currently does not work inside of an untagged enum.
let action = if action.starts_with('[') {
let ActionWithData(name, data) = serde_json::from_str(action)?;
cx.deserialize_action(name, Some(data.get()))
} else {
let name = serde_json::from_str(action)?;
cx.deserialize_action(name, None)
}
.with_context(|| {
format!(
"invalid binding value for keystroke {keystroke}, context {context:?}"
)
})?;
Binding::load(&keystroke, action, context.as_deref())
})
.collect::<Result<Vec<_>>>()?,
)
}
Ok(())
}
}

View file

@ -1,3 +1,5 @@
mod keymap_file;
use anyhow::Result;
use gpui::font_cache::{FamilyId, FontCache};
use schemars::{
@ -13,6 +15,8 @@ use std::{collections::HashMap, sync::Arc};
use theme::{Theme, ThemeRegistry};
use util::ResultExt as _;
pub use keymap_file::KeymapFile;
#[derive(Clone)]
pub struct Settings {
pub buffer_font_family: FamilyId,

View file

@ -1,11 +1,8 @@
use editor::Editor;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
elements::*,
impl_actions,
keymap::{self, Binding},
AppContext, Axis, Element, ElementBox, Entity, MutableAppContext, RenderContext, View,
ViewContext, ViewHandle,
actions, elements::*, keymap, AppContext, Axis, Element, ElementBox, Entity, MutableAppContext,
RenderContext, View, ViewContext, ViewHandle,
};
use settings::Settings;
use std::{cmp, sync::Arc};
@ -25,26 +22,14 @@ pub struct ThemeSelector {
selection_completed: bool,
}
#[derive(Clone)]
pub struct Toggle(pub Arc<ThemeRegistry>);
actions!(theme_selector, [Toggle, Reload]);
#[derive(Clone)]
pub struct Reload(pub Arc<ThemeRegistry>);
impl_actions!(theme_selector, [Toggle, Reload]);
pub fn init(themes: Arc<ThemeRegistry>, 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);
cx.add_action(ThemeSelector::toggle);
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")),
]);
}
pub enum Event {
@ -79,18 +64,20 @@ impl ThemeSelector {
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, _| {
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<Workspace>) {
fn reload(workspace: &mut Workspace, _: &Reload, cx: &mut ViewContext<Workspace>) {
let current_theme_name = cx.global::<Settings>().theme.name.clone();
action.0.clear();
match action.0.get(&current_theme_name) {
let themes = workspace.themes();
themes.clear();
match themes.get(&current_theme_name) {
Ok(theme) => {
Self::set_theme(theme, cx);
log::info!("reloaded theme {}", current_theme_name);

View file

@ -8,10 +8,12 @@ path = "src/vim.rs"
doctest = false
[dependencies]
assets = { path = "../assets" }
collections = { path = "../collections" }
editor = { path = "../editor" }
gpui = { path = "../gpui" }
language = { path = "../language" }
serde = { version = "1", features = ["derive"] }
settings = { path = "../settings" }
workspace = { path = "../workspace" }
log = { version = "0.4.16", features = ["kv_unstable_serde"] }

View file

@ -1,18 +1,12 @@
use crate::{mode::Mode, SwitchMode, VimState};
use editor::Bias;
use gpui::{actions, keymap::Binding, MutableAppContext, ViewContext};
use gpui::{actions, MutableAppContext, ViewContext};
use language::SelectionGoal;
use workspace::Workspace;
actions!(vim, [NormalBefore]);
pub fn init(cx: &mut MutableAppContext) {
let context = Some("Editor && vim_mode == insert");
cx.add_bindings(vec![
Binding::new("escape", NormalBefore, context),
Binding::new("ctrl-c", NormalBefore, context),
]);
cx.add_action(normal_before);
}

View file

@ -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,

View file

@ -1,18 +1,19 @@
mod g_prefix;
use crate::{mode::NormalState, Mode, SwitchMode, VimState};
use crate::VimState;
use editor::{char_kind, movement, Bias};
use gpui::{actions, impl_actions, keymap::Binding, MutableAppContext, ViewContext};
use gpui::{actions, impl_actions, 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!(
@ -39,26 +40,7 @@ actions!(
);
pub fn init(cx: &mut MutableAppContext) {
let context = Some("Editor && vim_mode == normal");
cx.add_bindings(vec![
Binding::new("i", SwitchMode(Mode::Insert), context),
Binding::new("g", SwitchMode(Mode::Normal(NormalState::GPrefix)), context),
Binding::new("h", MoveLeft, context),
Binding::new("j", MoveDown, context),
Binding::new("k", MoveUp, context),
Binding::new("l", MoveRight, context),
Binding::new("0", MoveToStartOfLine, context),
Binding::new("shift-$", MoveToEndOfLine, context),
Binding::new("shift-G", MoveToEnd, context),
Binding::new("w", MoveToNextWordStart(false), context),
Binding::new("shift-W", MoveToNextWordStart(true), context),
Binding::new("e", MoveToNextWordEnd(false), context),
Binding::new("shift-E", MoveToNextWordEnd(true), context),
Binding::new("b", MoveToPreviousWordStart(false), context),
Binding::new("shift-B", MoveToPreviousWordStart(true), context),
]);
g_prefix::init(cx);
cx.add_action(move_left);
cx.add_action(move_down);
cx.add_action(move_up);

View file

@ -1,16 +1,10 @@
use crate::{mode::Mode, SwitchMode, VimState};
use gpui::{actions, keymap::Binding, MutableAppContext, ViewContext};
use gpui::{actions, MutableAppContext, ViewContext};
use workspace::Workspace;
actions!(vim, [MoveToStart]);
pub fn init(cx: &mut MutableAppContext) {
let context = Some("Editor && vim_mode == normal && vim_submode == g");
cx.add_bindings(vec![
Binding::new("g", MoveToStart, context),
Binding::new("escape", SwitchMode(Mode::normal()), context),
]);
cx.add_action(move_to_start);
}

View file

@ -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]);

View file

@ -23,7 +23,10 @@ impl<'a> VimTestContext<'a> {
cx.update(|cx| {
editor::init(cx);
crate::init(cx);
settings::KeymapFile::load("keymaps/vim.json", cx).unwrap();
});
let params = cx.update(WorkspaceParams::test);
cx.update(|cx| {

View file

@ -8,7 +8,7 @@ path = "src/workspace.rs"
doctest = false
[features]
test-support = ["client/test-support", "project/test-support"]
test-support = ["client/test-support", "project/test-support", "settings/test-support"]
[dependencies]
client = { path = "../client" }

View file

@ -1,18 +1,4 @@
use gpui::{actions, keymap::Binding, MutableAppContext};
actions!(
gpui::actions!(
menu,
[Confirm, SelectPrev, SelectNext, SelectFirst, SelectLast,]
[Confirm, SelectPrev, SelectNext, SelectFirst, SelectLast]
);
pub fn init(cx: &mut MutableAppContext) {
cx.add_bindings([
Binding::new("up", SelectPrev, Some("menu")),
Binding::new("ctrl-p", SelectPrev, Some("menu")),
Binding::new("down", SelectNext, Some("menu")),
Binding::new("ctrl-n", SelectNext, Some("menu")),
Binding::new("cmd-up", SelectFirst, Some("menu")),
Binding::new("cmd-down", SelectLast, Some("menu")),
Binding::new("enter", Confirm, Some("menu")),
]);
}

View file

@ -7,13 +7,13 @@ use gpui::{
actions,
elements::*,
geometry::{rect::RectF, vector::vec2f},
impl_actions,
keymap::Binding,
impl_actions, impl_internal_actions,
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,29 +28,33 @@ actions!(
]
);
#[derive(Clone)]
#[derive(Clone, Deserialize)]
pub struct Split(pub SplitDirection);
#[derive(Clone)]
pub struct CloseItem(pub CloseItemParams);
#[derive(Clone)]
pub struct ActivateItem(pub usize);
#[derive(Clone)]
pub struct GoBack(pub Option<WeakViewHandle<Pane>>);
#[derive(Clone)]
pub struct GoForward(pub Option<WeakViewHandle<Pane>>);
impl_actions!(pane, [Split, CloseItem, ActivateItem, GoBack, GoForward,]);
#[derive(Clone)]
pub struct CloseItemParams {
pub struct CloseItem {
pub item_id: usize,
pub pane: WeakViewHandle<Pane>,
}
#[derive(Clone, Deserialize)]
pub struct ActivateItem(pub usize);
#[derive(Clone, Deserialize)]
pub struct GoBack {
#[serde(skip_deserializing)]
pub pane: Option<WeakViewHandle<Pane>>,
}
#[derive(Clone, Deserialize)]
pub struct GoForward {
#[serde(skip_deserializing)]
pub pane: Option<WeakViewHandle<Pane>>,
}
impl_actions!(pane, [Split, GoBack, GoForward]);
impl_internal_actions!(pane, [CloseItem, ActivateItem]);
const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
pub fn init(cx: &mut MutableAppContext) {
@ -66,8 +70,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);
@ -76,7 +80,7 @@ pub fn init(cx: &mut MutableAppContext) {
Pane::go_back(
workspace,
action
.0
.pane
.as_ref()
.and_then(|weak_handle| weak_handle.upgrade(cx)),
cx,
@ -87,26 +91,13 @@ pub fn init(cx: &mut MutableAppContext) {
Pane::go_forward(
workspace,
action
.0
.pane
.as_ref()
.and_then(|weak_handle| weak_handle.upgrade(cx)),
cx,
)
.detach();
});
cx.add_bindings(vec![
Binding::new("shift-cmd-{", ActivatePrevItem, Some("Pane")),
Binding::new("shift-cmd-}", ActivateNextItem, Some("Pane")),
Binding::new("cmd-w", CloseActiveItem, Some("Pane")),
Binding::new("alt-cmd-w", CloseInactiveItems, Some("Pane")),
Binding::new("cmd-k up", Split(SplitDirection::Up), Some("Pane")),
Binding::new("cmd-k down", Split(SplitDirection::Down), Some("Pane")),
Binding::new("cmd-k left", Split(SplitDirection::Left), Some("Pane")),
Binding::new("cmd-k right", Split(SplitDirection::Right), Some("Pane")),
Binding::new("ctrl--", GoBack(None), Some("Pane")),
Binding::new("shift-ctrl-_", GoForward(None), Some("Pane")),
]);
}
pub enum Event {
@ -747,10 +738,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")
@ -816,8 +807,8 @@ impl View for Pane {
.on_navigate_mouse_down(move |direction, cx| {
let this = this.clone();
match direction {
NavigationDirection::Back => cx.dispatch_action(GoBack(Some(this))),
NavigationDirection::Forward => cx.dispatch_action(GoForward(Some(this))),
NavigationDirection::Back => cx.dispatch_action(GoBack { pane: Some(this) }),
NavigationDirection::Forward => cx.dispatch_action(GoForward { pane: Some(this) }),
}
true

View file

@ -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,7 @@ impl PaneAxis {
}
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Deserialize)]
pub enum SplitDirection {
Up,
Down,

View file

@ -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 {

View file

@ -17,9 +17,8 @@ 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},
AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Border, ClipboardItem, Entity,
ImageData, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task,
@ -32,7 +31,7 @@ pub use pane_group::*;
use postage::prelude::Stream;
use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, Worktree};
use settings::Settings;
use sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItemFocus};
use sidebar::{Side, Sidebar, ToggleSidebarItem, ToggleSidebarItemFocus};
use status_bar::StatusBar;
pub use status_bar::StatusItemView;
use std::{
@ -101,14 +100,13 @@ pub struct ToggleFollow(pub PeerId);
#[derive(Clone)]
pub struct JoinProject(pub JoinProjectParams);
impl_actions!(
impl_internal_actions!(
workspace,
[Open, OpenNew, OpenPaths, ToggleFollow, JoinProject]
);
pub fn init(client: &Arc<Client>, cx: &mut MutableAppContext) {
pane::init(cx);
menu::init(cx);
cx.add_global_action(open);
cx.add_global_action(move |action: &OpenPaths, cx: &mut MutableAppContext| {
@ -144,29 +142,6 @@ pub fn init(client: &Arc<Client>, cx: &mut MutableAppContext) {
cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
workspace.activate_next_pane(cx)
});
cx.add_bindings(vec![
Binding::new("ctrl-alt-cmd-f", FollowNextCollaborator, None),
Binding::new("cmd-s", Save, None),
Binding::new("cmd-alt-i", DebugElements, None),
Binding::new("cmd-k cmd-left", ActivatePreviousPane, None),
Binding::new("cmd-k cmd-right", ActivateNextPane, None),
Binding::new(
"cmd-shift-!",
ToggleSidebarItem(SidebarItemId {
side: Side::Left,
item_index: 0,
}),
None,
),
Binding::new(
"cmd-1",
ToggleSidebarItemFocus(SidebarItemId {
side: Side::Left,
item_index: 0,
}),
None,
),
]);
client.add_view_request_handler(Workspace::handle_follow);
client.add_view_message_handler(Workspace::handle_unfollow);
@ -630,6 +605,7 @@ pub struct WorkspaceParams {
pub client: Arc<Client>,
pub fs: Arc<dyn Fs>,
pub languages: Arc<LanguageRegistry>,
pub themes: Arc<ThemeRegistry>,
pub user_store: ModelHandle<UserStore>,
pub channel_list: ModelHandle<ChannelList>,
}
@ -659,6 +635,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 +654,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 +672,7 @@ pub struct Workspace {
user_store: ModelHandle<client::UserStore>,
remote_entity_subscription: Option<Subscription>,
fs: Arc<dyn Fs>,
themes: Arc<ThemeRegistry>,
modal: Option<AnyViewHandle>,
center: PaneGroup,
left_sidebar: Sidebar,
@ -802,6 +781,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 +814,10 @@ impl Workspace {
&self.project
}
pub fn themes(&self) -> Arc<ThemeRegistry> {
self.themes.clone()
}
pub fn worktrees<'a>(
&self,
cx: &'a AppContext,

View file

@ -29,6 +29,7 @@ test-support = [
]
[dependencies]
assets = { path = "../assets" }
breadcrumbs = { path = "../breadcrumbs" }
chat_panel = { path = "../chat_panel" }
collections = { path = "../collections" }

View file

@ -1,6 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.87348 15.1266C4.04217 12.2953 4.04217 7.70484 6.87348 4.87354M17.1265 4.87354C19.9578 7.70484 19.9578 12.2953 17.1265 15.1266" stroke="#636B78" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.9948 13.0052C7.33507 11.3454 7.33507 8.65448 8.9948 6.99475M15.0052 6.99475C16.6649 8.65448 16.6649 11.3454 15.0052 13.0052" stroke="#636B78" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.5 10C12.5 10.2761 12.2761 10.5 12 10.5C11.7239 10.5 11.5 10.2761 11.5 10C11.5 9.72386 11.7239 9.5 12 9.5C12.2761 9.5 12.5 9.72386 12.5 10Z" stroke="#636B78" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 13.75V19.25" stroke="#636B78" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 879 B

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.01234 1.86426C4.13913 1.86426 1.00077 4.41444 1.00077 7.56176C1.00077 8.86644 1.54614 10.0613 2.45007 11.0186C2.04248 12.1006 1.19361 13.0149 1.17991 13.0251C0.998442 13.2168 0.950506 13.4976 1.05323 13.7373C1.15939 13.9769 1.39392 14.1358 1.65743 14.1358C3.34203 14.1358 4.64588 13.4305 5.46764 12.8689C6.23461 13.1168 7.11663 13.2593 8.01234 13.2593C11.8855 13.2593 15 10.7083 15 7.56176C15 4.41526 11.8855 1.86426 8.01234 1.86426ZM8.01508 11.9445C7.28235 11.9445 6.56002 11.8315 5.86811 11.6125L5.24494 11.4173L4.7108 11.7939C4.32047 12.0711 3.78276 12.3796 3.13577 12.5883C3.33778 12.2563 3.52939 11.883 3.68032 11.4858L3.97122 10.7188L3.4064 10.1198C2.91252 9.5915 2.31675 8.7177 2.31675 7.56176C2.31675 5.14443 4.87104 3.17907 8.01426 3.17907C11.1575 3.17907 13.7118 5.14443 13.7118 7.56176C13.7118 9.97909 11.1569 11.9445 8.01508 11.9445Z" fill="#7E7E83"/>
</svg>

Before

Width:  |  Height:  |  Size: 979 B

View file

@ -1,3 +0,0 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 10C7.76142 10 10 7.76142 10 5C10 2.23858 7.76142 0 5 0C2.23858 0 0 2.23858 0 5C0 7.76142 2.23858 10 5 10ZM2.68306 3.56694L4.11612 5L2.68306 6.43306L3.56694 7.31694L5 5.88388L6.43306 7.31694L7.31694 6.43306L5.88388 5L7.31694 3.56694L6.43306 2.68306L5 4.11612L3.56694 2.68306L2.68306 3.56694Z" fill="#EF4444"/>
</svg>

Before

Width:  |  Height:  |  Size: 464 B

View file

@ -1,3 +0,0 @@
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.5 13C10.0899 13 13 10.0899 13 6.5C13 2.91015 10.0899 0 6.5 0C2.91015 0 0 2.91015 0 6.5C0 10.0899 2.91015 13 6.5 13ZM3.48798 4.63702L5.35095 6.5L3.48798 8.36298L4.63702 9.51202L6.5 7.64905L8.36298 9.51202L9.51202 8.36298L7.64905 6.5L9.51202 4.63702L8.36298 3.48798L6.5 5.35095L4.63702 3.48798L3.48798 4.63702Z" fill="white" fill-opacity="0.6"/>
</svg>

Before

Width:  |  Height:  |  Size: 499 B

View file

@ -1,3 +0,0 @@
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13 13H0V10.4L5.6875 0H7.3125L13 10.4V13ZM5.6875 3.46667H7.3125V7.8H5.6875V3.46667ZM5.6875 9.53333H7.3125V11.2667H5.6875V9.53333Z" fill="white" fill-opacity="0.6"/>
</svg>

Before

Width:  |  Height:  |  Size: 317 B

View file

@ -1,3 +0,0 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 10H0V8L4.375 0H5.625L10 8V10ZM4.375 2.66667H5.625V6H4.375V2.66667ZM4.375 7.33333H5.625V8.66667H4.375V7.33333Z" fill="#FDE047"/>
</svg>

Before

Width:  |  Height:  |  Size: 284 B

View file

@ -1,3 +0,0 @@
<svg width="4" height="8" viewBox="0 0 4 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.923915 0.64914L3.7699 3.67213C3.85691 3.76411 3.9004 3.88214 3.9004 4.00028C3.9004 4.11835 3.85689 4.23635 3.7699 4.32843L0.923915 7.35142C0.742536 7.54234 0.440436 7.5503 0.249113 7.36932C0.0564376 7.18784 0.0496359 6.88444 0.230468 6.69431L2.7841 3.99948L0.230468 1.30465C0.0496359 1.11452 0.0563979 0.813217 0.249113 0.630446C0.440436 0.449663 0.742536 0.457618 0.923915 0.64914Z" fill="#66686A"/>
</svg>

Before

Width:  |  Height:  |  Size: 512 B

View file

@ -1,3 +0,0 @@
<svg width="8" height="4" viewBox="0 0 8 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.35131 0.916948L4.32837 3.76288C4.23689 3.8663 4.11756 3.91005 4.00022 3.91005C3.88289 3.91005 3.76396 3.86654 3.67208 3.77955L0.649138 0.916948C0.457619 0.733981 0.449664 0.431687 0.630444 0.240765C0.812019 0.0478537 1.11531 0.0418875 1.30543 0.222866L4.00022 2.77446L6.69501 0.220877C6.88518 0.0400179 7.18723 0.0468593 7.37 0.239522C7.55019 0.431687 7.54223 0.733981 7.35131 0.916948Z" fill="#66686A"/>
</svg>

Before

Width:  |  Height:  |  Size: 516 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M3.75 1.5a.25.25 0 00-.25.25v11.5c0 .138.112.25.25.25h8.5a.25.25 0 00.25-.25V6H9.75A1.75 1.75 0 018 4.25V1.5H3.75zm5.75.56v2.19c0 .138.112.25.25.25h2.19L9.5 2.06zM2 1.75C2 .784 2.784 0 3.75 0h5.086c.464 0 .909.184 1.237.513l3.414 3.414c.329.328.513.773.513 1.237v8.086A1.75 1.75 0 0112.25 15h-8.5A1.75 1.75 0 012 13.25V1.75z"/></svg>

Before

Width:  |  Height:  |  Size: 445 B

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.2222 9.55561H11.8889L10.8156 8.89377C10.6931 8.81672 10.5302 8.77783 10.4062 8.77783H8.77778C8.3483 8.77783 8 9.12613 8 9.55561V13.4445C8 13.874 8.3483 14.2223 8.77778 14.2223H14.2222C14.6517 14.2223 15 13.874 15 13.4445V10.3334C15 9.90318 14.6524 9.55561 14.2222 9.55561ZM13.8333 13.0556H9.16667V9.9445H10.2969L11.2764 10.5487C11.4611 10.6615 11.6726 10.7223 11.8889 10.7223H13.8333V13.0556ZM6.63889 5.66672C6.96215 5.66672 7.22222 5.40665 7.22222 5.08339C7.22222 4.76012 6.96215 4.50005 6.63889 4.50005H2.16667V2.36117C2.16667 2.03887 1.90538 1.77783 1.58333 1.77783C1.26128 1.77783 1 2.03887 1 2.36117V11.3056C1 12.0567 1.60934 12.6667 2.36111 12.6667H6.63889C6.96215 12.6667 7.22222 12.4067 7.22222 12.0834C7.22222 11.7611 6.96094 11.5001 6.63889 11.5001H2.36111C2.25417 11.5001 2.16667 11.4125 2.16667 11.3056V5.66672H6.63889ZM14.2222 2.55561H11.8889L10.8156 1.89377C10.6931 1.81789 10.5302 1.77783 10.4062 1.77783H8.77778C8.3483 1.77783 8 2.12613 8 2.55561V6.4445C8 6.87398 8.3483 7.22228 8.77778 7.22228H14.2222C14.6517 7.22228 15 6.87398 15 6.4445V3.33339C15 2.90391 14.6524 2.55561 14.2222 2.55561ZM13.8333 6.05561H9.16667V2.9445H10.2969L11.2764 3.54873C11.4611 3.66224 11.6726 3.72228 11.8889 3.72228H13.8333V6.05561Z" fill="#7E7E83"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1,3 +0,0 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.0893 10.4092L8.34129 7.66113C8.93602 6.93311 9.26414 6.01641 9.26414 5.01562C9.26414 2.65928 7.35425 0.75 4.99851 0.75C2.64278 0.75 0.751343 2.65989 0.751343 5.01562C0.751343 7.37136 2.66103 9.28125 4.99851 9.28125C5.99909 9.28125 6.91702 8.93446 7.64402 8.35758L10.3921 11.1056C10.5069 11.2028 10.6341 11.25 10.7592 11.25C10.8843 11.25 11.011 11.2019 11.1072 11.1058C11.2985 10.9137 11.2985 10.602 11.0893 10.4092ZM1.73572 5.01562C1.73572 3.20643 3.20777 1.73438 5.01697 1.73438C6.82617 1.73438 8.29822 3.20643 8.29822 5.01562C8.29822 6.82482 6.82617 8.29688 5.01697 8.29688C3.20777 8.29688 1.73572 6.82441 1.73572 5.01562Z" fill="white" fill-opacity="0.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 776 B

View file

@ -1,3 +0,0 @@
<svg width="14" height="12" viewBox="0 0 14 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.14992 9.84972C1.99189 9.84972 1.04997 8.90759 1.04997 7.74977C1.04997 6.87042 1.61368 6.07944 2.45278 5.78173L3.15692 5.53193L3.15307 4.98616L2.15704 4.18643C2.12729 4.39861 2.10016 4.59111 2.1017 4.79235C0.880227 5.22546 0 6.38043 0 7.74977C0 9.48879 1.41024 10.8997 3.14992 10.8997H10.6988L9.3592 9.84972H3.14992ZM13.0569 10.0816C13.6343 9.5391 13.9996 8.77787 13.9996 7.92477C13.9996 6.58321 13.1056 5.46171 11.8855 5.09203C11.8888 5.04391 11.8997 4.99797 11.8997 4.94985C11.8997 3.59582 10.8038 2.49991 9.44976 2.49991C9.20017 2.49991 8.96436 2.54819 8.73752 2.61753C8.06948 1.70171 6.99544 1.09995 5.77485 1.09995C4.71394 1.09995 3.76678 1.55668 3.1018 2.27679L0.849166 0.51172C0.752699 0.436537 0.638515 0.399963 0.525643 0.399963C0.369897 0.399963 0.215398 0.468999 0.112194 0.600946C-0.0669139 0.82914 -0.0272556 1.15944 0.201048 1.33816L13.1509 11.4881C13.3806 11.6676 13.71 11.6265 13.8879 11.3989C14.067 11.1705 14.0273 10.8405 13.799 10.6617L13.0569 10.0816ZM12.2169 9.42317L3.92865 2.92427C4.40114 2.44916 5.05081 2.14992 5.77485 2.14992C6.61483 2.14992 7.38547 2.54585 7.88923 3.23642L8.32979 3.84016L9.04442 3.62167C10.1132 3.29487 10.8869 4.18078 10.8373 5.03039L10.7893 5.85549L11.58 6.09589C12.3984 6.34544 12.9497 7.08042 12.9497 7.92477C12.9497 8.53288 12.6609 9.0688 12.2169 9.42317Z" fill="#B3B3B3"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,3 +0,0 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 3.04688C5.00332 3.04688 4.19531 3.85488 4.19531 4.85156C4.19531 5.84824 5.00332 6.65625 6 6.65625C6.99668 6.65625 7.80469 5.84824 7.80469 4.85156C7.80469 3.85488 6.99668 3.04688 6 3.04688ZM6 5.67188C5.5476 5.67188 5.17969 5.30376 5.17969 4.85156C5.17969 4.39834 5.54678 4.03125 6 4.03125C6.45322 4.03125 6.82031 4.39916 6.82031 4.85156C6.82031 5.30479 6.45322 5.67188 6 5.67188ZM6 0.75C3.1002 0.75 0.75 3.1002 0.75 6C0.75 8.8998 3.1002 11.25 6 11.25C8.8998 11.25 11.25 8.8998 11.25 6C11.25 3.1002 8.8998 0.75 6 0.75ZM6 10.2656C5.04167 10.2656 4.15922 9.94406 3.44678 9.4086C3.80156 8.72754 4.49062 8.29688 5.26582 8.29688H6.73603C7.5102 8.29688 8.19844 8.72774 8.55466 9.4086C7.8416 9.94365 6.95771 10.2656 6 10.2656ZM9.28535 8.71729C8.73164 7.85186 7.78828 7.3125 6.73418 7.3125H5.26582C4.21254 7.3125 3.26938 7.85083 2.71465 8.71688C2.1027 7.979 1.73438 7.03154 1.73438 6C1.73438 3.64775 3.64796 1.73438 6 1.73438C8.35204 1.73438 10.2656 3.64796 10.2656 6C10.2656 7.03154 9.89648 7.979 9.28535 8.71729Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.3125 9.3125H6.6875C4.02969 9.3125 1.875 11.4672 1.875 14.125C1.875 14.6082 2.26684 15 2.75 15H13.25C13.7332 15 14.125 14.6082 14.125 14.125C14.125 11.4672 11.9703 9.3125 9.3125 9.3125ZM3.21457 13.6875C3.43059 11.9621 4.90469 10.625 6.6875 10.625H9.3125C11.0942 10.625 12.5691 11.9635 12.7852 13.6875H3.21457ZM8 8C9.93293 8 11.5 6.43293 11.5 4.5C11.5 2.56707 9.93293 1 8 1C6.06707 1 4.5 2.56707 4.5 4.5C4.5 6.4332 6.0668 8 8 8ZM8 2.3125C9.20613 2.3125 10.1875 3.29387 10.1875 4.5C10.1875 5.70613 9.20613 6.6875 8 6.6875C6.79387 6.6875 5.8125 5.70586 5.8125 4.5C5.8125 3.29387 6.79414 2.3125 8 2.3125Z" fill="#9BA8BE"/>
</svg>

Before

Width:  |  Height:  |  Size: 733 B

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.6066 13.957C16.0751 14.4242 16.0751 15.1823 15.6066 15.6496C15.1381 16.1168 14.378 16.1168 13.9094 15.6496L8.00082 9.71303L2.0502 15.6476C1.5817 16.1148 0.821573 16.1148 0.353024 15.6476C-0.115524 15.1803 -0.115474 14.4223 0.353024 13.955L6.30564 8.02244L0.351374 2.04303C-0.117125 1.5758 -0.117125 0.817724 0.351374 0.350443C0.819872 -0.116839 1.58 -0.116789 2.04855 0.350443L8.00082 6.33185L13.9514 0.39732C14.4199 -0.0699117 15.1801 -0.0699117 15.6486 0.39732C16.1172 0.864552 16.1171 1.62263 15.6486 2.08991L9.69599 8.02244L15.6066 13.957Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 676 B

View file

@ -1,3 +0,0 @@
<svg width="8" height="12" viewBox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.00262 12L2.89358 7.9886C2.95207 7.71862 2.77658 7.49963 2.5021 7.49963H0.000671387L6.00037 0L5.10792 4.0108C5.04792 4.27929 5.22341 4.49828 5.4994 4.49828H7.99932L1.99962 11.9979L2.00262 12Z" fill="#FDE047"/>
</svg>

Before

Width:  |  Height:  |  Size: 322 B

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,7 @@
#![allow(non_snake_case)]
use anyhow::{anyhow, Context, Result};
use assets::Assets;
use client::{self, http, ChannelList, UserStore};
use fs::OpenOptions;
use futures::{channel::oneshot, StreamExt};
@ -9,19 +10,17 @@ use gpui::{App, AssetSource, Task};
use log::LevelFilter;
use parking_lot::Mutex;
use project::Fs;
use settings::{self, Settings};
use settings::{self, KeymapFile, Settings, SettingsFileContent};
use smol::process::Command;
use std::{env, fs, path::PathBuf, sync::Arc};
use theme::{ThemeRegistry, DEFAULT_THEME_NAME};
use util::ResultExt;
use workspace::{self, AppState, OpenNew, OpenPaths};
use zed::{
self,
assets::Assets,
build_window_options, build_workspace,
self, build_window_options, build_workspace,
fs::RealFs,
languages, menus,
settings_file::{settings_from_files, SettingsFile},
settings_file::{settings_from_files, watch_keymap_file, WatchedJsonFile},
};
fn main() {
@ -63,7 +62,8 @@ fn main() {
..Default::default()
},
);
let settings_file = load_settings_file(&app, fs.clone());
let config_files = load_config_files(&app, fs.clone());
let login_shell_env_loaded = if stdout_is_a_pty() {
Task::ready(())
@ -112,13 +112,16 @@ fn main() {
})
.detach_and_log_err(cx);
let settings_file = cx.background().block(settings_file).unwrap();
let (settings_file, keymap_file) = cx.background().block(config_files).unwrap();
let mut settings_rx = settings_from_files(
default_settings,
vec![settings_file],
themes.clone(),
cx.font_cache().clone(),
);
cx.spawn(|cx| watch_keymap_file(keymap_file, cx)).detach();
let settings = cx.background().block(settings_rx.next()).unwrap();
cx.spawn(|mut cx| async move {
while let Some(settings) = settings_rx.next().await {
@ -145,8 +148,8 @@ fn main() {
build_workspace: &build_workspace,
});
journal::init(app_state.clone(), cx);
theme_selector::init(cx);
zed::init(&app_state, cx);
theme_selector::init(app_state.themes.clone(), cx);
cx.set_menus(menus::menus(&app_state.clone()));
@ -254,14 +257,22 @@ fn load_embedded_fonts(app: &App) {
.unwrap();
}
fn load_settings_file(app: &App, fs: Arc<dyn Fs>) -> oneshot::Receiver<SettingsFile> {
fn load_config_files(
app: &App,
fs: Arc<dyn Fs>,
) -> oneshot::Receiver<(
WatchedJsonFile<SettingsFileContent>,
WatchedJsonFile<KeymapFile>,
)> {
let executor = app.background();
let (tx, rx) = oneshot::channel();
executor
.clone()
.spawn(async move {
let file = SettingsFile::new(fs, &executor, zed::SETTINGS_PATH.clone()).await;
tx.send(file).ok()
let settings_file =
WatchedJsonFile::new(fs.clone(), &executor, zed::SETTINGS_PATH.clone()).await;
let keymap_file = WatchedJsonFile::new(fs, &executor, zed::KEYMAP_PATH.clone()).await;
tx.send((settings_file, keymap_file)).ok()
})
.detach();
rx

View file

@ -1,17 +1,21 @@
use futures::{stream, StreamExt};
use gpui::{executor, FontCache};
use gpui::{executor, AsyncAppContext, FontCache};
use postage::sink::Sink as _;
use postage::{prelude::Stream, watch};
use project::Fs;
use settings::{Settings, SettingsFileContent};
use serde::Deserialize;
use settings::{KeymapFile, Settings, SettingsFileContent};
use std::{path::Path, sync::Arc, time::Duration};
use theme::ThemeRegistry;
use util::ResultExt;
#[derive(Clone)]
pub struct SettingsFile(watch::Receiver<SettingsFileContent>);
pub struct WatchedJsonFile<T>(watch::Receiver<T>);
impl SettingsFile {
impl<T> WatchedJsonFile<T>
where
T: 'static + for<'de> Deserialize<'de> + Clone + Default + Send + Sync,
{
pub async fn new(
fs: Arc<dyn Fs>,
executor: &executor::Background,
@ -35,21 +39,21 @@ impl SettingsFile {
Self(rx)
}
async fn load(fs: Arc<dyn Fs>, path: &Path) -> Option<SettingsFileContent> {
async fn load(fs: Arc<dyn Fs>, path: &Path) -> Option<T> {
if fs.is_file(&path).await {
fs.load(&path)
.await
.log_err()
.and_then(|data| serde_json::from_str(&data).log_err())
} else {
Some(SettingsFileContent::default())
Some(T::default())
}
}
}
pub fn settings_from_files(
defaults: Settings,
sources: Vec<SettingsFile>,
sources: Vec<WatchedJsonFile<SettingsFileContent>>,
theme_registry: Arc<ThemeRegistry>,
font_cache: Arc<FontCache>,
) -> impl futures::stream::Stream<Item = Settings> {
@ -72,6 +76,16 @@ pub fn settings_from_files(
})
}
pub async fn watch_keymap_file(mut file: WatchedJsonFile<KeymapFile>, mut cx: AsyncAppContext) {
while let Some(content) = file.0.recv().await {
cx.update(|cx| {
cx.clear_bindings();
settings::KeymapFile::load_defaults(cx);
content.add(cx).log_err();
});
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -102,9 +116,9 @@ mod tests {
.await
.unwrap();
let source1 = SettingsFile::new(fs.clone(), &executor, "/settings1.json".as_ref()).await;
let source2 = SettingsFile::new(fs.clone(), &executor, "/settings2.json".as_ref()).await;
let source3 = SettingsFile::new(fs.clone(), &executor, "/settings3.json".as_ref()).await;
let source1 = WatchedJsonFile::new(fs.clone(), &executor, "/settings1.json".as_ref()).await;
let source2 = WatchedJsonFile::new(fs.clone(), &executor, "/settings2.json".as_ref()).await;
let source3 = WatchedJsonFile::new(fs.clone(), &executor, "/settings3.json".as_ref()).await;
let mut settings_rx = settings_from_files(
cx.read(Settings::test),

View file

@ -1,4 +1,5 @@
use crate::{assets::Assets, build_window_options, build_workspace, AppState};
use crate::{build_window_options, build_workspace, AppState};
use assets::Assets;
use client::{test::FakeHttpClient, ChannelList, Client, UserStore};
use gpui::MutableAppContext;
use language::LanguageRegistry;

View file

@ -1,4 +1,3 @@
pub mod assets;
pub mod languages;
pub mod menus;
pub mod settings_file;
@ -14,8 +13,6 @@ pub use editor;
use gpui::{
actions,
geometry::vector::vec2f,
impl_actions,
keymap::Binding,
platform::{WindowBounds, WindowOptions},
ModelHandle, ViewContext,
};
@ -30,12 +27,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;
@ -44,20 +45,23 @@ lazy_static! {
.expect("failed to determine home directory")
.join(".zed");
pub static ref SETTINGS_PATH: PathBuf = ROOT_PATH.join("settings.json");
pub static ref KEYMAP_PATH: PathBuf = ROOT_PATH.join("keymap.json");
}
pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
cx.add_global_action(quit);
cx.add_global_action({
move |action: &AdjustBufferFontSize, cx| {
cx.update_global::<Settings, _, _>(|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, _, _>(|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, _, _>(|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<Workspace>| {
@ -99,11 +103,7 @@ pub fn init(app_state: &Arc<AppState>, 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-,", OpenSettings, None),
])
settings::KeymapFile::load_defaults(cx);
}
pub fn build_workspace(
@ -134,6 +134,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(),
};
@ -205,9 +206,8 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) {
#[cfg(test)]
mod tests {
use crate::assets::Assets;
use super::*;
use assets::Assets;
use editor::{DisplayPoint, Editor};
use gpui::{AssetSource, MutableAppContext, TestAppContext, ViewHandle};
use project::{Fs, ProjectPath};