Cargo.lock 🔗
@@ -14576,6 +14576,7 @@ dependencies = [
"fuzzy",
"gpui",
"log",
+ "menu",
"paths",
"project",
"schemars",
Ben Kunkle created
Cargo.lock | 1
crates/settings_ui/Cargo.toml | 1
crates/settings_ui/src/keybindings.rs | 77 ++++++++++++++++++++
crates/settings_ui/src/ui_components/table.rs | 15 ++++
4 files changed, 91 insertions(+), 3 deletions(-)
@@ -14576,6 +14576,7 @@ dependencies = [
"fuzzy",
"gpui",
"log",
+ "menu",
"paths",
"project",
"schemars",
@@ -22,6 +22,7 @@ fs.workspace = true
fuzzy.workspace = true
gpui.workspace = true
log.workspace = true
+menu.workspace = true
paths.workspace = true
project.workspace = true
schemars.workspace = true
@@ -4,8 +4,8 @@ use db::anyhow::anyhow;
use editor::{Editor, EditorEvent};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
- AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable, Global, ScrollStrategy,
- Subscription, actions, div,
+ AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable, Global, KeyContext,
+ ScrollStrategy, Subscription, actions, div,
};
use ui::{
@@ -61,6 +61,7 @@ struct KeymapEditor {
matches: Vec<StringMatch>,
table_interaction_state: Entity<TableInteractionState>,
filter_editor: Entity<Editor>,
+ selected_index: Option<usize>,
}
impl EventEmitter<()> for KeymapEditor {}
@@ -102,6 +103,7 @@ impl KeymapEditor {
_keymap_subscription,
table_interaction_state,
filter_editor,
+ selected_index: None,
};
this.update_keybindings(cx);
@@ -201,6 +203,69 @@ impl KeymapEditor {
self.update_matches(cx);
cx.notify();
}
+
+ fn dispatch_context(&self, _window: &Window, _cx: &Context<Self>) -> KeyContext {
+ let mut dispatch_context = KeyContext::new_with_defaults();
+ dispatch_context.add("KeymapEditor");
+ dispatch_context.add("menu");
+
+ // todo! track key context in keybind edit modal
+ // let identifier = if self.keymap_editor.focus_handle(cx).is_focused(window) {
+ // "editing"
+ // } else {
+ // "not_editing"
+ // };
+ // dispatch_context.add(identifier);
+
+ dispatch_context
+ }
+
+ fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
+ if let Some(selected) = &mut self.selected_index {
+ *selected += 1;
+ if *selected >= self.matches.len() {
+ self.select_last(&Default::default(), window, cx);
+ } else {
+ cx.notify();
+ }
+ } else {
+ self.select_first(&Default::default(), window, cx);
+ }
+ }
+
+ fn select_previous(
+ &mut self,
+ _: &menu::SelectPrevious,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ if let Some(selected) = &mut self.selected_index {
+ *selected = selected.saturating_sub(1);
+ if *selected == 0 {
+ self.select_first(&Default::default(), window, cx);
+ } else if *selected >= self.matches.len() {
+ self.select_last(&Default::default(), window, cx);
+ } else {
+ cx.notify();
+ }
+ } else {
+ self.select_last(&Default::default(), window, cx);
+ }
+ }
+
+ fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
+ if self.matches.get(0).is_some() {
+ self.selected_index = Some(0);
+ cx.notify();
+ }
+ }
+
+ fn select_last(&mut self, _: &menu::SelectLast, _: &mut Window, cx: &mut Context<Self>) {
+ if self.matches.last().is_some() {
+ self.selected_index = Some(self.matches.len() - 1);
+ cx.notify();
+ }
+ }
}
#[derive(Clone)]
@@ -220,11 +285,16 @@ impl Item for KeymapEditor {
}
impl Render for KeymapEditor {
- fn render(&mut self, _window: &mut Window, cx: &mut ui::Context<Self>) -> impl ui::IntoElement {
+ fn render(&mut self, window: &mut Window, cx: &mut ui::Context<Self>) -> impl ui::IntoElement {
let row_count = self.matches.len();
let theme = cx.theme();
div()
+ .key_context(self.dispatch_context(window, cx))
+ .on_action(cx.listener(Self::select_next))
+ .on_action(cx.listener(Self::select_previous))
+ .on_action(cx.listener(Self::select_first))
+ .on_action(cx.listener(Self::select_last))
.size_full()
.bg(theme.colors().background)
.id("keymap-editor")
@@ -248,6 +318,7 @@ impl Render for KeymapEditor {
.striped()
.column_widths([rems(24.), rems(16.), rems(32.), rems(8.)])
.header(["Command", "Keystrokes", "Context", "Source"])
+ .selected_item_index(self.selected_index.clone())
.uniform_list(
"keymap-editor-table",
row_count,
@@ -353,6 +353,7 @@ pub struct Table<const COLS: usize = 3> {
headers: Option<[AnyElement; COLS]>,
rows: TableContents<COLS>,
interaction_state: Option<WeakEntity<TableInteractionState>>,
+ selected_item_index: Option<usize>,
column_widths: Option<[Length; COLS]>,
}
@@ -365,6 +366,7 @@ impl<const COLS: usize> Table<COLS> {
headers: None,
rows: TableContents::Vec(Vec::new()),
interaction_state: None,
+ selected_item_index: None,
column_widths: None,
}
}
@@ -405,6 +407,11 @@ impl<const COLS: usize> Table<COLS> {
self
}
+ pub fn selected_item_index(mut self, selected_item_index: Option<usize>) -> Self {
+ self.selected_item_index = selected_item_index;
+ self
+ }
+
pub fn header(mut self, headers: [impl IntoElement; COLS]) -> Self {
self.headers = Some(headers.map(IntoElement::into_any_element));
self
@@ -450,6 +457,8 @@ pub fn render_row<const COLS: usize>(
let column_widths = table_context
.column_widths
.map_or([None; COLS], |widths| widths.map(|width| Some(width)));
+ let is_selected = table_context.selected_item_index == Some(row_index);
+
div()
.w_full()
.flex()
@@ -462,6 +471,10 @@ pub fn render_row<const COLS: usize>(
.when(!is_last, |row| {
row.border_b_1().border_color(cx.theme().colors().border)
})
+ .when(is_selected, |row| {
+ row.border_2()
+ .border_color(cx.theme().colors().panel_focused_border)
+ })
.children(
items
.map(IntoElement::into_any_element)
@@ -500,6 +513,7 @@ pub fn render_header<const COLS: usize>(
pub struct TableRenderContext<const COLS: usize> {
pub striped: bool,
pub total_row_count: usize,
+ pub selected_item_index: Option<usize>,
pub column_widths: Option<[Length; COLS]>,
}
@@ -509,6 +523,7 @@ impl<const COLS: usize> TableRenderContext<COLS> {
striped: table.striped,
total_row_count: table.rows.len(),
column_widths: table.column_widths,
+ selected_item_index: table.selected_item_index.clone(),
}
}
}