@@ -4,7 +4,7 @@ use std::{
};
use anyhow::{Context as _, anyhow};
-use collections::HashSet;
+use collections::{HashMap, HashSet};
use editor::{CompletionProvider, Editor, EditorEvent};
use feature_flags::FeatureFlagViewExt;
use fs::Fs;
@@ -240,7 +240,7 @@ impl KeymapEditor {
Some(Default) => 3,
None => 4,
};
- return (source_precedence, keybind.action.as_ref());
+ return (source_precedence, keybind.action_name.as_ref());
});
}
this.selected_index.take();
@@ -261,6 +261,12 @@ impl KeymapEditor {
let mut unmapped_action_names =
HashSet::from_iter(cx.all_action_names().into_iter().copied());
let action_documentation = cx.action_documentation();
+ let mut generator = KeymapFile::action_schema_generator();
+ let action_schema = HashMap::from_iter(
+ cx.action_schemas(&mut generator)
+ .into_iter()
+ .filter_map(|(name, schema)| schema.map(|schema| (name, schema))),
+ );
let mut processed_bindings = Vec::new();
let mut string_match_candidates = Vec::new();
@@ -295,9 +301,10 @@ impl KeymapEditor {
processed_bindings.push(ProcessedKeybinding {
keystroke_text: keystroke_text.into(),
ui_key_binding,
- action: action_name.into(),
+ action_name: action_name.into(),
action_input,
action_docs,
+ action_schema: action_schema.get(action_name).cloned(),
context: Some(context),
source,
});
@@ -311,9 +318,10 @@ impl KeymapEditor {
processed_bindings.push(ProcessedKeybinding {
keystroke_text: empty.clone(),
ui_key_binding: None,
- action: action_name.into(),
+ action_name: action_name.into(),
action_input: None,
action_docs: action_documentation.get(action_name).copied(),
+ action_schema: action_schema.get(action_name).cloned(),
context: None,
source: None,
});
@@ -326,8 +334,8 @@ impl KeymapEditor {
fn update_keybindings(&mut self, cx: &mut Context<KeymapEditor>) {
let workspace = self.workspace.clone();
cx.spawn(async move |this, cx| {
- let json_language = Self::load_json_language(workspace.clone(), cx).await;
- let rust_language = Self::load_rust_language(workspace.clone(), cx).await;
+ let json_language = load_json_language(workspace.clone(), cx).await;
+ let rust_language = load_rust_language(workspace.clone(), cx).await;
let query = this.update(cx, |this, cx| {
let (key_bindings, string_match_candidates) =
@@ -353,64 +361,6 @@ impl KeymapEditor {
.detach_and_log_err(cx);
}
- async fn load_json_language(
- workspace: WeakEntity<Workspace>,
- cx: &mut AsyncApp,
- ) -> Arc<Language> {
- let json_language_task = workspace
- .read_with(cx, |workspace, cx| {
- workspace
- .project()
- .read(cx)
- .languages()
- .language_for_name("JSON")
- })
- .context("Failed to load JSON language")
- .log_err();
- let json_language = match json_language_task {
- Some(task) => task.await.context("Failed to load JSON language").log_err(),
- None => None,
- };
- return json_language.unwrap_or_else(|| {
- Arc::new(Language::new(
- LanguageConfig {
- name: "JSON".into(),
- ..Default::default()
- },
- Some(tree_sitter_json::LANGUAGE.into()),
- ))
- });
- }
-
- async fn load_rust_language(
- workspace: WeakEntity<Workspace>,
- cx: &mut AsyncApp,
- ) -> Arc<Language> {
- let rust_language_task = workspace
- .read_with(cx, |workspace, cx| {
- workspace
- .project()
- .read(cx)
- .languages()
- .language_for_name("Rust")
- })
- .context("Failed to load Rust language")
- .log_err();
- let rust_language = match rust_language_task {
- Some(task) => task.await.context("Failed to load Rust language").log_err(),
- None => None,
- };
- return rust_language.unwrap_or_else(|| {
- Arc::new(Language::new(
- LanguageConfig {
- name: "Rust".into(),
- ..Default::default()
- },
- Some(tree_sitter_rust::LANGUAGE.into()),
- ))
- });
- }
-
fn dispatch_context(&self, _window: &Window, _cx: &Context<Self>) -> KeyContext {
let mut dispatch_context = KeyContext::new_with_defaults();
dispatch_context.add("KeymapEditor");
@@ -526,8 +476,10 @@ impl KeymapEditor {
self.workspace
.update(cx, |workspace, cx| {
let fs = workspace.app_state().fs.clone();
+ let workspace_weak = cx.weak_entity();
workspace.toggle_modal(window, cx, |window, cx| {
- let modal = KeybindingEditorModal::new(keybind.clone(), fs, window, cx);
+ let modal =
+ KeybindingEditorModal::new(keybind.clone(), workspace_weak, fs, window, cx);
window.focus(&modal.focus_handle(cx));
modal
});
@@ -564,7 +516,7 @@ impl KeymapEditor {
) {
let action = self
.selected_binding()
- .map(|binding| binding.action.to_string());
+ .map(|binding| binding.action_name.to_string());
let Some(action) = action else {
return;
};
@@ -576,9 +528,10 @@ impl KeymapEditor {
struct ProcessedKeybinding {
keystroke_text: SharedString,
ui_key_binding: Option<ui::KeyBinding>,
- action: SharedString,
+ action_name: SharedString,
action_input: Option<SyntaxHighlightedText>,
action_docs: Option<&'static str>,
+ action_schema: Option<schemars::Schema>,
context: Option<KeybindContextString>,
source: Option<(KeybindSource, SharedString)>,
}
@@ -685,10 +638,10 @@ impl Render for KeymapEditor {
let binding = &this.keybindings[candidate_id];
let action = div()
- .child(binding.action.clone())
+ .child(binding.action_name.clone())
.id(("keymap action", index))
.tooltip({
- let action_name = binding.action.clone();
+ let action_name = binding.action_name.clone();
let action_docs = binding.action_docs;
move |_, cx| {
let action_tooltip = Tooltip::new(
@@ -828,6 +781,7 @@ struct KeybindingEditorModal {
editing_keybind: ProcessedKeybinding,
keybind_editor: Entity<KeystrokeInput>,
context_editor: Entity<Editor>,
+ input_editor: Option<Entity<Editor>>,
fs: Arc<dyn Fs>,
error: Option<String>,
}
@@ -845,6 +799,7 @@ impl Focusable for KeybindingEditorModal {
impl KeybindingEditorModal {
pub fn new(
editing_keybind: ProcessedKeybinding,
+ workspace: WeakEntity<Workspace>,
fs: Arc<dyn Fs>,
window: &mut Window,
cx: &mut App,
@@ -881,11 +836,39 @@ impl KeybindingEditorModal {
editor
});
+
+ let input_editor = editing_keybind.action_schema.clone().map(|_schema| {
+ cx.new(|cx| {
+ let mut editor = Editor::auto_height_unbounded(1, window, cx);
+ if let Some(input) = editing_keybind.action_input.clone() {
+ editor.set_text(input.text, window, cx);
+ } else {
+ // TODO: default value from schema?
+ editor.set_placeholder_text("Action input", cx);
+ }
+ cx.spawn(async |editor, cx| {
+ let json_language = load_json_language(workspace, cx).await;
+ editor
+ .update(cx, |editor, cx| {
+ if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
+ buffer.update(cx, |buffer, cx| {
+ buffer.set_language(Some(json_language), cx)
+ });
+ }
+ })
+ .context("Failed to load JSON language for editing keybinding action input")
+ })
+ .detach_and_log_err(cx);
+ editor
+ })
+ });
+
Self {
editing_keybind,
fs,
keybind_editor,
context_editor,
+ input_editor,
error: None,
}
}
@@ -964,13 +947,38 @@ impl Render for KeybindingEditorModal {
)
.child(self.keybind_editor.clone()),
)
+ .when_some(self.input_editor.clone(), |this, editor| {
+ this.child(
+ v_flex()
+ .p_3()
+ .gap_3()
+ .child(
+ v_flex().child(Label::new("Edit Input")).child(
+ Label::new("Input the desired input to the binding.")
+ .color(Color::Muted),
+ ),
+ )
+ .child(
+ div()
+ .w_full()
+ .border_color(cx.theme().colors().border_variant)
+ .border_1()
+ .py_2()
+ .px_3()
+ .min_h_8()
+ .rounded_md()
+ .bg(theme.editor_background)
+ .child(editor),
+ ),
+ )
+ })
.child(
v_flex()
.p_3()
.gap_3()
.child(
- v_flex().child(Label::new("Edit Keystroke")).child(
- Label::new("Input the desired keystroke for the selected action.")
+ v_flex().child(Label::new("Edit Context")).child(
+ Label::new("Input the desired context for the binding.")
.color(Color::Muted),
),
)
@@ -1081,6 +1089,58 @@ impl CompletionProvider for KeyContextCompletionProvider {
}
}
+async fn load_json_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) -> Arc<Language> {
+ let json_language_task = workspace
+ .read_with(cx, |workspace, cx| {
+ workspace
+ .project()
+ .read(cx)
+ .languages()
+ .language_for_name("JSON")
+ })
+ .context("Failed to load JSON language")
+ .log_err();
+ let json_language = match json_language_task {
+ Some(task) => task.await.context("Failed to load JSON language").log_err(),
+ None => None,
+ };
+ return json_language.unwrap_or_else(|| {
+ Arc::new(Language::new(
+ LanguageConfig {
+ name: "JSON".into(),
+ ..Default::default()
+ },
+ Some(tree_sitter_json::LANGUAGE.into()),
+ ))
+ });
+}
+
+async fn load_rust_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp) -> Arc<Language> {
+ let rust_language_task = workspace
+ .read_with(cx, |workspace, cx| {
+ workspace
+ .project()
+ .read(cx)
+ .languages()
+ .language_for_name("Rust")
+ })
+ .context("Failed to load Rust language")
+ .log_err();
+ let rust_language = match rust_language_task {
+ Some(task) => task.await.context("Failed to load Rust language").log_err(),
+ None => None,
+ };
+ return rust_language.unwrap_or_else(|| {
+ Arc::new(Language::new(
+ LanguageConfig {
+ name: "Rust".into(),
+ ..Default::default()
+ },
+ Some(tree_sitter_rust::LANGUAGE.into()),
+ ))
+ });
+}
+
async fn save_keybinding_update(
existing: ProcessedKeybinding,
new_keystrokes: &[Keystroke],
@@ -1113,7 +1173,7 @@ async fn save_keybinding_update(
target: settings::KeybindUpdateTarget {
context: existing_context,
keystrokes: existing_keystrokes,
- action_name: &existing.action,
+ action_name: &existing.action_name,
use_key_equivalents: false,
input,
},
@@ -1124,7 +1184,7 @@ async fn save_keybinding_update(
source: settings::KeybindUpdateTarget {
context: new_context,
keystrokes: new_keystrokes,
- action_name: &existing.action,
+ action_name: &existing.action_name,
use_key_equivalents: false,
input,
},