keymap_ui: Hover tooltip for action documentation (#33862)
Closes #ISSUE Show the documentation for an action when hovered. As a bonus, also show the humanized command palette name! Release Notes: - N/A *or* Added/Fixed/Improved ...
This commit is contained in:
parent
34322ef1cd
commit
4e6b7ee3ea
4 changed files with 44 additions and 7 deletions
|
@ -225,6 +225,7 @@ pub(crate) struct ActionRegistry {
|
||||||
all_names: Vec<&'static str>, // So we can return a static slice.
|
all_names: Vec<&'static str>, // So we can return a static slice.
|
||||||
deprecated_aliases: HashMap<&'static str, &'static str>, // deprecated name -> preferred name
|
deprecated_aliases: HashMap<&'static str, &'static str>, // deprecated name -> preferred name
|
||||||
deprecation_messages: HashMap<&'static str, &'static str>, // action name -> deprecation message
|
deprecation_messages: HashMap<&'static str, &'static str>, // action name -> deprecation message
|
||||||
|
documentation: HashMap<&'static str, &'static str>, // action name -> documentation
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ActionRegistry {
|
impl Default for ActionRegistry {
|
||||||
|
@ -232,6 +233,7 @@ impl Default for ActionRegistry {
|
||||||
let mut this = ActionRegistry {
|
let mut this = ActionRegistry {
|
||||||
by_name: Default::default(),
|
by_name: Default::default(),
|
||||||
names_by_type_id: Default::default(),
|
names_by_type_id: Default::default(),
|
||||||
|
documentation: Default::default(),
|
||||||
all_names: Default::default(),
|
all_names: Default::default(),
|
||||||
deprecated_aliases: Default::default(),
|
deprecated_aliases: Default::default(),
|
||||||
deprecation_messages: Default::default(),
|
deprecation_messages: Default::default(),
|
||||||
|
@ -327,6 +329,9 @@ impl ActionRegistry {
|
||||||
if let Some(deprecation_msg) = action.deprecation_message {
|
if let Some(deprecation_msg) = action.deprecation_message {
|
||||||
self.deprecation_messages.insert(name, deprecation_msg);
|
self.deprecation_messages.insert(name, deprecation_msg);
|
||||||
}
|
}
|
||||||
|
if let Some(documentation) = action.documentation {
|
||||||
|
self.documentation.insert(name, documentation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
|
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
|
||||||
|
@ -388,6 +393,10 @@ impl ActionRegistry {
|
||||||
pub fn deprecation_messages(&self) -> &HashMap<&'static str, &'static str> {
|
pub fn deprecation_messages(&self) -> &HashMap<&'static str, &'static str> {
|
||||||
&self.deprecation_messages
|
&self.deprecation_messages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn documentation(&self) -> &HashMap<&'static str, &'static str> {
|
||||||
|
&self.documentation
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a list of all the registered actions.
|
/// Generate a list of all the registered actions.
|
||||||
|
|
|
@ -1403,11 +1403,16 @@ impl App {
|
||||||
self.actions.deprecated_aliases()
|
self.actions.deprecated_aliases()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a list of all action deprecation messages.
|
/// Get a map from an action name to the deprecation messages.
|
||||||
pub fn action_deprecation_messages(&self) -> &HashMap<&'static str, &'static str> {
|
pub fn action_deprecation_messages(&self) -> &HashMap<&'static str, &'static str> {
|
||||||
self.actions.deprecation_messages()
|
self.actions.deprecation_messages()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a map from an action name to the documentation.
|
||||||
|
pub fn action_documentation(&self) -> &HashMap<&'static str, &'static str> {
|
||||||
|
self.actions.documentation()
|
||||||
|
}
|
||||||
|
|
||||||
/// Register a callback to be invoked when the application is about to quit.
|
/// Register a callback to be invoked when the application is about to quit.
|
||||||
/// It is not possible to cancel the quit event at this point.
|
/// It is not possible to cancel the quit event at this point.
|
||||||
pub fn on_app_quit<Fut>(
|
pub fn on_app_quit<Fut>(
|
||||||
|
|
|
@ -254,7 +254,9 @@ impl KeymapEditor {
|
||||||
let key_bindings_ptr = cx.key_bindings();
|
let key_bindings_ptr = cx.key_bindings();
|
||||||
let lock = key_bindings_ptr.borrow();
|
let lock = key_bindings_ptr.borrow();
|
||||||
let key_bindings = lock.bindings();
|
let key_bindings = lock.bindings();
|
||||||
let mut unmapped_action_names = HashSet::from_iter(cx.all_action_names());
|
let mut unmapped_action_names =
|
||||||
|
HashSet::from_iter(cx.all_action_names().into_iter().copied());
|
||||||
|
let action_documentation = cx.action_documentation();
|
||||||
|
|
||||||
let mut processed_bindings = Vec::new();
|
let mut processed_bindings = Vec::new();
|
||||||
let mut string_match_candidates = Vec::new();
|
let mut string_match_candidates = Vec::new();
|
||||||
|
@ -280,6 +282,7 @@ impl KeymapEditor {
|
||||||
let action_input = key_binding
|
let action_input = key_binding
|
||||||
.action_input()
|
.action_input()
|
||||||
.map(|input| SyntaxHighlightedText::new(input, json_language.clone()));
|
.map(|input| SyntaxHighlightedText::new(input, json_language.clone()));
|
||||||
|
let action_docs = action_documentation.get(action_name).copied();
|
||||||
|
|
||||||
let index = processed_bindings.len();
|
let index = processed_bindings.len();
|
||||||
let string_match_candidate = StringMatchCandidate::new(index, &action_name);
|
let string_match_candidate = StringMatchCandidate::new(index, &action_name);
|
||||||
|
@ -288,6 +291,7 @@ impl KeymapEditor {
|
||||||
ui_key_binding,
|
ui_key_binding,
|
||||||
action: action_name.into(),
|
action: action_name.into(),
|
||||||
action_input,
|
action_input,
|
||||||
|
action_docs,
|
||||||
context: Some(context),
|
context: Some(context),
|
||||||
source,
|
source,
|
||||||
});
|
});
|
||||||
|
@ -301,8 +305,9 @@ impl KeymapEditor {
|
||||||
processed_bindings.push(ProcessedKeybinding {
|
processed_bindings.push(ProcessedKeybinding {
|
||||||
keystroke_text: empty.clone(),
|
keystroke_text: empty.clone(),
|
||||||
ui_key_binding: None,
|
ui_key_binding: None,
|
||||||
action: (*action_name).into(),
|
action: action_name.into(),
|
||||||
action_input: None,
|
action_input: None,
|
||||||
|
action_docs: action_documentation.get(action_name).copied(),
|
||||||
context: None,
|
context: None,
|
||||||
source: None,
|
source: None,
|
||||||
});
|
});
|
||||||
|
@ -537,6 +542,7 @@ struct ProcessedKeybinding {
|
||||||
ui_key_binding: Option<ui::KeyBinding>,
|
ui_key_binding: Option<ui::KeyBinding>,
|
||||||
action: SharedString,
|
action: SharedString,
|
||||||
action_input: Option<SyntaxHighlightedText>,
|
action_input: Option<SyntaxHighlightedText>,
|
||||||
|
action_docs: Option<&'static str>,
|
||||||
context: Option<KeybindContextString>,
|
context: Option<KeybindContextString>,
|
||||||
source: Option<(KeybindSource, SharedString)>,
|
source: Option<(KeybindSource, SharedString)>,
|
||||||
}
|
}
|
||||||
|
@ -635,7 +641,26 @@ impl Render for KeymapEditor {
|
||||||
let candidate_id = this.matches.get(index)?.candidate_id;
|
let candidate_id = this.matches.get(index)?.candidate_id;
|
||||||
let binding = &this.keybindings[candidate_id];
|
let binding = &this.keybindings[candidate_id];
|
||||||
|
|
||||||
let action = binding.action.clone().into_any_element();
|
let action = div()
|
||||||
|
.child(binding.action.clone())
|
||||||
|
.id(("keymap action", index))
|
||||||
|
.tooltip({
|
||||||
|
let action_name = binding.action.clone();
|
||||||
|
let action_docs = binding.action_docs;
|
||||||
|
move |_, cx| {
|
||||||
|
let action_tooltip = Tooltip::new(
|
||||||
|
command_palette::humanize_action_name(
|
||||||
|
&action_name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let action_tooltip = match action_docs {
|
||||||
|
Some(docs) => action_tooltip.meta(docs),
|
||||||
|
None => action_tooltip,
|
||||||
|
};
|
||||||
|
cx.new(|_| action_tooltip).into()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into_any_element();
|
||||||
let keystrokes = binding.ui_key_binding.clone().map_or(
|
let keystrokes = binding.ui_key_binding.clone().map_or(
|
||||||
binding.keystroke_text.clone().into_any_element(),
|
binding.keystroke_text.clone().into_any_element(),
|
||||||
IntoElement::into_any_element,
|
IntoElement::into_any_element,
|
||||||
|
|
|
@ -12,8 +12,7 @@ use ui::{
|
||||||
ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator,
|
ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator,
|
||||||
InteractiveElement as _, IntoElement, ParentElement, Pixels, RegisterComponent, RenderOnce,
|
InteractiveElement as _, IntoElement, ParentElement, Pixels, RegisterComponent, RenderOnce,
|
||||||
Scrollbar, ScrollbarState, StatefulInteractiveElement as _, Styled, StyledExt as _,
|
Scrollbar, ScrollbarState, StatefulInteractiveElement as _, Styled, StyledExt as _,
|
||||||
StyledTypography, Tooltip, Window, div, example_group_with_title, h_flex, px, single_example,
|
StyledTypography, Window, div, example_group_with_title, h_flex, px, single_example, v_flex,
|
||||||
v_flex,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UniformListData<const COLS: usize> {
|
struct UniformListData<const COLS: usize> {
|
||||||
|
@ -474,7 +473,6 @@ pub fn render_row<const COLS: usize>(
|
||||||
let row = div().w_full().child(
|
let row = div().w_full().child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("table_row")
|
.id("table_row")
|
||||||
.tooltip(Tooltip::text("Hit enter to edit"))
|
|
||||||
.w_full()
|
.w_full()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.px_1p5()
|
.px_1p5()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue