Compare commits
5 commits
main
...
keymap-ui-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1624ae79ac | ||
![]() |
907225f002 | ||
![]() |
1a836521a8 | ||
![]() |
750582d2a2 | ||
![]() |
0c14b615c1 |
3 changed files with 206 additions and 87 deletions
|
@ -623,7 +623,7 @@ impl KeymapFile {
|
||||||
target_keybind_source,
|
target_keybind_source,
|
||||||
} if target_keybind_source != KeybindSource::User => {
|
} if target_keybind_source != KeybindSource::User => {
|
||||||
target.action_name = gpui::NoAction.name();
|
target.action_name = gpui::NoAction.name();
|
||||||
target.input.take();
|
target.action_arguments.take();
|
||||||
operation = KeybindUpdateOperation::Add(target);
|
operation = KeybindUpdateOperation::Add(target);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -848,17 +848,17 @@ pub struct KeybindUpdateTarget<'a> {
|
||||||
pub keystrokes: &'a [Keystroke],
|
pub keystrokes: &'a [Keystroke],
|
||||||
pub action_name: &'a str,
|
pub action_name: &'a str,
|
||||||
pub use_key_equivalents: bool,
|
pub use_key_equivalents: bool,
|
||||||
pub input: Option<&'a str>,
|
pub action_arguments: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> KeybindUpdateTarget<'a> {
|
impl<'a> KeybindUpdateTarget<'a> {
|
||||||
fn action_value(&self) -> Result<Value> {
|
fn action_value(&self) -> Result<Value> {
|
||||||
let action_name: Value = self.action_name.into();
|
let action_name: Value = self.action_name.into();
|
||||||
let value = match self.input {
|
let value = match self.action_arguments {
|
||||||
Some(input) => {
|
Some(args) => {
|
||||||
let input = serde_json::from_str::<Value>(input)
|
let args = serde_json::from_str::<Value>(args)
|
||||||
.context("Failed to parse action input as JSON")?;
|
.context("Failed to parse action arguments as JSON")?;
|
||||||
serde_json::json!([action_name, input])
|
serde_json::json!([action_name, args])
|
||||||
}
|
}
|
||||||
None => action_name,
|
None => action_name,
|
||||||
};
|
};
|
||||||
|
@ -986,7 +986,7 @@ mod tests {
|
||||||
action_name: "zed::SomeAction",
|
action_name: "zed::SomeAction",
|
||||||
context: None,
|
context: None,
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: None,
|
action_arguments: None,
|
||||||
}),
|
}),
|
||||||
r#"[
|
r#"[
|
||||||
{
|
{
|
||||||
|
@ -1012,7 +1012,7 @@ mod tests {
|
||||||
action_name: "zed::SomeOtherAction",
|
action_name: "zed::SomeOtherAction",
|
||||||
context: None,
|
context: None,
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: None,
|
action_arguments: None,
|
||||||
}),
|
}),
|
||||||
r#"[
|
r#"[
|
||||||
{
|
{
|
||||||
|
@ -1043,7 +1043,7 @@ mod tests {
|
||||||
action_name: "zed::SomeOtherAction",
|
action_name: "zed::SomeOtherAction",
|
||||||
context: None,
|
context: None,
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: Some(r#"{"foo": "bar"}"#),
|
action_arguments: Some(r#"{"foo": "bar"}"#),
|
||||||
}),
|
}),
|
||||||
r#"[
|
r#"[
|
||||||
{
|
{
|
||||||
|
@ -1079,7 +1079,7 @@ mod tests {
|
||||||
action_name: "zed::SomeOtherAction",
|
action_name: "zed::SomeOtherAction",
|
||||||
context: Some("Zed > Editor && some_condition = true"),
|
context: Some("Zed > Editor && some_condition = true"),
|
||||||
use_key_equivalents: true,
|
use_key_equivalents: true,
|
||||||
input: Some(r#"{"foo": "bar"}"#),
|
action_arguments: Some(r#"{"foo": "bar"}"#),
|
||||||
}),
|
}),
|
||||||
r#"[
|
r#"[
|
||||||
{
|
{
|
||||||
|
@ -1118,14 +1118,14 @@ mod tests {
|
||||||
action_name: "zed::SomeAction",
|
action_name: "zed::SomeAction",
|
||||||
context: None,
|
context: None,
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: None,
|
action_arguments: None,
|
||||||
},
|
},
|
||||||
source: KeybindUpdateTarget {
|
source: KeybindUpdateTarget {
|
||||||
keystrokes: &parse_keystrokes("ctrl-b"),
|
keystrokes: &parse_keystrokes("ctrl-b"),
|
||||||
action_name: "zed::SomeOtherAction",
|
action_name: "zed::SomeOtherAction",
|
||||||
context: None,
|
context: None,
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: Some(r#"{"foo": "bar"}"#),
|
action_arguments: Some(r#"{"foo": "bar"}"#),
|
||||||
},
|
},
|
||||||
target_keybind_source: KeybindSource::Base,
|
target_keybind_source: KeybindSource::Base,
|
||||||
},
|
},
|
||||||
|
@ -1164,14 +1164,14 @@ mod tests {
|
||||||
action_name: "zed::SomeAction",
|
action_name: "zed::SomeAction",
|
||||||
context: None,
|
context: None,
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: None,
|
action_arguments: None,
|
||||||
},
|
},
|
||||||
source: KeybindUpdateTarget {
|
source: KeybindUpdateTarget {
|
||||||
keystrokes: &parse_keystrokes("ctrl-b"),
|
keystrokes: &parse_keystrokes("ctrl-b"),
|
||||||
action_name: "zed::SomeOtherAction",
|
action_name: "zed::SomeOtherAction",
|
||||||
context: None,
|
context: None,
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: Some(r#"{"foo": "bar"}"#),
|
action_arguments: Some(r#"{"foo": "bar"}"#),
|
||||||
},
|
},
|
||||||
target_keybind_source: KeybindSource::User,
|
target_keybind_source: KeybindSource::User,
|
||||||
},
|
},
|
||||||
|
@ -1205,14 +1205,14 @@ mod tests {
|
||||||
action_name: "zed::SomeNonexistentAction",
|
action_name: "zed::SomeNonexistentAction",
|
||||||
context: None,
|
context: None,
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: None,
|
action_arguments: None,
|
||||||
},
|
},
|
||||||
source: KeybindUpdateTarget {
|
source: KeybindUpdateTarget {
|
||||||
keystrokes: &parse_keystrokes("ctrl-b"),
|
keystrokes: &parse_keystrokes("ctrl-b"),
|
||||||
action_name: "zed::SomeOtherAction",
|
action_name: "zed::SomeOtherAction",
|
||||||
context: None,
|
context: None,
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: None,
|
action_arguments: None,
|
||||||
},
|
},
|
||||||
target_keybind_source: KeybindSource::User,
|
target_keybind_source: KeybindSource::User,
|
||||||
},
|
},
|
||||||
|
@ -1248,14 +1248,14 @@ mod tests {
|
||||||
action_name: "zed::SomeAction",
|
action_name: "zed::SomeAction",
|
||||||
context: None,
|
context: None,
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: None,
|
action_arguments: None,
|
||||||
},
|
},
|
||||||
source: KeybindUpdateTarget {
|
source: KeybindUpdateTarget {
|
||||||
keystrokes: &parse_keystrokes("ctrl-b"),
|
keystrokes: &parse_keystrokes("ctrl-b"),
|
||||||
action_name: "zed::SomeOtherAction",
|
action_name: "zed::SomeOtherAction",
|
||||||
context: None,
|
context: None,
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: Some(r#"{"foo": "bar"}"#),
|
action_arguments: Some(r#"{"foo": "bar"}"#),
|
||||||
},
|
},
|
||||||
target_keybind_source: KeybindSource::User,
|
target_keybind_source: KeybindSource::User,
|
||||||
},
|
},
|
||||||
|
@ -1293,14 +1293,14 @@ mod tests {
|
||||||
action_name: "foo::bar",
|
action_name: "foo::bar",
|
||||||
context: Some("SomeContext"),
|
context: Some("SomeContext"),
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: None,
|
action_arguments: None,
|
||||||
},
|
},
|
||||||
source: KeybindUpdateTarget {
|
source: KeybindUpdateTarget {
|
||||||
keystrokes: &parse_keystrokes("c"),
|
keystrokes: &parse_keystrokes("c"),
|
||||||
action_name: "foo::baz",
|
action_name: "foo::baz",
|
||||||
context: Some("SomeOtherContext"),
|
context: Some("SomeOtherContext"),
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: None,
|
action_arguments: None,
|
||||||
},
|
},
|
||||||
target_keybind_source: KeybindSource::User,
|
target_keybind_source: KeybindSource::User,
|
||||||
},
|
},
|
||||||
|
@ -1337,14 +1337,14 @@ mod tests {
|
||||||
action_name: "foo::bar",
|
action_name: "foo::bar",
|
||||||
context: Some("SomeContext"),
|
context: Some("SomeContext"),
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: None,
|
action_arguments: None,
|
||||||
},
|
},
|
||||||
source: KeybindUpdateTarget {
|
source: KeybindUpdateTarget {
|
||||||
keystrokes: &parse_keystrokes("c"),
|
keystrokes: &parse_keystrokes("c"),
|
||||||
action_name: "foo::baz",
|
action_name: "foo::baz",
|
||||||
context: Some("SomeOtherContext"),
|
context: Some("SomeOtherContext"),
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: None,
|
action_arguments: None,
|
||||||
},
|
},
|
||||||
target_keybind_source: KeybindSource::User,
|
target_keybind_source: KeybindSource::User,
|
||||||
},
|
},
|
||||||
|
@ -1376,7 +1376,7 @@ mod tests {
|
||||||
keystrokes: &parse_keystrokes("a"),
|
keystrokes: &parse_keystrokes("a"),
|
||||||
action_name: "foo::bar",
|
action_name: "foo::bar",
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: None,
|
action_arguments: None,
|
||||||
},
|
},
|
||||||
target_keybind_source: KeybindSource::User,
|
target_keybind_source: KeybindSource::User,
|
||||||
},
|
},
|
||||||
|
@ -1408,7 +1408,7 @@ mod tests {
|
||||||
keystrokes: &parse_keystrokes("a"),
|
keystrokes: &parse_keystrokes("a"),
|
||||||
action_name: "foo::bar",
|
action_name: "foo::bar",
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: Some("true"),
|
action_arguments: Some("true"),
|
||||||
},
|
},
|
||||||
target_keybind_source: KeybindSource::User,
|
target_keybind_source: KeybindSource::User,
|
||||||
},
|
},
|
||||||
|
@ -1451,7 +1451,7 @@ mod tests {
|
||||||
keystrokes: &parse_keystrokes("a"),
|
keystrokes: &parse_keystrokes("a"),
|
||||||
action_name: "foo::bar",
|
action_name: "foo::bar",
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: Some("true"),
|
action_arguments: Some("true"),
|
||||||
},
|
},
|
||||||
target_keybind_source: KeybindSource::User,
|
target_keybind_source: KeybindSource::User,
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,9 +10,9 @@ use feature_flags::FeatureFlagViewExt;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, Animation, AnimationExt, AppContext as _, AsyncApp, ClickEvent, Context, DismissEvent,
|
Action, Animation, AnimationExt, AppContext as _, AsyncApp, Axis, ClickEvent, Context,
|
||||||
Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, IsZero, KeyContext,
|
DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, IsZero,
|
||||||
Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Point, ScrollStrategy,
|
KeyContext, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Point, ScrollStrategy,
|
||||||
ScrollWheelEvent, StyledText, Subscription, WeakEntity, actions, anchored, deferred, div,
|
ScrollWheelEvent, StyledText, Subscription, WeakEntity, actions, anchored, deferred, div,
|
||||||
};
|
};
|
||||||
use language::{Language, LanguageConfig, ToOffset as _};
|
use language::{Language, LanguageConfig, ToOffset as _};
|
||||||
|
@ -282,6 +282,25 @@ struct KeymapEditor {
|
||||||
keystroke_editor: Entity<KeystrokeInput>,
|
keystroke_editor: Entity<KeystrokeInput>,
|
||||||
selected_index: Option<usize>,
|
selected_index: Option<usize>,
|
||||||
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
|
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
|
||||||
|
previous_edit: Option<PreviousEdit>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PreviousEdit {
|
||||||
|
/// When deleting, we want to maintain the same scroll position
|
||||||
|
ScrollBarOffset(Point<Pixels>),
|
||||||
|
/// When editing or creating, because the new keybinding could be in a different position in the sort order
|
||||||
|
/// we store metadata about the new binding (either the modified version or newly created one)
|
||||||
|
/// and upon reload, we search for this binding in the list of keybindings, and if we find the one that matches
|
||||||
|
/// this metadata, we set the selected index to it and scroll to it,
|
||||||
|
/// and if we don't find it, we scroll to 0 and don't set a selected index
|
||||||
|
Keybinding {
|
||||||
|
action_mapping: ActionMapping,
|
||||||
|
action_name: SharedString,
|
||||||
|
/// The scrollbar position to fallback to if we don't find the keybinding during a refresh
|
||||||
|
/// this can happen if there's a filter applied to the search and the keybinding modification
|
||||||
|
/// filters the binding from the search results
|
||||||
|
fallback: Point<Pixels>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<()> for KeymapEditor {}
|
impl EventEmitter<()> for KeymapEditor {}
|
||||||
|
@ -294,8 +313,7 @@ impl Focusable for KeymapEditor {
|
||||||
|
|
||||||
impl KeymapEditor {
|
impl KeymapEditor {
|
||||||
fn new(workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
fn new(workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let _keymap_subscription =
|
let _keymap_subscription = cx.observe_global::<KeymapEventChannel>(Self::on_keymap_changed);
|
||||||
cx.observe_global::<KeymapEventChannel>(Self::update_keybindings);
|
|
||||||
let table_interaction_state = TableInteractionState::new(window, cx);
|
let table_interaction_state = TableInteractionState::new(window, cx);
|
||||||
|
|
||||||
let keystroke_editor = cx.new(|cx| {
|
let keystroke_editor = cx.new(|cx| {
|
||||||
|
@ -315,7 +333,7 @@ impl KeymapEditor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.update_matches(cx);
|
this.on_query_changed(cx);
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
@ -324,7 +342,7 @@ impl KeymapEditor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.update_matches(cx);
|
this.on_query_changed(cx);
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
@ -343,9 +361,10 @@ impl KeymapEditor {
|
||||||
keystroke_editor,
|
keystroke_editor,
|
||||||
selected_index: None,
|
selected_index: None,
|
||||||
context_menu: None,
|
context_menu: None,
|
||||||
|
previous_edit: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.update_keybindings(cx);
|
this.on_keymap_changed(cx);
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
@ -367,17 +386,20 @@ impl KeymapEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_matches(&self, cx: &mut Context<Self>) {
|
fn on_query_changed(&self, cx: &mut Context<Self>) {
|
||||||
let action_query = self.current_action_query(cx);
|
let action_query = self.current_action_query(cx);
|
||||||
let keystroke_query = self.current_keystroke_query(cx);
|
let keystroke_query = self.current_keystroke_query(cx);
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
Self::process_query(this, action_query, keystroke_query, cx).await
|
Self::update_matches(this.clone(), action_query, keystroke_query, cx).await?;
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.scroll_to_item(0, ScrollStrategy::Top, cx)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_query(
|
async fn update_matches(
|
||||||
this: WeakEntity<Self>,
|
this: WeakEntity<Self>,
|
||||||
action_query: String,
|
action_query: String,
|
||||||
keystroke_query: Vec<Keystroke>,
|
keystroke_query: Vec<Keystroke>,
|
||||||
|
@ -445,7 +467,6 @@ impl KeymapEditor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.selected_index.take();
|
this.selected_index.take();
|
||||||
this.scroll_to_item(0, ScrollStrategy::Top, cx);
|
|
||||||
this.matches = matches;
|
this.matches = matches;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
|
@ -499,9 +520,9 @@ impl KeymapEditor {
|
||||||
|
|
||||||
let action_name = key_binding.action().name();
|
let action_name = key_binding.action().name();
|
||||||
unmapped_action_names.remove(&action_name);
|
unmapped_action_names.remove(&action_name);
|
||||||
let action_input = key_binding
|
let action_arguments = key_binding
|
||||||
.action_input()
|
.action_input()
|
||||||
.map(|input| SyntaxHighlightedText::new(input, json_language.clone()));
|
.map(|arguments| SyntaxHighlightedText::new(arguments, json_language.clone()));
|
||||||
let action_docs = action_documentation.get(action_name).copied();
|
let action_docs = action_documentation.get(action_name).copied();
|
||||||
|
|
||||||
let index = processed_bindings.len();
|
let index = processed_bindings.len();
|
||||||
|
@ -510,7 +531,7 @@ impl KeymapEditor {
|
||||||
keystroke_text: keystroke_text.into(),
|
keystroke_text: keystroke_text.into(),
|
||||||
ui_key_binding,
|
ui_key_binding,
|
||||||
action_name: action_name.into(),
|
action_name: action_name.into(),
|
||||||
action_input,
|
action_arguments,
|
||||||
action_docs,
|
action_docs,
|
||||||
action_schema: action_schema.get(action_name).cloned(),
|
action_schema: action_schema.get(action_name).cloned(),
|
||||||
context: Some(context),
|
context: Some(context),
|
||||||
|
@ -527,7 +548,7 @@ impl KeymapEditor {
|
||||||
keystroke_text: empty.clone(),
|
keystroke_text: empty.clone(),
|
||||||
ui_key_binding: None,
|
ui_key_binding: None,
|
||||||
action_name: action_name.into(),
|
action_name: action_name.into(),
|
||||||
action_input: None,
|
action_arguments: None,
|
||||||
action_docs: action_documentation.get(action_name).copied(),
|
action_docs: action_documentation.get(action_name).copied(),
|
||||||
action_schema: action_schema.get(action_name).cloned(),
|
action_schema: action_schema.get(action_name).cloned(),
|
||||||
context: None,
|
context: None,
|
||||||
|
@ -539,7 +560,7 @@ impl KeymapEditor {
|
||||||
(processed_bindings, string_match_candidates)
|
(processed_bindings, string_match_candidates)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_keybindings(&mut self, cx: &mut Context<KeymapEditor>) {
|
fn on_keymap_changed(&mut self, cx: &mut Context<KeymapEditor>) {
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
let json_language = load_json_language(workspace.clone(), cx).await;
|
let json_language = load_json_language(workspace.clone(), cx).await;
|
||||||
|
@ -574,7 +595,47 @@ impl KeymapEditor {
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
// calls cx.notify
|
// calls cx.notify
|
||||||
Self::process_query(this, action_query, keystroke_query, cx).await
|
Self::update_matches(this.clone(), action_query, keystroke_query, cx).await?;
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
if let Some(previous_edit) = this.previous_edit.take() {
|
||||||
|
match previous_edit {
|
||||||
|
// should remove scroll from process_query
|
||||||
|
PreviousEdit::ScrollBarOffset(offset) => {
|
||||||
|
this.table_interaction_state.update(cx, |table, _| {
|
||||||
|
table.set_scrollbar_offset(Axis::Vertical, offset)
|
||||||
|
})
|
||||||
|
// set selected index and scroll
|
||||||
|
}
|
||||||
|
PreviousEdit::Keybinding {
|
||||||
|
action_mapping,
|
||||||
|
action_name,
|
||||||
|
fallback,
|
||||||
|
} => {
|
||||||
|
let scroll_position =
|
||||||
|
this.matches.iter().enumerate().find_map(|(index, item)| {
|
||||||
|
let binding = &this.keybindings[item.candidate_id];
|
||||||
|
if binding.get_action_mapping() == action_mapping
|
||||||
|
&& binding.action_name == action_name
|
||||||
|
{
|
||||||
|
Some(index)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(scroll_position) = scroll_position {
|
||||||
|
this.scroll_to_item(scroll_position, ScrollStrategy::Top, cx);
|
||||||
|
this.selected_index = Some(scroll_position);
|
||||||
|
} else {
|
||||||
|
this.table_interaction_state.update(cx, |table, _| {
|
||||||
|
table.set_scrollbar_offset(Axis::Vertical, fallback)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
@ -806,6 +867,7 @@ impl KeymapEditor {
|
||||||
let Some(to_remove) = self.selected_binding().cloned() else {
|
let Some(to_remove) = self.selected_binding().cloned() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(fs) = self
|
let Ok(fs) = self
|
||||||
.workspace
|
.workspace
|
||||||
.read_with(cx, |workspace, _| workspace.app_state().fs.clone())
|
.read_with(cx, |workspace, _| workspace.app_state().fs.clone())
|
||||||
|
@ -813,6 +875,11 @@ impl KeymapEditor {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let tab_size = cx.global::<settings::SettingsStore>().json_tab_size();
|
let tab_size = cx.global::<settings::SettingsStore>().json_tab_size();
|
||||||
|
self.previous_edit = Some(PreviousEdit::ScrollBarOffset(
|
||||||
|
self.table_interaction_state
|
||||||
|
.read(cx)
|
||||||
|
.get_scrollbar_offset(Axis::Vertical),
|
||||||
|
));
|
||||||
cx.spawn(async move |_, _| remove_keybinding(to_remove, &fs, tab_size).await)
|
cx.spawn(async move |_, _| remove_keybinding(to_remove, &fs, tab_size).await)
|
||||||
.detach_and_notify_err(window, cx);
|
.detach_and_notify_err(window, cx);
|
||||||
}
|
}
|
||||||
|
@ -861,7 +928,7 @@ impl KeymapEditor {
|
||||||
fn set_filter_state(&mut self, filter_state: FilterState, cx: &mut Context<Self>) {
|
fn set_filter_state(&mut self, filter_state: FilterState, cx: &mut Context<Self>) {
|
||||||
if self.filter_state != filter_state {
|
if self.filter_state != filter_state {
|
||||||
self.filter_state = filter_state;
|
self.filter_state = filter_state;
|
||||||
self.update_matches(cx);
|
self.on_query_changed(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -872,7 +939,7 @@ impl KeymapEditor {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.search_mode = self.search_mode.invert();
|
self.search_mode = self.search_mode.invert();
|
||||||
self.update_matches(cx);
|
self.on_query_changed(cx);
|
||||||
|
|
||||||
// Update the keystroke editor to turn the `search` bool on
|
// Update the keystroke editor to turn the `search` bool on
|
||||||
self.keystroke_editor.update(cx, |keystroke_editor, cx| {
|
self.keystroke_editor.update(cx, |keystroke_editor, cx| {
|
||||||
|
@ -894,7 +961,7 @@ struct ProcessedKeybinding {
|
||||||
keystroke_text: SharedString,
|
keystroke_text: SharedString,
|
||||||
ui_key_binding: Option<ui::KeyBinding>,
|
ui_key_binding: Option<ui::KeyBinding>,
|
||||||
action_name: SharedString,
|
action_name: SharedString,
|
||||||
action_input: Option<SyntaxHighlightedText>,
|
action_arguments: Option<SyntaxHighlightedText>,
|
||||||
action_docs: Option<&'static str>,
|
action_docs: Option<&'static str>,
|
||||||
action_schema: Option<schemars::Schema>,
|
action_schema: Option<schemars::Schema>,
|
||||||
context: Option<KeybindContextString>,
|
context: Option<KeybindContextString>,
|
||||||
|
@ -1177,8 +1244,8 @@ impl Render for KeymapEditor {
|
||||||
binding.keystroke_text.clone().into_any_element(),
|
binding.keystroke_text.clone().into_any_element(),
|
||||||
IntoElement::into_any_element,
|
IntoElement::into_any_element,
|
||||||
);
|
);
|
||||||
let action_input = match binding.action_input.clone() {
|
let action_arguments = match binding.action_arguments.clone() {
|
||||||
Some(input) => input.into_any_element(),
|
Some(arguments) => arguments.into_any_element(),
|
||||||
None => {
|
None => {
|
||||||
if binding.action_schema.is_some() {
|
if binding.action_schema.is_some() {
|
||||||
muted_styled_text(NO_ACTION_ARGUMENTS_TEXT, cx)
|
muted_styled_text(NO_ACTION_ARGUMENTS_TEXT, cx)
|
||||||
|
@ -1212,7 +1279,14 @@ impl Render for KeymapEditor {
|
||||||
.map(|(_source, name)| name)
|
.map(|(_source, name)| name)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.into_any_element();
|
.into_any_element();
|
||||||
Some([icon, action, action_input, keystrokes, context, source])
|
Some([
|
||||||
|
icon,
|
||||||
|
action,
|
||||||
|
action_arguments,
|
||||||
|
keystrokes,
|
||||||
|
context,
|
||||||
|
source,
|
||||||
|
])
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}),
|
}),
|
||||||
|
@ -1379,7 +1453,7 @@ struct KeybindingEditorModal {
|
||||||
editing_keybind_idx: usize,
|
editing_keybind_idx: usize,
|
||||||
keybind_editor: Entity<KeystrokeInput>,
|
keybind_editor: Entity<KeystrokeInput>,
|
||||||
context_editor: Entity<SingleLineInput>,
|
context_editor: Entity<SingleLineInput>,
|
||||||
input_editor: Option<Entity<Editor>>,
|
action_arguments_editor: Option<Entity<Editor>>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
error: Option<InputError>,
|
error: Option<InputError>,
|
||||||
keymap_editor: Entity<KeymapEditor>,
|
keymap_editor: Entity<KeymapEditor>,
|
||||||
|
@ -1444,16 +1518,16 @@ impl KeybindingEditorModal {
|
||||||
input
|
input
|
||||||
});
|
});
|
||||||
|
|
||||||
let input_editor = editing_keybind.action_schema.clone().map(|_schema| {
|
let action_arguments_editor = editing_keybind.action_schema.clone().map(|_schema| {
|
||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
let mut editor = Editor::auto_height_unbounded(1, window, cx);
|
let mut editor = Editor::auto_height_unbounded(1, window, cx);
|
||||||
let workspace = workspace.clone();
|
let workspace = workspace.clone();
|
||||||
|
|
||||||
if let Some(input) = editing_keybind.action_input.clone() {
|
if let Some(arguments) = editing_keybind.action_arguments.clone() {
|
||||||
editor.set_text(input.text, window, cx);
|
editor.set_text(arguments.text, window, cx);
|
||||||
} else {
|
} else {
|
||||||
// TODO: default value from schema?
|
// TODO: default value from schema?
|
||||||
editor.set_placeholder_text("Action Input", cx);
|
editor.set_placeholder_text("Action Arguments", cx);
|
||||||
}
|
}
|
||||||
cx.spawn(async |editor, cx| {
|
cx.spawn(async |editor, cx| {
|
||||||
let json_language = load_json_language(workspace, cx).await;
|
let json_language = load_json_language(workspace, cx).await;
|
||||||
|
@ -1465,7 +1539,7 @@ impl KeybindingEditorModal {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.context("Failed to load JSON language for editing keybinding action input")
|
.context("Failed to load JSON language for editing keybinding action arguments input")
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
editor
|
editor
|
||||||
|
@ -1479,7 +1553,7 @@ impl KeybindingEditorModal {
|
||||||
fs,
|
fs,
|
||||||
keybind_editor,
|
keybind_editor,
|
||||||
context_editor,
|
context_editor,
|
||||||
input_editor,
|
action_arguments_editor,
|
||||||
error: None,
|
error: None,
|
||||||
keymap_editor,
|
keymap_editor,
|
||||||
workspace,
|
workspace,
|
||||||
|
@ -1500,22 +1574,22 @@ impl KeybindingEditorModal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_action_input(&self, cx: &App) -> anyhow::Result<Option<String>> {
|
fn validate_action_arguments(&self, cx: &App) -> anyhow::Result<Option<String>> {
|
||||||
let input = self
|
let action_arguments = self
|
||||||
.input_editor
|
.action_arguments_editor
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|editor| editor.read(cx).text(cx));
|
.map(|editor| editor.read(cx).text(cx));
|
||||||
|
|
||||||
let value = input
|
let value = action_arguments
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|input| {
|
.map(|args| {
|
||||||
serde_json::from_str(input).context("Failed to parse action input as JSON")
|
serde_json::from_str(args).context("Failed to parse action arguments as JSON")
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
cx.build_action(&self.editing_keybind.action_name, value)
|
cx.build_action(&self.editing_keybind.action_name, value)
|
||||||
.context("Failed to validate action input")?;
|
.context("Failed to validate action arguments")?;
|
||||||
Ok(input)
|
Ok(action_arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&mut self, cx: &mut Context<Self>) {
|
fn save(&mut self, cx: &mut Context<Self>) {
|
||||||
|
@ -1545,7 +1619,7 @@ impl KeybindingEditorModal {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_input = match self.validate_action_input(cx) {
|
let new_action_args = match self.validate_action_arguments(cx) {
|
||||||
Err(input_err) => {
|
Err(input_err) => {
|
||||||
self.set_error(InputError::error(input_err.to_string()), cx);
|
self.set_error(InputError::error(input_err.to_string()), cx);
|
||||||
return;
|
return;
|
||||||
|
@ -1623,12 +1697,14 @@ impl KeybindingEditorModal {
|
||||||
.log_err();
|
.log_err();
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
|
let action_name = existing_keybind.action_name.clone();
|
||||||
|
|
||||||
if let Err(err) = save_keybinding_update(
|
if let Err(err) = save_keybinding_update(
|
||||||
create,
|
create,
|
||||||
existing_keybind,
|
existing_keybind,
|
||||||
&new_keystrokes,
|
&new_keystrokes,
|
||||||
new_context.as_deref(),
|
new_context.as_deref(),
|
||||||
new_input.as_deref(),
|
new_action_args.as_deref(),
|
||||||
&fs,
|
&fs,
|
||||||
tab_size,
|
tab_size,
|
||||||
)
|
)
|
||||||
|
@ -1639,7 +1715,22 @@ impl KeybindingEditorModal {
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
} else {
|
} else {
|
||||||
this.update(cx, |_this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
|
let action_mapping = (
|
||||||
|
ui::text_for_keystrokes(new_keystrokes.as_slice(), cx).into(),
|
||||||
|
new_context.map(SharedString::from),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.keymap_editor.update(cx, |keymap, cx| {
|
||||||
|
keymap.previous_edit = Some(PreviousEdit::Keybinding {
|
||||||
|
action_mapping,
|
||||||
|
action_name,
|
||||||
|
fallback: keymap
|
||||||
|
.table_interaction_state
|
||||||
|
.read(cx)
|
||||||
|
.get_scrollbar_offset(Axis::Vertical),
|
||||||
|
})
|
||||||
|
});
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
@ -1683,7 +1774,7 @@ impl Render for KeybindingEditorModal {
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(self.keybind_editor.clone()),
|
.child(self.keybind_editor.clone()),
|
||||||
)
|
)
|
||||||
.when_some(self.input_editor.clone(), |this, editor| {
|
.when_some(self.action_arguments_editor.clone(), |this, editor| {
|
||||||
this.child(
|
this.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.mt_1p5()
|
.mt_1p5()
|
||||||
|
@ -1865,7 +1956,7 @@ async fn save_keybinding_update(
|
||||||
existing: ProcessedKeybinding,
|
existing: ProcessedKeybinding,
|
||||||
new_keystrokes: &[Keystroke],
|
new_keystrokes: &[Keystroke],
|
||||||
new_context: Option<&str>,
|
new_context: Option<&str>,
|
||||||
new_input: Option<&str>,
|
new_args: Option<&str>,
|
||||||
fs: &Arc<dyn Fs>,
|
fs: &Arc<dyn Fs>,
|
||||||
tab_size: usize,
|
tab_size: usize,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
@ -1879,10 +1970,10 @@ async fn save_keybinding_update(
|
||||||
.context
|
.context
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(KeybindContextString::local_str);
|
.and_then(KeybindContextString::local_str);
|
||||||
let existing_input = existing
|
let existing_args = existing
|
||||||
.action_input
|
.action_arguments
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|input| input.text.as_ref());
|
.map(|args| args.text.as_ref());
|
||||||
|
|
||||||
settings::KeybindUpdateOperation::Replace {
|
settings::KeybindUpdateOperation::Replace {
|
||||||
target: settings::KeybindUpdateTarget {
|
target: settings::KeybindUpdateTarget {
|
||||||
|
@ -1890,7 +1981,7 @@ async fn save_keybinding_update(
|
||||||
keystrokes: existing_keystrokes,
|
keystrokes: existing_keystrokes,
|
||||||
action_name: &existing.action_name,
|
action_name: &existing.action_name,
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: existing_input,
|
action_arguments: existing_args,
|
||||||
},
|
},
|
||||||
target_keybind_source: existing
|
target_keybind_source: existing
|
||||||
.source
|
.source
|
||||||
|
@ -1902,7 +1993,7 @@ async fn save_keybinding_update(
|
||||||
keystrokes: new_keystrokes,
|
keystrokes: new_keystrokes,
|
||||||
action_name: &existing.action_name,
|
action_name: &existing.action_name,
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: new_input,
|
action_arguments: new_args,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1911,15 +2002,18 @@ async fn save_keybinding_update(
|
||||||
keystrokes: new_keystrokes,
|
keystrokes: new_keystrokes,
|
||||||
action_name: &existing.action_name,
|
action_name: &existing.action_name,
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: new_input,
|
action_arguments: new_args,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let updated_keymap_contents =
|
let updated_keymap_contents =
|
||||||
settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
|
settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
|
||||||
.context("Failed to update keybinding")?;
|
.context("Failed to update keybinding")?;
|
||||||
fs.atomic_write(paths::keymap_file().clone(), updated_keymap_contents)
|
fs.write(
|
||||||
.await
|
paths::keymap_file().as_path(),
|
||||||
.context("Failed to write keymap file")?;
|
updated_keymap_contents.as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("Failed to write keymap file")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1944,10 +2038,10 @@ async fn remove_keybinding(
|
||||||
keystrokes,
|
keystrokes,
|
||||||
action_name: &existing.action_name,
|
action_name: &existing.action_name,
|
||||||
use_key_equivalents: false,
|
use_key_equivalents: false,
|
||||||
input: existing
|
action_arguments: existing
|
||||||
.action_input
|
.action_arguments
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|input| input.text.as_ref()),
|
.map(|arguments| arguments.text.as_ref()),
|
||||||
},
|
},
|
||||||
target_keybind_source: existing
|
target_keybind_source: existing
|
||||||
.source
|
.source
|
||||||
|
@ -1959,9 +2053,12 @@ async fn remove_keybinding(
|
||||||
let updated_keymap_contents =
|
let updated_keymap_contents =
|
||||||
settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
|
settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
|
||||||
.context("Failed to update keybinding")?;
|
.context("Failed to update keybinding")?;
|
||||||
fs.atomic_write(paths::keymap_file().clone(), updated_keymap_contents)
|
fs.write(
|
||||||
.await
|
paths::keymap_file().as_path(),
|
||||||
.context("Failed to write keymap file")?;
|
updated_keymap_contents.as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("Failed to write keymap file")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ use std::{ops::Range, rc::Rc, time::Duration};
|
||||||
use editor::{EditorSettings, ShowScrollbar, scroll::ScrollbarAutoHide};
|
use editor::{EditorSettings, ShowScrollbar, scroll::ScrollbarAutoHide};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext, Axis, Context, Entity, FocusHandle, Length, ListHorizontalSizingBehavior,
|
AppContext, Axis, Context, Entity, FocusHandle, Length, ListHorizontalSizingBehavior,
|
||||||
ListSizingBehavior, MouseButton, Task, UniformListScrollHandle, WeakEntity, transparent_black,
|
ListSizingBehavior, MouseButton, Point, Task, UniformListScrollHandle, WeakEntity,
|
||||||
uniform_list,
|
transparent_black, uniform_list,
|
||||||
};
|
};
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
use ui::{
|
use ui::{
|
||||||
|
@ -90,6 +90,28 @@ impl TableInteractionState {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_scrollbar_offset(&self, axis: Axis) -> Point<Pixels> {
|
||||||
|
match axis {
|
||||||
|
Axis::Vertical => self.vertical_scrollbar.state.scroll_handle().offset(),
|
||||||
|
Axis::Horizontal => self.horizontal_scrollbar.state.scroll_handle().offset(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_scrollbar_offset(&self, axis: Axis, offset: Point<Pixels>) {
|
||||||
|
match axis {
|
||||||
|
Axis::Vertical => self
|
||||||
|
.vertical_scrollbar
|
||||||
|
.state
|
||||||
|
.scroll_handle()
|
||||||
|
.set_offset(offset),
|
||||||
|
Axis::Horizontal => self
|
||||||
|
.horizontal_scrollbar
|
||||||
|
.state
|
||||||
|
.scroll_handle()
|
||||||
|
.set_offset(offset),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn update_scrollbar_visibility(&mut self, cx: &mut Context<Self>) {
|
fn update_scrollbar_visibility(&mut self, cx: &mut Context<Self>) {
|
||||||
let show_setting = EditorSettings::get_global(cx).scrollbar.show;
|
let show_setting = EditorSettings::get_global(cx).scrollbar.show;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue