Restructure KeyMap file, make it easy to edit in Zed

Add a JSON schema for this file so that autocomplete can be used for the actions.
This commit is contained in:
Max Brunsfeld 2022-04-21 13:33:39 -07:00
parent f52050a9ec
commit 066b4faf61
6 changed files with 610 additions and 466 deletions

View file

@ -1,264 +1,310 @@
{ [
"*": { {
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator", "bindings": {
"cmd-s": "workspace::Save", "ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
"cmd-alt-i": "zed::DebugElements", "cmd-s": "workspace::Save",
"cmd-k cmd-left": "workspace::ActivatePreviousPane", "cmd-alt-i": "zed::DebugElements",
"cmd-k cmd-right": "workspace::ActivateNextPane", "cmd-k cmd-left": "workspace::ActivatePreviousPane",
"cmd-=": "zed::IncreaseBufferFontSize", "cmd-k cmd-right": "workspace::ActivateNextPane",
"cmd--": "zed::DecreaseBufferFontSize", "cmd-=": "zed::IncreaseBufferFontSize",
"cmd-,": "zed::OpenSettings" "cmd--": "zed::DecreaseBufferFontSize",
"cmd-,": "zed::OpenSettings",
"alt-cmd-,": "zed::OpenKeymap"
}
}, },
"menu": { {
"up": "menu::SelectPrev", "context": "menu",
"ctrl-p": "menu::SelectPrev", "bindings": {
"down": "menu::SelectNext", "up": "menu::SelectPrev",
"ctrl-n": "menu::SelectNext", "ctrl-p": "menu::SelectPrev",
"cmd-up": "menu::SelectFirst", "down": "menu::SelectNext",
"cmd-down": "menu::SelectLast", "ctrl-n": "menu::SelectNext",
"enter": "menu::Confirm", "cmd-up": "menu::SelectFirst",
"escape": "menu::Cancel" "cmd-down": "menu::SelectLast",
"enter": "menu::Confirm",
"escape": "menu::Cancel",
"ctrl-c": "menu::Cancel"
}
}, },
"Pane": { {
"shift-cmd-{": "pane::ActivatePrevItem", "context": "Pane",
"shift-cmd-}": "pane::ActivateNextItem", "bindings": {
"cmd-w": "pane::CloseActiveItem", "shift-cmd-{": "pane::ActivatePrevItem",
"alt-cmd-w": "pane::CloseInactiveItems", "shift-cmd-}": "pane::ActivateNextItem",
"ctrl--": "pane::GoBack", "cmd-w": "pane::CloseActiveItem",
"shift-ctrl-_": "pane::GoForward", "alt-cmd-w": "pane::CloseInactiveItems",
"cmd-k up": [ "ctrl--": "pane::GoBack",
"pane::Split", "shift-ctrl-_": "pane::GoForward",
"Up" "cmd-k up": [
], "pane::Split",
"cmd-k down": [ "Up"
"pane::Split", ],
"Down" "cmd-k down": [
], "pane::Split",
"cmd-k left": [ "Down"
"pane::Split", ],
"Left" "cmd-k left": [
], "pane::Split",
"cmd-k right": [ "Left"
"pane::Split", ],
"Right" "cmd-k right": [
], "pane::Split",
"cmd-shift-F": "project_search::ToggleFocus", "Right"
"cmd-f": "project_search::ToggleFocus", ],
"cmd-g": "search::SelectNextMatch", "cmd-shift-F": "project_search::ToggleFocus",
"cmd-shift-G": "search::SelectPrevMatch" "cmd-f": "project_search::ToggleFocus",
"cmd-g": "search::SelectNextMatch",
"cmd-shift-G": "search::SelectPrevMatch"
}
}, },
"Workspace": { {
"cmd-shift-F": "project_search::Deploy", "context": "Workspace",
"cmd-k cmd-t": "theme_selector::Toggle", "bindings": {
"cmd-k t": "theme_selector::Reload", "cmd-shift-F": "project_search::Deploy",
"cmd-t": "project_symbols::Toggle", "cmd-k cmd-t": "theme_selector::Toggle",
"cmd-p": "file_finder::Toggle", "cmd-k t": "theme_selector::Reload",
"cmd-shift-P": "command_palette::Toggle", "cmd-t": "project_symbols::Toggle",
"alt-shift-D": "diagnostics::Deploy", "cmd-p": "file_finder::Toggle",
"ctrl-alt-cmd-j": "journal::NewJournalEntry", "cmd-shift-P": "command_palette::Toggle",
"cmd-1": [ "alt-shift-D": "diagnostics::Deploy",
"workspace::ToggleSidebarItemFocus", "ctrl-alt-cmd-j": "journal::NewJournalEntry",
{ "cmd-1": [
"side": "Left", "workspace::ToggleSidebarItemFocus",
"item_index": 0 {
} "side": "Left",
], "item_index": 0
"cmd-shift-!": [ }
"workspace::ToggleSidebarItem", ],
{ "cmd-shift-!": [
"side": "Left", "workspace::ToggleSidebarItem",
"item_index": 0 {
} "side": "Left",
] "item_index": 0
}
]
}
}, },
"ProjectSearchBar": { {
"enter": "project_search::Search", "context": "ProjectSearchBar",
"cmd-enter": "project_search::SearchInNew" "bindings": {
"enter": "project_search::Search",
"cmd-enter": "project_search::SearchInNew"
}
}, },
"BufferSearchBar": { {
"escape": "buffer_search::Dismiss", "context": "BufferSearchBar",
"cmd-f": "buffer_search::FocusEditor", "bindings": {
"enter": "search::SelectNextMatch", "escape": "buffer_search::Dismiss",
"shift-enter": "search::SelectPrevMatch" "cmd-f": "buffer_search::FocusEditor",
"enter": "search::SelectNextMatch",
"shift-enter": "search::SelectPrevMatch"
}
}, },
"Editor": { {
"escape": "editor::Cancel", "context": "Editor",
"backspace": "editor::Backspace", "bindings": {
"ctrl-h": "editor::Backspace", "escape": "editor::Cancel",
"delete": "editor::Delete", "backspace": "editor::Backspace",
"ctrl-d": "editor::Delete", "ctrl-h": "editor::Backspace",
"tab": "editor::Tab", "delete": "editor::Delete",
"shift-tab": "editor::TabPrev", "ctrl-d": "editor::Delete",
"cmd-[": "editor::Outdent", "tab": "editor::Tab",
"cmd-]": "editor::Indent", "shift-tab": "editor::TabPrev",
"ctrl-shift-K": "editor::DeleteLine", "cmd-[": "editor::Outdent",
"alt-backspace": "editor::DeleteToPreviousWordStart", "cmd-]": "editor::Indent",
"alt-h": "editor::DeleteToPreviousWordStart", "ctrl-shift-K": "editor::DeleteLine",
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", "alt-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", "alt-h": "editor::DeleteToPreviousWordStart",
"alt-delete": "editor::DeleteToNextWordEnd", "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
"alt-d": "editor::DeleteToNextWordEnd", "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
"ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", "alt-delete": "editor::DeleteToNextWordEnd",
"ctrl-alt-d": "editor::DeleteToNextSubwordEnd", "alt-d": "editor::DeleteToNextWordEnd",
"cmd-backspace": "editor::DeleteToBeginningOfLine", "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd",
"cmd-delete": "editor::DeleteToEndOfLine", "ctrl-alt-d": "editor::DeleteToNextSubwordEnd",
"ctrl-k": "editor::CutToEndOfLine", "cmd-backspace": "editor::DeleteToBeginningOfLine",
"cmd-shift-D": "editor::DuplicateLine", "cmd-delete": "editor::DeleteToEndOfLine",
"ctrl-cmd-up": "editor::MoveLineUp", "ctrl-k": "editor::CutToEndOfLine",
"ctrl-cmd-down": "editor::MoveLineDown", "cmd-shift-D": "editor::DuplicateLine",
"cmd-x": "editor::Cut", "ctrl-cmd-up": "editor::MoveLineUp",
"cmd-c": "editor::Copy", "ctrl-cmd-down": "editor::MoveLineDown",
"cmd-v": "editor::Paste", "cmd-x": "editor::Cut",
"cmd-z": "editor::Undo", "cmd-c": "editor::Copy",
"cmd-shift-Z": "editor::Redo", "cmd-v": "editor::Paste",
"up": "editor::MoveUp", "cmd-z": "editor::Undo",
"down": "editor::MoveDown", "cmd-shift-Z": "editor::Redo",
"left": "editor::MoveLeft", "up": "editor::MoveUp",
"right": "editor::MoveRight", "down": "editor::MoveDown",
"ctrl-p": "editor::MoveUp", "left": "editor::MoveLeft",
"ctrl-n": "editor::MoveDown", "right": "editor::MoveRight",
"ctrl-b": "editor::MoveLeft", "ctrl-p": "editor::MoveUp",
"ctrl-f": "editor::MoveRight", "ctrl-n": "editor::MoveDown",
"alt-left": "editor::MoveToPreviousWordStart", "ctrl-b": "editor::MoveLeft",
"alt-b": "editor::MoveToPreviousWordStart", "ctrl-f": "editor::MoveRight",
"ctrl-alt-left": "editor::MoveToPreviousSubwordStart", "alt-left": "editor::MoveToPreviousWordStart",
"ctrl-alt-b": "editor::MoveToPreviousSubwordStart", "alt-b": "editor::MoveToPreviousWordStart",
"alt-right": "editor::MoveToNextWordEnd", "ctrl-alt-left": "editor::MoveToPreviousSubwordStart",
"alt-f": "editor::MoveToNextWordEnd", "ctrl-alt-b": "editor::MoveToPreviousSubwordStart",
"ctrl-alt-right": "editor::MoveToNextSubwordEnd", "alt-right": "editor::MoveToNextWordEnd",
"ctrl-alt-f": "editor::MoveToNextSubwordEnd", "alt-f": "editor::MoveToNextWordEnd",
"cmd-left": "editor::MoveToBeginningOfLine", "ctrl-alt-right": "editor::MoveToNextSubwordEnd",
"ctrl-a": "editor::MoveToBeginningOfLine", "ctrl-alt-f": "editor::MoveToNextSubwordEnd",
"cmd-right": "editor::MoveToEndOfLine", "cmd-left": "editor::MoveToBeginningOfLine",
"ctrl-e": "editor::MoveToEndOfLine", "ctrl-a": "editor::MoveToBeginningOfLine",
"cmd-up": "editor::MoveToBeginning", "cmd-right": "editor::MoveToEndOfLine",
"cmd-down": "editor::MoveToEnd", "ctrl-e": "editor::MoveToEndOfLine",
"shift-up": "editor::SelectUp", "cmd-up": "editor::MoveToBeginning",
"ctrl-shift-P": "editor::SelectUp", "cmd-down": "editor::MoveToEnd",
"shift-down": "editor::SelectDown", "shift-up": "editor::SelectUp",
"ctrl-shift-N": "editor::SelectDown", "ctrl-shift-P": "editor::SelectUp",
"shift-left": "editor::SelectLeft", "shift-down": "editor::SelectDown",
"ctrl-shift-B": "editor::SelectLeft", "ctrl-shift-N": "editor::SelectDown",
"shift-right": "editor::SelectRight", "shift-left": "editor::SelectLeft",
"ctrl-shift-F": "editor::SelectRight", "ctrl-shift-B": "editor::SelectLeft",
"alt-shift-left": "editor::SelectToPreviousWordStart", "shift-right": "editor::SelectRight",
"alt-shift-B": "editor::SelectToPreviousWordStart", "ctrl-shift-F": "editor::SelectRight",
"ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", "alt-shift-left": "editor::SelectToPreviousWordStart",
"ctrl-alt-shift-B": "editor::SelectToPreviousSubwordStart", "alt-shift-B": "editor::SelectToPreviousWordStart",
"alt-shift-right": "editor::SelectToNextWordEnd", "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart",
"alt-shift-F": "editor::SelectToNextWordEnd", "ctrl-alt-shift-B": "editor::SelectToPreviousSubwordStart",
"ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", "alt-shift-right": "editor::SelectToNextWordEnd",
"cmd-shift-up": "editor::SelectToBeginning", "alt-shift-F": "editor::SelectToNextWordEnd",
"cmd-shift-down": "editor::SelectToEnd", "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd",
"cmd-a": "editor::SelectAll", "cmd-shift-up": "editor::SelectToBeginning",
"cmd-l": "editor::SelectLine", "cmd-shift-down": "editor::SelectToEnd",
"cmd-shift-L": "editor::SplitSelectionIntoLines", "cmd-a": "editor::SelectAll",
"cmd-alt-up": "editor::AddSelectionAbove", "cmd-l": "editor::SelectLine",
"cmd-ctrl-p": "editor::AddSelectionAbove", "cmd-shift-L": "editor::SplitSelectionIntoLines",
"cmd-alt-down": "editor::AddSelectionBelow", "cmd-alt-up": "editor::AddSelectionAbove",
"cmd-ctrl-n": "editor::AddSelectionBelow", "cmd-ctrl-p": "editor::AddSelectionAbove",
"ctrl-alt-shift-F": "editor::SelectToNextSubwordEnd", "cmd-alt-down": "editor::AddSelectionBelow",
"cmd-shift-left": [ "cmd-ctrl-n": "editor::AddSelectionBelow",
"editor::SelectToBeginningOfLine", "ctrl-alt-shift-F": "editor::SelectToNextSubwordEnd",
{ "cmd-shift-left": [
"stop_at_soft_wraps": true "editor::SelectToBeginningOfLine",
} {
], "stop_at_soft_wraps": true
"ctrl-shift-A": [ }
"editor::SelectToBeginningOfLine", ],
{ "ctrl-shift-A": [
"stop_at_soft_wraps": true "editor::SelectToBeginningOfLine",
} {
], "stop_at_soft_wraps": true
"cmd-shift-right": [ }
"editor::SelectToEndOfLine", ],
{ "cmd-shift-right": [
"stop_at_soft_wraps": true "editor::SelectToEndOfLine",
} {
], "stop_at_soft_wraps": true
"ctrl-shift-E": [ }
"editor::SelectToEndOfLine", ],
{ "ctrl-shift-E": [
"stop_at_soft_wraps": true "editor::SelectToEndOfLine",
} {
], "stop_at_soft_wraps": true
"cmd-d": [ }
"editor::SelectNext", ],
{ "cmd-d": [
"replace_newest": false "editor::SelectNext",
} {
], "replace_newest": false
"cmd-k cmd-d": [ }
"editor::SelectNext", ],
{ "cmd-k cmd-d": [
"replace_newest": true "editor::SelectNext",
} {
], "replace_newest": true
"cmd-/": "editor::ToggleComments", }
"alt-up": "editor::SelectLargerSyntaxNode", ],
"ctrl-w": "editor::SelectLargerSyntaxNode", "cmd-/": "editor::ToggleComments",
"alt-down": "editor::SelectSmallerSyntaxNode", "alt-up": "editor::SelectLargerSyntaxNode",
"ctrl-shift-W": "editor::SelectSmallerSyntaxNode", "ctrl-w": "editor::SelectLargerSyntaxNode",
"cmd-u": "editor::UndoSelection", "alt-down": "editor::SelectSmallerSyntaxNode",
"cmd-shift-U": "editor::RedoSelection", "ctrl-shift-W": "editor::SelectSmallerSyntaxNode",
"f8": "editor::GoToNextDiagnostic", "cmd-u": "editor::UndoSelection",
"shift-f8": "editor::GoToPrevDiagnostic", "cmd-shift-U": "editor::RedoSelection",
"f2": "editor::Rename", "f8": "editor::GoToNextDiagnostic",
"f12": "editor::GoToDefinition", "shift-f8": "editor::GoToPrevDiagnostic",
"alt-shift-f12": "editor::FindAllReferences", "f2": "editor::Rename",
"ctrl-m": "editor::MoveToEnclosingBracket", "f12": "editor::GoToDefinition",
"pageup": "editor::PageUp", "alt-shift-f12": "editor::FindAllReferences",
"pagedown": "editor::PageDown", "ctrl-m": "editor::MoveToEnclosingBracket",
"alt-cmd-[": "editor::Fold", "pageup": "editor::PageUp",
"alt-cmd-]": "editor::UnfoldLines", "pagedown": "editor::PageDown",
"alt-cmd-f": "editor::FoldSelectedRanges", "alt-cmd-[": "editor::Fold",
"ctrl-space": "editor::ShowCompletions", "alt-cmd-]": "editor::UnfoldLines",
"cmd-.": "editor::ToggleCodeActions", "alt-cmd-f": "editor::FoldSelectedRanges",
"alt-enter": "editor::OpenExcerpts", "ctrl-space": "editor::ShowCompletions",
"cmd-f10": "editor::RestartLanguageServer" "cmd-.": "editor::ToggleCodeActions",
"alt-enter": "editor::OpenExcerpts",
"cmd-f10": "editor::RestartLanguageServer"
}
}, },
"Editor && renaming": { {
"enter": "editor::ConfirmRename" "context": "Editor && mode == full",
"bindings": {
"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 && showing_completions": { {
"enter": "editor::ConfirmCompletion", "context": "Editor && renaming",
"tab": "editor::ConfirmCompletion" "bindings": {
"enter": "editor::ConfirmRename"
}
}, },
"Editor && showing_code_actions": { {
"enter": "editor::ConfirmCodeAction" "context": "Editor && showing_completions",
"bindings": {
"enter": "editor::ConfirmCompletion",
"tab": "editor::ConfirmCompletion"
}
}, },
"Editor && mode == full": { {
"enter": "editor::Newline", "context": "Editor && showing_code_actions",
"cmd-f": [ "bindings": {
"buffer_search::Deploy", "enter": "editor::ConfirmCodeAction"
{ }
"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": [ "context": "Editor && mode == auto_height",
"editor::Input", "bindings": {
"\n" "alt-enter": [
] "editor::Input",
"\n"
]
}
}, },
"GoToLine": { {
"escape": "go_to_line::Toggle", "context": "GoToLine",
"enter": "go_to_line::Confirm" "bindings": {
"escape": "go_to_line::Toggle",
"enter": "go_to_line::Confirm"
}
}, },
"ChatPanel": { {
"enter": "chat_panel::Send" "context": "ChatPanel",
"bindings": {
"enter": "chat_panel::Send"
}
}, },
"ProjectPanel": { {
"left": "project_panel::CollapseSelectedEntry", "context": "ProjectPanel",
"right": "project_panel::ExpandSelectedEntry" "bindings": {
"left": "project_panel::CollapseSelectedEntry",
"right": "project_panel::ExpandSelectedEntry"
}
} }
} ]

View file

@ -1,93 +1,111 @@
{ [
"Editor && VimControl": { {
"i": [ "context": "Editor && VimControl",
"vim::SwitchMode", "bindings": {
"Insert" "i": [
], "vim::SwitchMode",
"g": [ "Insert"
"vim::PushOperator", ],
{ "g": [
"Namespace": "G" "vim::PushOperator",
} {
], "Namespace": "G"
"h": "vim::Left", }
"j": "vim::Down", ],
"k": "vim::Up", "h": "vim::Left",
"l": "vim::Right", "j": "vim::Down",
"0": "vim::StartOfLine", "k": "vim::Up",
"shift-$": "vim::EndOfLine", "l": "vim::Right",
"shift-G": "vim::EndOfDocument", "0": "vim::StartOfLine",
"w": "vim::NextWordStart", "shift-$": "vim::EndOfLine",
"shift-W": [ "shift-G": "vim::EndOfDocument",
"vim::NextWordStart", "w": "vim::NextWordStart",
{ "shift-W": [
"ignorePunctuation": true "vim::NextWordStart",
} {
], "ignorePunctuation": true
"e": "vim::NextWordEnd", }
"shift-E": [ ],
"vim::NextWordEnd", "e": "vim::NextWordEnd",
{ "shift-E": [
"ignorePunctuation": true "vim::NextWordEnd",
} {
], "ignorePunctuation": true
"b": "vim::PreviousWordStart", }
"shift-B": [ ],
"vim::PreviousWordStart", "b": "vim::PreviousWordStart",
{ "shift-B": [
"ignorePunctuation": true "vim::PreviousWordStart",
} {
], "ignorePunctuation": true
"escape": [ }
"vim::SwitchMode", ],
"Normal" "escape": [
] "vim::SwitchMode",
"Normal"
]
}
}, },
"Editor && vim_operator == g": { {
"g": "vim::StartOfDocument" "context": "Editor && vim_operator == g",
"bindings": {
"g": "vim::StartOfDocument"
}
}, },
"Editor && vim_mode == insert": { {
"escape": "vim::NormalBefore", "context": "Editor && vim_mode == insert",
"ctrl-c": "vim::NormalBefore" "bindings": {
"escape": "vim::NormalBefore",
"ctrl-c": "vim::NormalBefore"
}
}, },
"Editor && vim_mode == normal": { {
"c": [ "context": "Editor && vim_mode == normal",
"vim::PushOperator", "bindings": {
"Change" "c": [
], "vim::PushOperator",
"d": [ "Change"
"vim::PushOperator", ],
"Delete" "d": [
] "vim::PushOperator",
"Delete"
]
}
}, },
"Editor && vim_operator == c": { {
"w": [ "context": "Editor && vim_operator == c",
"vim::NextWordEnd", "bindings": {
{ "w": [
"ignorePunctuation": false "vim::NextWordEnd",
} {
], "ignorePunctuation": false
"shift-W": [ }
"vim::NextWordEnd", ],
{ "shift-W": [
"ignorePunctuation": true "vim::NextWordEnd",
} {
] "ignorePunctuation": true
}
]
}
}, },
"Editor && vim_operator == d": { {
"w": [ "context": "Editor && vim_operator == d",
"vim::NextWordStart", "bindings": {
{ "w": [
"ignorePunctuation": false, "vim::NextWordStart",
"stopAtNewline": true {
} "ignorePunctuation": false,
], "stopAtNewline": true
"shift-W": [ }
"vim::NextWordStart", ],
{ "shift-W": [
"ignorePunctuation": true, "vim::NextWordStart",
"stopAtNewline": true {
} "ignorePunctuation": true,
] "stopAtNewline": true
}
]
}
} }
} ]

View file

@ -1306,6 +1306,10 @@ impl MutableAppContext {
} }
} }
pub fn all_action_names<'a>(&'a self) -> impl Iterator<Item = &'static str> + 'a {
self.action_deserializers.keys().copied()
}
pub fn available_actions( pub fn available_actions(
&self, &self,
window_id: usize, window_id: usize,

View file

@ -3,14 +3,38 @@ use anyhow::{Context, Result};
use assets::Assets; use assets::Assets;
use collections::BTreeMap; use collections::BTreeMap;
use gpui::{keymap::Binding, MutableAppContext}; use gpui::{keymap::Binding, MutableAppContext};
use schemars::{
gen::{SchemaGenerator, SchemaSettings},
schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation},
JsonSchema,
};
use serde::Deserialize; use serde::Deserialize;
use serde_json::value::RawValue; use serde_json::{value::RawValue, Value};
#[derive(Deserialize, Default, Clone, JsonSchema)]
#[serde(transparent)]
pub struct KeymapFileContent(Vec<KeymapBlock>);
#[derive(Deserialize, Default, Clone, JsonSchema)]
pub struct KeymapBlock {
#[serde(default)]
context: Option<String>,
bindings: BTreeMap<String, KeymapAction>,
}
#[derive(Deserialize, Default, Clone)] #[derive(Deserialize, Default, Clone)]
#[serde(transparent)] #[serde(transparent)]
pub struct KeymapFileContent(BTreeMap<String, ActionsByKeystroke>); pub struct KeymapAction(Box<RawValue>);
type ActionsByKeystroke = BTreeMap<String, Box<RawValue>>; impl JsonSchema for KeymapAction {
fn schema_name() -> String {
"KeymapAction".into()
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
Schema::Bool(true)
}
}
#[derive(Deserialize)] #[derive(Deserialize)]
struct ActionWithData(Box<str>, Box<RawValue>); struct ActionWithData(Box<str>, Box<RawValue>);
@ -29,13 +53,12 @@ impl KeymapFileContent {
} }
pub fn add(self, cx: &mut MutableAppContext) -> Result<()> { pub fn add(self, cx: &mut MutableAppContext) -> Result<()> {
for (context, actions) in self.0 { for KeymapBlock { context, bindings } in self.0 {
let context = if context == "*" { None } else { Some(context) };
cx.add_bindings( cx.add_bindings(
actions bindings
.into_iter() .into_iter()
.map(|(keystroke, action)| { .map(|(keystroke, action)| {
let action = action.get(); let action = action.0.get();
// This is a workaround for a limitation in serde: serde-rs/json#497 // 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 // We want to deserialize the action data as a `RawValue` so that we can
@ -61,3 +84,39 @@ impl KeymapFileContent {
Ok(()) Ok(())
} }
} }
pub fn keymap_file_json_schema(action_names: &[&'static str]) -> serde_json::Value {
let mut root_schema = SchemaSettings::draft07()
.with(|settings| settings.option_add_null_type = false)
.into_generator()
.into_root_schema_for::<KeymapFileContent>();
let action_schema = Schema::Object(SchemaObject {
subschemas: Some(Box::new(SubschemaValidation {
one_of: Some(vec![
Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
enum_values: Some(
action_names
.into_iter()
.map(|name| Value::String(name.to_string()))
.collect(),
),
..Default::default()
}),
Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Array))),
..Default::default()
}),
]),
..Default::default()
})),
..Default::default()
});
root_schema
.definitions
.insert("KeymapAction".to_owned(), action_schema);
serde_json::to_value(root_schema).unwrap()
}

View file

@ -15,7 +15,7 @@ use std::{collections::HashMap, sync::Arc};
use theme::{Theme, ThemeRegistry}; use theme::{Theme, ThemeRegistry};
use util::ResultExt as _; use util::ResultExt as _;
pub use keymap_file::KeymapFileContent; pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
#[derive(Clone)] #[derive(Clone)]
pub struct Settings { pub struct Settings {
@ -78,90 +78,6 @@ impl Settings {
}) })
} }
pub fn file_json_schema(
theme_names: Vec<String>,
language_names: Vec<String>,
) -> serde_json::Value {
let settings = SchemaSettings::draft07().with(|settings| {
settings.option_add_null_type = false;
});
let generator = SchemaGenerator::new(settings);
let mut root_schema = generator.into_root_schema_for::<SettingsFileContent>();
// Construct theme names reference type
let theme_names = theme_names
.into_iter()
.map(|name| Value::String(name))
.collect();
let theme_names_schema = Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
enum_values: Some(theme_names),
..Default::default()
});
root_schema
.definitions
.insert("ThemeName".to_owned(), theme_names_schema);
// Construct language overrides reference type
let language_override_schema_reference = Schema::Object(SchemaObject {
reference: Some("#/definitions/LanguageOverride".to_owned()),
..Default::default()
});
let language_overrides_properties = language_names
.into_iter()
.map(|name| {
(
name,
Schema::Object(SchemaObject {
subschemas: Some(Box::new(SubschemaValidation {
all_of: Some(vec![language_override_schema_reference.clone()]),
..Default::default()
})),
..Default::default()
}),
)
})
.collect();
let language_overrides_schema = Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
object: Some(Box::new(ObjectValidation {
properties: language_overrides_properties,
..Default::default()
})),
..Default::default()
});
root_schema
.definitions
.insert("LanguageOverrides".to_owned(), language_overrides_schema);
// Modify theme property to use new theme reference type
let settings_file_schema = root_schema.schema.object.as_mut().unwrap();
let language_overrides_schema_reference = Schema::Object(SchemaObject {
reference: Some("#/definitions/ThemeName".to_owned()),
..Default::default()
});
settings_file_schema.properties.insert(
"theme".to_owned(),
Schema::Object(SchemaObject {
subschemas: Some(Box::new(SubschemaValidation {
all_of: Some(vec![language_overrides_schema_reference]),
..Default::default()
})),
..Default::default()
}),
);
// Modify language_overrides property to use LanguageOverrides reference
settings_file_schema.properties.insert(
"language_overrides".to_owned(),
Schema::Object(SchemaObject {
reference: Some("#/definitions/LanguageOverrides".to_owned()),
..Default::default()
}),
);
serde_json::to_value(root_schema).unwrap()
}
pub fn with_overrides( pub fn with_overrides(
mut self, mut self,
language_name: impl Into<Arc<str>>, language_name: impl Into<Arc<str>>,
@ -249,6 +165,90 @@ impl Settings {
} }
} }
pub fn settings_file_json_schema(
theme_names: Vec<String>,
language_names: Vec<String>,
) -> serde_json::Value {
let settings = SchemaSettings::draft07().with(|settings| {
settings.option_add_null_type = false;
});
let generator = SchemaGenerator::new(settings);
let mut root_schema = generator.into_root_schema_for::<SettingsFileContent>();
// Construct theme names reference type
let theme_names = theme_names
.into_iter()
.map(|name| Value::String(name))
.collect();
let theme_names_schema = Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
enum_values: Some(theme_names),
..Default::default()
});
root_schema
.definitions
.insert("ThemeName".to_owned(), theme_names_schema);
// Construct language overrides reference type
let language_override_schema_reference = Schema::Object(SchemaObject {
reference: Some("#/definitions/LanguageOverride".to_owned()),
..Default::default()
});
let language_overrides_properties = language_names
.into_iter()
.map(|name| {
(
name,
Schema::Object(SchemaObject {
subschemas: Some(Box::new(SubschemaValidation {
all_of: Some(vec![language_override_schema_reference.clone()]),
..Default::default()
})),
..Default::default()
}),
)
})
.collect();
let language_overrides_schema = Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
object: Some(Box::new(ObjectValidation {
properties: language_overrides_properties,
..Default::default()
})),
..Default::default()
});
root_schema
.definitions
.insert("LanguageOverrides".to_owned(), language_overrides_schema);
// Modify theme property to use new theme reference type
let settings_file_schema = root_schema.schema.object.as_mut().unwrap();
let language_overrides_schema_reference = Schema::Object(SchemaObject {
reference: Some("#/definitions/ThemeName".to_owned()),
..Default::default()
});
settings_file_schema.properties.insert(
"theme".to_owned(),
Schema::Object(SchemaObject {
subschemas: Some(Box::new(SubschemaValidation {
all_of: Some(vec![language_overrides_schema_reference]),
..Default::default()
})),
..Default::default()
}),
);
// Modify language_overrides property to use LanguageOverrides reference
settings_file_schema.properties.insert(
"language_overrides".to_owned(),
Schema::Object(SchemaObject {
reference: Some("#/definitions/LanguageOverrides".to_owned()),
..Default::default()
}),
);
serde_json::to_value(root_schema).unwrap()
}
fn merge<T: Copy>(target: &mut T, value: Option<T>) { fn merge<T: Copy>(target: &mut T, value: Option<T>) {
if let Some(value) = value { if let Some(value) = value {
*target = value; *target = value;

View file

@ -25,7 +25,7 @@ pub use project::{self, fs};
use project_panel::ProjectPanel; use project_panel::ProjectPanel;
use search::{BufferSearchBar, ProjectSearchBar}; use search::{BufferSearchBar, ProjectSearchBar};
use serde_json::to_string_pretty; use serde_json::to_string_pretty;
use settings::Settings; use settings::{keymap_file_json_schema, settings_file_json_schema, Settings};
use std::{ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
@ -41,6 +41,7 @@ actions!(
Quit, Quit,
DebugElements, DebugElements,
OpenSettings, OpenSettings,
OpenKeymap,
IncreaseBufferFontSize, IncreaseBufferFontSize,
DecreaseBufferFontSize, DecreaseBufferFontSize,
InstallCommandLineInterface, InstallCommandLineInterface,
@ -78,39 +79,13 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
cx.add_action({ cx.add_action({
let app_state = app_state.clone(); let app_state = app_state.clone();
move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| { move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
let app_state = app_state.clone(); open_config_file(&SETTINGS_PATH, app_state.clone(), cx);
cx.spawn(move |workspace, mut cx| async move { }
let fs = &app_state.fs; });
if !fs.is_file(&SETTINGS_PATH).await { cx.add_action({
fs.create_dir(&ROOT_PATH).await?; let app_state = app_state.clone();
fs.create_file(&SETTINGS_PATH, Default::default()).await?; move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| {
} open_config_file(&KEYMAP_PATH, app_state.clone(), cx);
workspace
.update(&mut cx, |workspace, cx| {
if workspace.project().read(cx).is_local() {
workspace.open_paths(&[SETTINGS_PATH.clone()], cx)
} else {
let (_, workspace) =
cx.add_window((app_state.build_window_options)(), |cx| {
let project = Project::local(
app_state.client.clone(),
app_state.user_store.clone(),
app_state.languages.clone(),
app_state.fs.clone(),
cx,
);
(app_state.build_workspace)(project, &app_state, cx)
});
workspace.update(cx, |workspace, cx| {
workspace.open_paths(&[SETTINGS_PATH.clone()], cx)
})
}
})
.await;
Ok::<_, anyhow::Error>(())
})
.detach_and_log_err(cx);
} }
}); });
cx.add_action( cx.add_action(
@ -137,7 +112,6 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
); );
workspace::lsp_status::init(cx); workspace::lsp_status::init(cx);
settings::KeymapFileContent::load_defaults(cx); settings::KeymapFileContent::load_defaults(cx);
} }
@ -179,13 +153,18 @@ pub fn build_workspace(
let theme_names = app_state.themes.list().collect(); let theme_names = app_state.themes.list().collect();
let language_names = app_state.languages.language_names(); let language_names = app_state.languages.language_names();
project.update(cx, |project, _| { project.update(cx, |project, cx| {
let action_names = cx.all_action_names().collect::<Vec<_>>();
project.set_language_server_settings(serde_json::json!({ project.set_language_server_settings(serde_json::json!({
"json": { "json": {
"schemas": [ "schemas": [
{ {
"fileMatch": "**/.zed/settings.json", "fileMatch": [".zed/settings.json"],
"schema": Settings::file_json_schema(theme_names, language_names), "schema": settings_file_json_schema(theme_names, language_names),
},
{
"fileMatch": [".zed/keymap.json"],
"schema": keymap_file_json_schema(&action_names),
} }
] ]
} }
@ -289,6 +268,44 @@ async fn install_cli(cx: &AsyncAppContext) -> Result<()> {
} }
} }
fn open_config_file(
path: &'static Path,
app_state: Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) {
cx.spawn(|workspace, mut cx| async move {
let fs = &app_state.fs;
if !fs.is_file(path).await {
fs.create_dir(&ROOT_PATH).await?;
fs.create_file(path, Default::default()).await?;
}
workspace
.update(&mut cx, |workspace, cx| {
if workspace.project().read(cx).is_local() {
workspace.open_paths(&[path.to_path_buf()], cx)
} else {
let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
let project = Project::local(
app_state.client.clone(),
app_state.user_store.clone(),
app_state.languages.clone(),
app_state.fs.clone(),
cx,
);
(app_state.build_workspace)(project, &app_state, cx)
});
workspace.update(cx, |workspace, cx| {
workspace.open_paths(&[path.to_path_buf()], cx)
})
}
})
.await;
Ok::<_, anyhow::Error>(())
})
.detach_and_log_err(cx)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;