keymap_ui: Editor for action input in modal (#34080)
Closes #ISSUE Adds a very simple editor for editing action input to the edit keybind modal. No auto-complete yet. Release Notes: - N/A *or* Added/Fixed/Improved ...
This commit is contained in:
parent
1220049089
commit
6b7c30d7ad
2 changed files with 140 additions and 74 deletions
|
@ -426,12 +426,18 @@ impl KeymapFile {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a JSON schema generator, suitable for generating json schemas
|
||||
/// for actions
|
||||
pub fn action_schema_generator() -> schemars::SchemaGenerator {
|
||||
schemars::generate::SchemaSettings::draft2019_09().into_generator()
|
||||
}
|
||||
|
||||
pub fn generate_json_schema_for_registered_actions(cx: &mut App) -> Value {
|
||||
// instead of using DefaultDenyUnknownFields, actions typically use
|
||||
// `#[serde(deny_unknown_fields)]` so that these cases are reported as parse failures. This
|
||||
// is because the rest of the keymap will still load in these cases, whereas other settings
|
||||
// files would not.
|
||||
let mut generator = schemars::generate::SchemaSettings::draft2019_09().into_generator();
|
||||
let mut generator = Self::action_schema_generator();
|
||||
|
||||
let action_schemas = cx.action_schemas(&mut generator);
|
||||
let deprecations = cx.deprecated_actions_to_preferred_actions();
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue