Add KeyContextView (#19872)
Release Notes: - Added `cmd-shift-p debug: Open Key Context View` to help debug custom key bindings https://github.com/user-attachments/assets/de273c97-5b27-45aa-9ff1-f943b0ed7dfe
This commit is contained in:
parent
cf7b0c8971
commit
ce5222f1df
12 changed files with 390 additions and 8 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -6346,6 +6346,7 @@ dependencies = [
|
||||||
"env_logger 0.11.5",
|
"env_logger 0.11.5",
|
||||||
"futures 0.3.30",
|
"futures 0.3.30",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
"itertools 0.13.0",
|
||||||
"language",
|
"language",
|
||||||
"lsp",
|
"lsp",
|
||||||
"project",
|
"project",
|
||||||
|
@ -6357,6 +6358,7 @@ dependencies = [
|
||||||
"ui",
|
"ui",
|
||||||
"util",
|
"util",
|
||||||
"workspace",
|
"workspace",
|
||||||
|
"zed_actions",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -75,6 +75,18 @@ impl Keymap {
|
||||||
.filter(move |binding| binding.action().partial_eq(action))
|
.filter(move |binding| binding.action().partial_eq(action))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// all bindings for input returns all bindings that might match the input
|
||||||
|
/// (without checking context)
|
||||||
|
pub fn all_bindings_for_input(&self, input: &[Keystroke]) -> Vec<KeyBinding> {
|
||||||
|
self.bindings()
|
||||||
|
.rev()
|
||||||
|
.filter_map(|binding| {
|
||||||
|
binding.match_keystrokes(input).filter(|pending| !pending)?;
|
||||||
|
Some(binding.clone())
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// bindings_for_input returns a list of bindings that match the given input,
|
/// bindings_for_input returns a list of bindings that match the given input,
|
||||||
/// and a boolean indicating whether or not more bindings might match if
|
/// and a boolean indicating whether or not more bindings might match if
|
||||||
/// the input was longer.
|
/// the input was longer.
|
||||||
|
|
|
@ -69,6 +69,11 @@ impl KeyBinding {
|
||||||
pub fn action(&self) -> &dyn Action {
|
pub fn action(&self) -> &dyn Action {
|
||||||
self.action.as_ref()
|
self.action.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the predicate used to match this binding
|
||||||
|
pub fn predicate(&self) -> Option<&KeyBindingContextPredicate> {
|
||||||
|
self.context_predicate.as_ref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for KeyBinding {
|
impl std::fmt::Debug for KeyBinding {
|
||||||
|
|
|
@ -11,9 +11,12 @@ use std::fmt;
|
||||||
pub struct KeyContext(SmallVec<[ContextEntry; 1]>);
|
pub struct KeyContext(SmallVec<[ContextEntry; 1]>);
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
struct ContextEntry {
|
/// An entry in a KeyContext
|
||||||
key: SharedString,
|
pub struct ContextEntry {
|
||||||
value: Option<SharedString>,
|
/// The key (or name if no value)
|
||||||
|
pub key: SharedString,
|
||||||
|
/// The value
|
||||||
|
pub value: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a str> for KeyContext {
|
impl<'a> TryFrom<&'a str> for KeyContext {
|
||||||
|
@ -39,6 +42,17 @@ impl KeyContext {
|
||||||
context
|
context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the primary context entry (usually the name of the component)
|
||||||
|
pub fn primary(&self) -> Option<&ContextEntry> {
|
||||||
|
self.0.iter().find(|p| p.value.is_none())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns everything except the primary context entry.
|
||||||
|
pub fn secondary(&self) -> impl Iterator<Item = &ContextEntry> {
|
||||||
|
let primary = self.primary();
|
||||||
|
self.0.iter().filter(move |&p| Some(p) != primary)
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a key context from a string.
|
/// Parse a key context from a string.
|
||||||
/// The key context format is very simple:
|
/// The key context format is very simple:
|
||||||
/// - either a single identifier, such as `StatusBar`
|
/// - either a single identifier, such as `StatusBar`
|
||||||
|
@ -178,6 +192,20 @@ pub enum KeyBindingContextPredicate {
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for KeyBindingContextPredicate {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Identifier(name) => write!(f, "{}", name),
|
||||||
|
Self::Equal(left, right) => write!(f, "{} == {}", left, right),
|
||||||
|
Self::NotEqual(left, right) => write!(f, "{} != {}", left, right),
|
||||||
|
Self::Not(pred) => write!(f, "!{}", pred),
|
||||||
|
Self::Child(parent, child) => write!(f, "{} > {}", parent, child),
|
||||||
|
Self::And(left, right) => write!(f, "({} && {})", left, right),
|
||||||
|
Self::Or(left, right) => write!(f, "({} || {})", left, right),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl KeyBindingContextPredicate {
|
impl KeyBindingContextPredicate {
|
||||||
/// Parse a string in the same format as the keymap's context field.
|
/// Parse a string in the same format as the keymap's context field.
|
||||||
///
|
///
|
||||||
|
|
|
@ -121,6 +121,32 @@ impl Keystroke {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Produces a representation of this key that Parse can understand.
|
||||||
|
pub fn unparse(&self) -> String {
|
||||||
|
let mut str = String::new();
|
||||||
|
if self.modifiers.control {
|
||||||
|
str.push_str("ctrl-");
|
||||||
|
}
|
||||||
|
if self.modifiers.alt {
|
||||||
|
str.push_str("alt-");
|
||||||
|
}
|
||||||
|
if self.modifiers.platform {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
str.push_str("cmd-");
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
str.push_str("super-");
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
str.push_str("win-");
|
||||||
|
}
|
||||||
|
if self.modifiers.shift {
|
||||||
|
str.push_str("shift-");
|
||||||
|
}
|
||||||
|
str.push_str(&self.key);
|
||||||
|
str
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if this keystroke left
|
/// Returns true if this keystroke left
|
||||||
/// the ime system in an incomplete state.
|
/// the ime system in an incomplete state.
|
||||||
pub fn is_ime_in_progress(&self) -> bool {
|
pub fn is_ime_in_progress(&self) -> bool {
|
||||||
|
|
|
@ -3324,17 +3324,18 @@ impl<'a> WindowContext<'a> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pending_input_changed();
|
|
||||||
self.propagate_event = true;
|
self.propagate_event = true;
|
||||||
for binding in match_result.bindings {
|
for binding in match_result.bindings {
|
||||||
self.dispatch_action_on_node(node_id, binding.action.as_ref());
|
self.dispatch_action_on_node(node_id, binding.action.as_ref());
|
||||||
if !self.propagate_event {
|
if !self.propagate_event {
|
||||||
self.dispatch_keystroke_observers(event, Some(binding.action));
|
self.dispatch_keystroke_observers(event, Some(binding.action));
|
||||||
|
self.pending_input_changed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.finish_dispatch_key_event(event, dispatch_path)
|
self.finish_dispatch_key_event(event, dispatch_path);
|
||||||
|
self.pending_input_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish_dispatch_key_event(
|
fn finish_dispatch_key_event(
|
||||||
|
@ -3664,6 +3665,22 @@ impl<'a> WindowContext<'a> {
|
||||||
receiver
|
receiver
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the current context stack.
|
||||||
|
pub fn context_stack(&self) -> Vec<KeyContext> {
|
||||||
|
let dispatch_tree = &self.window.rendered_frame.dispatch_tree;
|
||||||
|
let node_id = self
|
||||||
|
.window
|
||||||
|
.focus
|
||||||
|
.and_then(|focus_id| dispatch_tree.focusable_node_id(focus_id))
|
||||||
|
.unwrap_or_else(|| dispatch_tree.root_node_id());
|
||||||
|
|
||||||
|
dispatch_tree
|
||||||
|
.dispatch_path(node_id)
|
||||||
|
.iter()
|
||||||
|
.filter_map(move |&node_id| dispatch_tree.node(node_id).context.clone())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns all available actions for the focused element.
|
/// Returns all available actions for the focused element.
|
||||||
pub fn available_actions(&self) -> Vec<Box<dyn Action>> {
|
pub fn available_actions(&self) -> Vec<Box<dyn Action>> {
|
||||||
let node_id = self
|
let node_id = self
|
||||||
|
@ -3704,6 +3721,11 @@ impl<'a> WindowContext<'a> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns key bindings that invoke the given action on the currently focused element.
|
||||||
|
pub fn all_bindings_for_input(&self, input: &[Keystroke]) -> Vec<KeyBinding> {
|
||||||
|
RefCell::borrow(&self.keymap).all_bindings_for_input(input)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns any bindings that would invoke the given action on the given focus handle if it were focused.
|
/// Returns any bindings that would invoke the given action on the given focus handle if it were focused.
|
||||||
pub fn bindings_for_action_in(
|
pub fn bindings_for_action_in(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -19,6 +19,7 @@ copilot.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
itertools.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
lsp.workspace = true
|
lsp.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
|
@ -28,6 +29,7 @@ theme.workspace = true
|
||||||
tree-sitter.workspace = true
|
tree-sitter.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
zed_actions.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
client = { workspace = true, features = ["test-support"] }
|
client = { workspace = true, features = ["test-support"] }
|
||||||
|
|
280
crates/language_tools/src/key_context_view.rs
Normal file
280
crates/language_tools/src/key_context_view.rs
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
use gpui::{
|
||||||
|
actions, Action, AppContext, EventEmitter, FocusHandle, FocusableView,
|
||||||
|
KeyBindingContextPredicate, KeyContext, Keystroke, MouseButton, Render, Subscription,
|
||||||
|
};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use serde_json::json;
|
||||||
|
use ui::{
|
||||||
|
div, h_flex, px, v_flex, ButtonCommon, Clickable, FluentBuilder, InteractiveElement, Label,
|
||||||
|
LabelCommon, LabelSize, ParentElement, SharedString, StatefulInteractiveElement, Styled,
|
||||||
|
ViewContext, VisualContext, WindowContext,
|
||||||
|
};
|
||||||
|
use ui::{Button, ButtonStyle};
|
||||||
|
use workspace::Item;
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
actions!(debug, [OpenKeyContextView]);
|
||||||
|
|
||||||
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
cx.observe_new_views(|workspace: &mut Workspace, _| {
|
||||||
|
workspace.register_action(|workspace, _: &OpenKeyContextView, cx| {
|
||||||
|
let key_context_view = cx.new_view(KeyContextView::new);
|
||||||
|
workspace.add_item_to_active_pane(Box::new(key_context_view), None, true, cx)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct KeyContextView {
|
||||||
|
pending_keystrokes: Option<Vec<Keystroke>>,
|
||||||
|
last_keystrokes: Option<SharedString>,
|
||||||
|
last_possibilities: Vec<(SharedString, SharedString, Option<bool>)>,
|
||||||
|
context_stack: Vec<KeyContext>,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
_subscriptions: [Subscription; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyContextView {
|
||||||
|
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||||
|
let sub1 = cx.observe_keystrokes(|this, e, cx| {
|
||||||
|
let mut pending = this.pending_keystrokes.take().unwrap_or_default();
|
||||||
|
pending.push(e.keystroke.clone());
|
||||||
|
let mut possibilities = cx.all_bindings_for_input(&pending);
|
||||||
|
possibilities.reverse();
|
||||||
|
this.context_stack = cx.context_stack();
|
||||||
|
this.last_keystrokes = Some(
|
||||||
|
json!(pending.iter().map(|p| p.unparse()).join(" "))
|
||||||
|
.to_string()
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
this.last_possibilities = possibilities
|
||||||
|
.into_iter()
|
||||||
|
.map(|binding| {
|
||||||
|
let match_state = if let Some(predicate) = binding.predicate() {
|
||||||
|
if this.matches(predicate) {
|
||||||
|
if this.action_matches(&e.action, binding.action()) {
|
||||||
|
Some(true)
|
||||||
|
} else {
|
||||||
|
Some(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if this.action_matches(&e.action, binding.action()) {
|
||||||
|
Some(true)
|
||||||
|
} else {
|
||||||
|
Some(false)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let predicate = if let Some(predicate) = binding.predicate() {
|
||||||
|
format!("{}", predicate)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
|
let mut name = binding.action().name();
|
||||||
|
if name == "zed::NoAction" {
|
||||||
|
name = "(null)"
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
name.to_owned().into(),
|
||||||
|
json!(predicate).to_string().into(),
|
||||||
|
match_state,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
});
|
||||||
|
let sub2 = cx.observe_pending_input(|this, cx| {
|
||||||
|
this.pending_keystrokes = cx
|
||||||
|
.pending_input_keystrokes()
|
||||||
|
.map(|k| k.iter().cloned().collect());
|
||||||
|
if this.pending_keystrokes.is_some() {
|
||||||
|
this.last_keystrokes.take();
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
context_stack: Vec::new(),
|
||||||
|
pending_keystrokes: None,
|
||||||
|
last_keystrokes: None,
|
||||||
|
last_possibilities: Vec::new(),
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
_subscriptions: [sub1, sub2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<()> for KeyContextView {}
|
||||||
|
|
||||||
|
impl FocusableView for KeyContextView {
|
||||||
|
fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl KeyContextView {
|
||||||
|
fn set_context_stack(&mut self, stack: Vec<KeyContext>, cx: &mut ViewContext<Self>) {
|
||||||
|
self.context_stack = stack;
|
||||||
|
cx.notify()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches(&self, predicate: &KeyBindingContextPredicate) -> bool {
|
||||||
|
let mut stack = self.context_stack.clone();
|
||||||
|
while !stack.is_empty() {
|
||||||
|
if predicate.eval(&stack) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn action_matches(&self, a: &Option<Box<dyn Action>>, b: &dyn Action) -> bool {
|
||||||
|
if let Some(last_action) = a {
|
||||||
|
last_action.partial_eq(b)
|
||||||
|
} else {
|
||||||
|
b.name() == "zed::NoAction"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Item for KeyContextView {
|
||||||
|
type Event = ();
|
||||||
|
|
||||||
|
fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {}
|
||||||
|
|
||||||
|
fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
|
||||||
|
Some("Keyboard Context".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone_on_split(
|
||||||
|
&self,
|
||||||
|
_workspace_id: Option<workspace::WorkspaceId>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Option<gpui::View<Self>>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Some(cx.new_view(Self::new))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for KeyContextView {
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl ui::IntoElement {
|
||||||
|
use itertools::Itertools;
|
||||||
|
v_flex()
|
||||||
|
.id("key-context-view")
|
||||||
|
.overflow_scroll()
|
||||||
|
.size_full()
|
||||||
|
.max_h_full()
|
||||||
|
.pt_4()
|
||||||
|
.pl_4()
|
||||||
|
.track_focus(&self.focus_handle)
|
||||||
|
.key_context("KeyContextView")
|
||||||
|
.on_mouse_up_out(
|
||||||
|
MouseButton::Left,
|
||||||
|
cx.listener(|this, _, cx| {
|
||||||
|
this.last_keystrokes.take();
|
||||||
|
this.set_context_stack(cx.context_stack(), cx);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.on_mouse_up_out(
|
||||||
|
MouseButton::Right,
|
||||||
|
cx.listener(|_, _, cx| {
|
||||||
|
cx.defer(|this, cx| {
|
||||||
|
this.last_keystrokes.take();
|
||||||
|
this.set_context_stack(cx.context_stack(), cx);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(Label::new("Keyboard Context").size(LabelSize::Large))
|
||||||
|
.child(Label::new("This view lets you determine the current context stack for creating custom key bindings in Zed. When a keyboard shortcut is triggered, it also shows all the possible contexts it could have triggered in, and which one matched."))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.mt_4()
|
||||||
|
.gap_4()
|
||||||
|
.child(
|
||||||
|
Button::new("default", "Open Documentation")
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.on_click(|_, cx| cx.open_url("https://zed.dev/docs/key-bindings")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("default", "View default keymap")
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.key_binding(ui::KeyBinding::for_action(
|
||||||
|
&zed_actions::OpenDefaultKeymap,
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
.on_click(|_, cx| {
|
||||||
|
cx.dispatch_action(workspace::SplitRight.boxed_clone());
|
||||||
|
cx.dispatch_action(zed_actions::OpenDefaultKeymap.boxed_clone());
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("default", "Edit your keymap")
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.key_binding(ui::KeyBinding::for_action(&zed_actions::OpenKeymap, cx))
|
||||||
|
.on_click(|_, cx| {
|
||||||
|
cx.dispatch_action(workspace::SplitRight.boxed_clone());
|
||||||
|
cx.dispatch_action(zed_actions::OpenKeymap.boxed_clone());
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new("Current Context Stack")
|
||||||
|
.size(LabelSize::Large)
|
||||||
|
.mt_8(),
|
||||||
|
)
|
||||||
|
.children({
|
||||||
|
cx.context_stack().iter().enumerate().map(|(i, context)| {
|
||||||
|
let primary = context.primary().map(|e| e.key.clone()).unwrap_or_default();
|
||||||
|
let secondary = context
|
||||||
|
.secondary()
|
||||||
|
.map(|e| {
|
||||||
|
if let Some(value) = e.value.as_ref() {
|
||||||
|
format!("{}={}", e.key, value)
|
||||||
|
} else {
|
||||||
|
e.key.to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join(" ");
|
||||||
|
Label::new(format!("{} {}", primary, secondary)).ml(px(12. * (i + 1) as f32))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.child(Label::new("Last Keystroke").mt_4().size(LabelSize::Large))
|
||||||
|
.when_some(self.pending_keystrokes.as_ref(), |el, keystrokes| {
|
||||||
|
el.child(
|
||||||
|
Label::new(format!(
|
||||||
|
"Waiting for more input: {}",
|
||||||
|
keystrokes.iter().map(|k| k.unparse()).join(" ")
|
||||||
|
))
|
||||||
|
.ml(px(12.)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when_some(self.last_keystrokes.as_ref(), |el, keystrokes| {
|
||||||
|
el.child(Label::new(format!("Typed: {}", keystrokes)).ml_4())
|
||||||
|
.children(
|
||||||
|
self.last_possibilities
|
||||||
|
.iter()
|
||||||
|
.map(|(name, predicate, state)| {
|
||||||
|
let (text, color) = match state {
|
||||||
|
Some(true) => ("(match)", ui::Color::Success),
|
||||||
|
Some(false) => ("(low precedence)", ui::Color::Hint),
|
||||||
|
None => ("(no match)", ui::Color::Error),
|
||||||
|
};
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.ml_8()
|
||||||
|
.child(div().min_w(px(200.)).child(Label::new(name.clone())))
|
||||||
|
.child(Label::new(predicate.clone()))
|
||||||
|
.child(Label::new(text).color(color))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod key_context_view;
|
||||||
mod lsp_log;
|
mod lsp_log;
|
||||||
mod syntax_tree_view;
|
mod syntax_tree_view;
|
||||||
|
|
||||||
|
@ -12,4 +13,5 @@ pub use syntax_tree_view::{SyntaxTreeToolbarItemView, SyntaxTreeView};
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
lsp_log::init(cx);
|
lsp_log::init(cx);
|
||||||
syntax_tree_view::init(cx);
|
syntax_tree_view::init(cx);
|
||||||
|
key_context_view::init(cx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,6 @@ actions!(
|
||||||
Hide,
|
Hide,
|
||||||
HideOthers,
|
HideOthers,
|
||||||
Minimize,
|
Minimize,
|
||||||
OpenDefaultKeymap,
|
|
||||||
OpenDefaultSettings,
|
OpenDefaultSettings,
|
||||||
OpenProjectSettings,
|
OpenProjectSettings,
|
||||||
OpenProjectTasks,
|
OpenProjectTasks,
|
||||||
|
@ -474,7 +473,7 @@ pub fn initialize_workspace(
|
||||||
.register_action(open_project_tasks_file)
|
.register_action(open_project_tasks_file)
|
||||||
.register_action(
|
.register_action(
|
||||||
move |workspace: &mut Workspace,
|
move |workspace: &mut Workspace,
|
||||||
_: &OpenDefaultKeymap,
|
_: &zed_actions::OpenDefaultKeymap,
|
||||||
cx: &mut ViewContext<Workspace>| {
|
cx: &mut ViewContext<Workspace>| {
|
||||||
open_bundled_file(
|
open_bundled_file(
|
||||||
workspace,
|
workspace,
|
||||||
|
|
|
@ -18,7 +18,10 @@ pub fn app_menus() -> Vec<Menu> {
|
||||||
MenuItem::action("Open Settings", super::OpenSettings),
|
MenuItem::action("Open Settings", super::OpenSettings),
|
||||||
MenuItem::action("Open Key Bindings", zed_actions::OpenKeymap),
|
MenuItem::action("Open Key Bindings", zed_actions::OpenKeymap),
|
||||||
MenuItem::action("Open Default Settings", super::OpenDefaultSettings),
|
MenuItem::action("Open Default Settings", super::OpenDefaultSettings),
|
||||||
MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap),
|
MenuItem::action(
|
||||||
|
"Open Default Key Bindings",
|
||||||
|
zed_actions::OpenDefaultKeymap,
|
||||||
|
),
|
||||||
MenuItem::action("Open Project Settings", super::OpenProjectSettings),
|
MenuItem::action("Open Project Settings", super::OpenProjectSettings),
|
||||||
MenuItem::action("Select Theme...", theme_selector::Toggle::default()),
|
MenuItem::action("Select Theme...", theme_selector::Toggle::default()),
|
||||||
],
|
],
|
||||||
|
|
|
@ -26,6 +26,7 @@ actions!(
|
||||||
zed,
|
zed,
|
||||||
[
|
[
|
||||||
OpenSettings,
|
OpenSettings,
|
||||||
|
OpenDefaultKeymap,
|
||||||
OpenAccountSettings,
|
OpenAccountSettings,
|
||||||
OpenServerSettings,
|
OpenServerSettings,
|
||||||
Quit,
|
Quit,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue