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 authored and GitHub committed 2022-04-11 17:31:22 -07:00
commit 25e1e3d2df
74 files changed
+1091 -728

No files matched your search

Generated
+16
View File
@@ -168,6 +168,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109"
[[package]]
name = "assets"
version = "0.1.0"
dependencies = [
"anyhow",
"gpui",
"rust-embed",
]
[[package]]
name = "async-attributes"
version = "1.1.2"
@@ -4300,6 +4309,7 @@ dependencies = [
"log",
"postage",
"project",
"serde",
"serde_json",
"settings",
"theme",
@@ -4464,6 +4474,8 @@ name = "settings"
version = "0.1.0"
dependencies = [
"anyhow",
"assets",
"collections",
"gpui",
"schemars",
"serde",
@@ -5787,6 +5799,7 @@ checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
name = "vim"
version = "0.1.0"
dependencies = [
"assets",
"collections",
"editor",
"gpui",
@@ -5794,6 +5807,7 @@ dependencies = [
"language",
"log",
"project",
"serde",
"settings",
"util",
"workspace",
@@ -6061,6 +6075,7 @@ name = "zed"
version = "0.25.0"
dependencies = [
"anyhow",
"assets",
"async-compression",
"async-recursion",
"async-trait",
@@ -6181,6 +6196,7 @@ dependencies = [
"sha-1 0.9.6",
"sqlx 0.5.5",
"surf",
"theme",
"tide",
"tide-compress",
"time 0.2.27",
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+259
View File
@@ -0,0 +1,259 @@
{
"*": {
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
"cmd-s": "workspace::Save",
"cmd-alt-i": "workspace::DebugElements",
"cmd-k cmd-left": "workspace::ActivatePreviousPane",
"cmd-k cmd-right": "workspace::ActivateNextPane",
"cmd-=": "zed::IncreaseBufferFontSize",
"cmd--": "zed::DecreaseBufferFontSize",
"cmd-,": "zed::OpenSettings"
},
"menu": {
"up": "menu::SelectPrev",
"ctrl-p": "menu::SelectPrev",
"down": "menu::SelectNext",
"ctrl-n": "menu::SelectNext",
"cmd-up": "menu::SelectFirst",
"cmd-down": "menu::SelectLast",
"enter": "menu::Confirm"
},
"Pane": {
"shift-cmd-{": "pane::ActivatePrevItem",
"shift-cmd-}": "pane::ActivateNextItem",
"cmd-w": "pane::CloseActiveItem",
"alt-cmd-w": "pane::CloseInactiveItems",
"ctrl--": "pane::GoBack",
"shift-ctrl-_": "pane::GoForward",
"cmd-k up": [
"pane::Split",
"Up"
],
"cmd-k down": [
"pane::Split",
"Down"
],
"cmd-k left": [
"pane::Split",
"Left"
],
"cmd-k right": [
"pane::Split",
"Right"
],
"cmd-shift-F": "project_search::ToggleFocus",
"cmd-f": "project_search::ToggleFocus",
"cmd-g": "search::SelectNextMatch",
"cmd-shift-G": "search::SelectPrevMatch"
},
"Workspace": {
"cmd-shift-F": "project_search::Deploy",
"cmd-k cmd-t": "theme_selector::Toggle",
"cmd-k t": "theme_selector::Reload",
"cmd-t": "project_symbols::Toggle",
"cmd-p": "file_finder::Toggle",
"alt-shift-D": "diagnostics::Deploy",
"ctrl-alt-cmd-j": "journal::NewJournalEntry"
},
"ProjectSearchBar": {
"enter": "project_search::Search",
"cmd-enter": "project_search::SearchInNew"
},
"BufferSearchBar": {
"escape": "buffer_search::Dismiss",
"cmd-f": "buffer_search::FocusEditor",
"enter": "search::SelectNextMatch",
"shift-enter": "search::SelectPrevMatch"
},
"Editor": {
"escape": "editor::Cancel",
"backspace": "editor::Backspace",
"ctrl-h": "editor::Backspace",
"delete": "editor::Delete",
"ctrl-d": "editor::Delete",
"tab": "editor::Tab",
"shift-tab": "editor::TabPrev",
"cmd-[": "editor::Outdent",
"cmd-]": "editor::Indent",
"ctrl-shift-K": "editor::DeleteLine",
"alt-backspace": "editor::DeleteToPreviousWordStart",
"alt-h": "editor::DeleteToPreviousWordStart",
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
"alt-delete": "editor::DeleteToNextWordEnd",
"alt-d": "editor::DeleteToNextWordEnd",
"ctrl-alt-delete": "editor::DeleteToNextSubwordEnd",
"ctrl-alt-d": "editor::DeleteToNextSubwordEnd",
"cmd-backspace": "editor::DeleteToBeginningOfLine",
"cmd-delete": "editor::DeleteToEndOfLine",
"ctrl-k": "editor::CutToEndOfLine",
"cmd-shift-D": "editor::DuplicateLine",
"ctrl-cmd-up": "editor::MoveLineUp",
"ctrl-cmd-down": "editor::MoveLineDown",
"cmd-x": "editor::Cut",
"cmd-c": "editor::Copy",
"cmd-v": "editor::Paste",
"cmd-z": "editor::Undo",
"cmd-shift-Z": "editor::Redo",
"up": "editor::MoveUp",
"down": "editor::MoveDown",
"left": "editor::MoveLeft",
"right": "editor::MoveRight",
"ctrl-p": "editor::MoveUp",
"ctrl-n": "editor::MoveDown",
"ctrl-b": "editor::MoveLeft",
"ctrl-f": "editor::MoveRight",
"alt-left": "editor::MoveToPreviousWordStart",
"alt-b": "editor::MoveToPreviousWordStart",
"ctrl-alt-left": "editor::MoveToPreviousSubwordStart",
"ctrl-alt-b": "editor::MoveToPreviousSubwordStart",
"alt-right": "editor::MoveToNextWordEnd",
"alt-f": "editor::MoveToNextWordEnd",
"ctrl-alt-right": "editor::MoveToNextSubwordEnd",
"ctrl-alt-f": "editor::MoveToNextSubwordEnd",
"cmd-left": "editor::MoveToBeginningOfLine",
"ctrl-a": "editor::MoveToBeginningOfLine",
"cmd-right": "editor::MoveToEndOfLine",
"ctrl-e": "editor::MoveToEndOfLine",
"cmd-up": "editor::MoveToBeginning",
"cmd-down": "editor::MoveToEnd",
"shift-up": "editor::SelectUp",
"ctrl-shift-P": "editor::SelectUp",
"shift-down": "editor::SelectDown",
"ctrl-shift-N": "editor::SelectDown",
"shift-left": "editor::SelectLeft",
"ctrl-shift-B": "editor::SelectLeft",
"shift-right": "editor::SelectRight",
"ctrl-shift-F": "editor::SelectRight",
"alt-shift-left": "editor::SelectToPreviousWordStart",
"alt-shift-B": "editor::SelectToPreviousWordStart",
"ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart",
"ctrl-alt-shift-B": "editor::SelectToPreviousSubwordStart",
"alt-shift-right": "editor::SelectToNextWordEnd",
"alt-shift-F": "editor::SelectToNextWordEnd",
"ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd",
"cmd-shift-up": "editor::SelectToBeginning",
"cmd-shift-down": "editor::SelectToEnd",
"cmd-a": "editor::SelectAll",
"cmd-l": "editor::SelectLine",
"cmd-shift-L": "editor::SplitSelectionIntoLines",
"cmd-alt-up": "editor::AddSelectionAbove",
"cmd-ctrl-p": "editor::AddSelectionAbove",
"cmd-alt-down": "editor::AddSelectionBelow",
"cmd-ctrl-n": "editor::AddSelectionBelow",
"ctrl-alt-shift-F": "editor::SelectToNextSubwordEnd",
"cmd-shift-left": [
"editor::SelectToBeginningOfLine",
{
"stop_at_soft_wraps": true
}
],
"ctrl-shift-A": [
"editor::SelectToBeginningOfLine",
{
"stop_at_soft_wraps": true
}
],
"cmd-shift-right": [
"editor::SelectToEndOfLine",
{
"stop_at_soft_wraps": true
}
],
"ctrl-shift-E": [
"editor::SelectToEndOfLine",
{
"stop_at_soft_wraps": true
}
],
"cmd-d": [
"editor::SelectNext",
{
"replace_newest": false
}
],
"cmd-k cmd-d": [
"editor::SelectNext",
{
"replace_newest": true
}
],
"cmd-/": "editor::ToggleComments",
"alt-up": "editor::SelectLargerSyntaxNode",
"ctrl-w": "editor::SelectLargerSyntaxNode",
"alt-down": "editor::SelectSmallerSyntaxNode",
"ctrl-shift-W": "editor::SelectSmallerSyntaxNode",
"cmd-u": "editor::UndoSelection",
"cmd-shift-U": "editor::RedoSelection",
"f8": "editor::GoToNextDiagnostic",
"shift-f8": "editor::GoToPrevDiagnostic",
"f2": "editor::Rename",
"f12": "editor::GoToDefinition",
"alt-shift-f12": "editor::FindAllReferences",
"ctrl-m": "editor::MoveToEnclosingBracket",
"pageup": "editor::PageUp",
"pagedown": "editor::PageDown",
"alt-cmd-[": "editor::Fold",
"alt-cmd-]": "editor::UnfoldLines",
"alt-cmd-f": "editor::FoldSelectedRanges",
"ctrl-space": "editor::ShowCompletions",
"cmd-.": "editor::ToggleCodeActions",
"alt-enter": "editor::OpenExcerpts",
"cmd-f10": "editor::RestartLanguageServer"
},
"Editor && renaming": {
"enter": "editor::ConfirmRename"
},
"Editor && showing_completions": {
"enter": "editor::ConfirmCompletion"
},
"Editor && showing_code_actions": {
"enter": "editor::ConfirmCodeAction"
},
"Editor && mode == full": {
"enter": "editor::Newline",
"cmd-f": [
"buffer_search::Deploy",
{
"focus": true
}
],
"cmd-e": [
"buffer_search::Deploy",
{
"focus": false
}
],
"cmd-shift-O": "outline::Toggle",
"ctrl-g": "go_to_line::Toggle"
},
"Editor && mode == auto_height": {
"alt-enter": [
"editor::Input",
"\n"
]
},
"OutlineView": {
"escape": "outline::Toggle"
},
"ProjectSymbolsView": {
"escape": "project_symbols::Toggle"
},
"ThemeSelector": {
"escape": "theme_selector::Toggle"
},
"GoToLine": {
"escape": "go_to_line::Toggle",
"enter": "go_to_line::Confirm"
},
"FileFinder": {
"escape": "file_finder::Toggle"
},
"ChatPanel": {
"enter": "chat_panel::Send"
},
"ProjectPanel": {
"left": "project_panel::CollapseSelectedEntry",
"right": "project_panel::ExpandSelectedEntry"
}
}
+58
View File
@@ -0,0 +1,58 @@
{
"Editor && vim_mode == insert": {
"escape": "vim::NormalBefore",
"ctrl-c": "vim::NormalBefore"
},
"Editor && vim_mode == normal && vim_submode == g": {
"g": "vim::MoveToStart",
"escape": [
"vim::SwitchMode",
{
"Normal": "None"
}
]
},
"Editor && vim_mode == normal": {
"i": [
"vim::SwitchMode",
"Insert"
],
"g": [
"vim::SwitchMode",
{
"Normal": "GPrefix"
}
],
"h": "vim::MoveLeft",
"j": "vim::MoveDown",
"k": "vim::MoveUp",
"l": "vim::MoveRight",
"0": "vim::MoveToStartOfLine",
"shift-$": "vim::MoveToEndOfLine",
"shift-G": "vim::MoveToEnd",
"w": [
"vim::MoveToNextWordStart",
false
],
"shift-W": [
"vim::MoveToNextWordStart",
true
],
"e": [
"vim::MoveToNextWordEnd",
false
],
"shift-E": [
"vim::MoveToNextWordEnd",
true
],
"b": [
"vim::MoveToPreviousWordStart",
false
],
"shift-B": [
"vim::MoveToPreviousWordStart",
true
]
}
}
File renamed without changes.
File renamed without changes.
+14
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"] }
@@ -3,7 +3,7 @@ use gpui::AssetSource;
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
#[folder = "assets"]
#[folder = "../../assets"]
#[exclude = "*.DS_Store"]
pub struct Assets;
-3
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 {
+2 -4
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);
}
+147 -284
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),
+3 -12
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 {
+2 -7
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);
}
+112 -42
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,
+84 -31
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
}
};
}
+3
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;
+58 -39
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
}
+3 -1
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]);
+1 -2
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));
}
+2 -10
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);
+2 -7
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 {
@@ -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);
+1
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"] }
+60 -44
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)]
+24 -24
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()
}
+3 -5
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 {
+1
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"
+10 -3
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)
}),
+2
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" }
+62
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(())
}
}
+4
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,
+11 -24
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);
+2
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"] }
+1 -7
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);
}
+3 -2
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,
+6 -24
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);
+1 -7
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);
}
+2 -1
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]);
+3
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| {
+1 -1
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" }
+2 -16
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")),
]);
}
+30 -39
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
+2 -1
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,
+4 -2
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 {
+12 -28
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,
+1
View File
@@ -29,6 +29,7 @@ test-support = [
]
[dependencies]
assets = { path = "../assets" }
breadcrumbs = { path = "../breadcrumbs" }
chat_panel = { path = "../chat_panel" }
collections = { path = "../collections" }
+22 -11
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
+24 -10
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),
+2 -1
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;
+25 -25
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};
+1 -1
View File
@@ -10,7 +10,7 @@ for (let theme of themes) {
let styleTree = snakeCase(app(theme));
let styleTreeJSON = JSON.stringify(styleTree, null, 2);
let outPath = path.resolve(
`${__dirname}/../../crates/zed/assets/themes/${theme.name}.json`
`${__dirname}/../assets/themes/${theme.name}.json`
);
fs.writeFileSync(outPath, styleTreeJSON);
console.log(`- ${outPath} created`);