settings_ui: Add Edit keybindings button (#44914)

Closes #ISSUE

Release Notes:

- settings_ui: Added an "Open Keymap Editor" item under the Keymap
section
This commit is contained in:
Ben Kunkle authored and GitHub committed 2025-12-15 21:03:02 -05:00
1 parent 6d45b1a226
commit 897303e0d0
3 files changed
+155 -43

No files matched your search

+51 -40
View File
@@ -81,50 +81,61 @@ pub fn init(cx: &mut App) {
let keymap_event_channel = KeymapEventChannel::new();
cx.set_global(keymap_event_channel);
fn common(filter: Option<String>, cx: &mut App) {
workspace::with_active_or_new_workspace(cx, move |workspace, window, cx| {
workspace
.with_local_workspace(window, cx, move |workspace, window, cx| {
let existing = workspace
.active_pane()
.read(cx)
.items()
.find_map(|item| item.downcast::<KeymapEditor>());
fn open_keymap_editor(
filter: Option<String>,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
workspace
.with_local_workspace(window, cx, |workspace, window, cx| {
let existing = workspace
.active_pane()
.read(cx)
.items()
.find_map(|item| item.downcast::<KeymapEditor>());
let keymap_editor = if let Some(existing) = existing {
workspace.activate_item(&existing, true, true, window, cx);
existing
} else {
let keymap_editor =
cx.new(|cx| KeymapEditor::new(workspace.weak_handle(), window, cx));
workspace.add_item_to_active_pane(
Box::new(keymap_editor.clone()),
None,
true,
window,
cx,
);
keymap_editor
};
let keymap_editor = if let Some(existing) = existing {
workspace.activate_item(&existing, true, true, window, cx);
existing
} else {
let keymap_editor =
cx.new(|cx| KeymapEditor::new(workspace.weak_handle(), window, cx));
workspace.add_item_to_active_pane(
Box::new(keymap_editor.clone()),
None,
true,
window,
cx,
);
keymap_editor
};
if let Some(filter) = filter {
keymap_editor.update(cx, |editor, cx| {
editor.filter_editor.update(cx, |editor, cx| {
editor.clear(window, cx);
editor.insert(&filter, window, cx);
});
if !editor.has_binding_for(&filter) {
open_binding_modal_after_loading(cx)
}
})
}
})
.detach();
})
if let Some(filter) = filter {
keymap_editor.update(cx, |editor, cx| {
editor.filter_editor.update(cx, |editor, cx| {
editor.clear(window, cx);
editor.insert(&filter, window, cx);
});
if !editor.has_binding_for(&filter) {
open_binding_modal_after_loading(cx)
}
})
}
})
.detach_and_log_err(cx);
}
cx.on_action(|_: &OpenKeymap, cx| common(None, cx))
.on_action(|action: &ChangeKeybinding, cx| common(Some(action.action.clone()), cx));
cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
workspace
.register_action(|workspace, _: &OpenKeymap, window, cx| {
open_keymap_editor(None, workspace, window, cx);
})
.register_action(|workspace, action: &ChangeKeybinding, window, cx| {
open_keymap_editor(Some(action.action.clone()), workspace, window, cx);
});
})
.detach();
register_serializable_item::<KeymapEditor>(cx);
}
+22 -3
View File
@@ -1,12 +1,12 @@
use gpui::App;
use gpui::{Action as _, App};
use settings::{LanguageSettingsContent, SettingsContent};
use std::sync::Arc;
use strum::IntoDiscriminant as _;
use ui::{IntoElement, SharedString};
use crate::{
DynamicItem, PROJECT, SettingField, SettingItem, SettingsFieldMetadata, SettingsPage,
SettingsPageItem, SubPageLink, USER, all_language_names, sub_page_stack,
ActionLink, DynamicItem, PROJECT, SettingField, SettingItem, SettingsFieldMetadata,
SettingsPage, SettingsPageItem, SubPageLink, USER, all_language_names, sub_page_stack,
};
const DEFAULT_STRING: String = String::new();
@@ -1054,6 +1054,25 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
SettingsPage {
title: "Keymap",
items: vec![
SettingsPageItem::SectionHeader("Keybindings"),
SettingsPageItem::ActionLink(ActionLink {
title: "Edit Keybindings".into(),
description: Some("Customize keybindings in the keymap editor.".into()),
button_text: "Open Keymap".into(),
on_click: Arc::new(|settings_window, window, cx| {
let Some(original_window) = settings_window.original_window else {
return;
};
original_window
.update(cx, |_workspace, original_window, cx| {
original_window
.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx);
original_window.activate_window();
})
.ok();
window.remove_window();
}),
}),
SettingsPageItem::SectionHeader("Base Keymap"),
SettingsPageItem::SettingItem(SettingItem {
title: "Base Keymap",
+82
View File
@@ -731,6 +731,7 @@ enum SettingsPageItem {
SettingItem(SettingItem),
SubPageLink(SubPageLink),
DynamicItem(DynamicItem),
ActionLink(ActionLink),
}
impl std::fmt::Debug for SettingsPageItem {
@@ -746,6 +747,9 @@ impl std::fmt::Debug for SettingsPageItem {
SettingsPageItem::DynamicItem(dynamic_item) => {
write!(f, "DynamicItem({})", dynamic_item.discriminant.title)
}
SettingsPageItem::ActionLink(action_link) => {
write!(f, "ActionLink({})", action_link.title)
}
}
}
}
@@ -973,6 +977,55 @@ impl SettingsPageItem {
return content.into_any_element();
}
SettingsPageItem::ActionLink(action_link) => v_flex()
.group("setting-item")
.px_8()
.child(
h_flex()
.id(action_link.title.clone())
.w_full()
.min_w_0()
.justify_between()
.map(apply_padding)
.child(
v_flex()
.relative()
.w_full()
.max_w_1_2()
.child(Label::new(action_link.title.clone()))
.when_some(
action_link.description.as_ref(),
|this, description| {
this.child(
Label::new(description.clone())
.size(LabelSize::Small)
.color(Color::Muted),
)
},
),
)
.child(
Button::new(
("action-link".into(), action_link.title.clone()),
action_link.button_text.clone(),
)
.icon(IconName::ArrowUpRight)
.tab_index(0_isize)
.icon_position(IconPosition::End)
.icon_color(Color::Muted)
.icon_size(IconSize::Small)
.style(ButtonStyle::OutlinedGhost)
.size(ButtonSize::Medium)
.on_click({
let on_click = action_link.on_click.clone();
cx.listener(move |this, _, window, cx| {
on_click(this, window, cx);
})
}),
),
)
.when(!is_last, |this| this.child(Divider::horizontal()))
.into_any_element(),
}
}
}
@@ -1207,6 +1260,20 @@ impl PartialEq for SubPageLink {
}
}
#[derive(Clone)]
struct ActionLink {
title: SharedString,
description: Option<SharedString>,
button_text: SharedString,
on_click: Arc<dyn Fn(&mut SettingsWindow, &mut Window, &mut App) + Send + Sync>,
}
impl PartialEq for ActionLink {
fn eq(&self, other: &Self) -> bool {
self.title == other.title
}
}
fn all_language_names(cx: &App) -> Vec<SharedString> {
workspace::AppState::global(cx)
.upgrade()
@@ -1626,6 +1693,9 @@ impl SettingsWindow {
any_found_since_last_header = true;
}
}
SettingsPageItem::ActionLink(_) => {
any_found_since_last_header = true;
}
}
}
if let Some(last_header) = page_filter.get_mut(header_index)
@@ -1864,6 +1934,18 @@ impl SettingsWindow {
sub_page_link.title.as_ref(),
);
}
SettingsPageItem::ActionLink(action_link) => {
documents.push(bm25::Document {
id: key_index,
contents: [page.title, header_str, action_link.title.as_ref()]
.join("\n"),
});
push_candidates(
&mut fuzzy_match_candidates,
key_index,
action_link.title.as_ref(),
);
}
}
push_candidates(&mut fuzzy_match_candidates, key_index, page.title);
push_candidates(&mut fuzzy_match_candidates, key_index, header_str);