Detailed changes
@@ -233,31 +233,25 @@ pub fn deploy_context_menu(
.action("Copy and Trim", Box::new(CopyAndTrim))
.action("Paste", Box::new(Paste))
.separator()
- .map(|builder| {
- let reveal_in_finder_label = if cfg!(target_os = "macos") {
+ .action_disabled_when(
+ !has_reveal_target,
+ if cfg!(target_os = "macos") {
"Reveal in Finder"
} else {
"Reveal in File Manager"
- };
- const OPEN_IN_TERMINAL_LABEL: &str = "Open in Terminal";
- if has_reveal_target {
- builder
- .action(reveal_in_finder_label, Box::new(RevealInFileManager))
- .action(OPEN_IN_TERMINAL_LABEL, Box::new(OpenInTerminal))
- } else {
- builder
- .disabled_action(reveal_in_finder_label, Box::new(RevealInFileManager))
- .disabled_action(OPEN_IN_TERMINAL_LABEL, Box::new(OpenInTerminal))
- }
- })
- .map(|builder| {
- const COPY_PERMALINK_LABEL: &str = "Copy Permalink";
- if has_git_repo {
- builder.action(COPY_PERMALINK_LABEL, Box::new(CopyPermalinkToLine))
- } else {
- builder.disabled_action(COPY_PERMALINK_LABEL, Box::new(CopyPermalinkToLine))
- }
- });
+ },
+ Box::new(RevealInFileManager),
+ )
+ .action_disabled_when(
+ !has_reveal_target,
+ "Open in Terminal",
+ Box::new(OpenInTerminal),
+ )
+ .action_disabled_when(
+ !has_git_repo,
+ "Copy Permalink",
+ Box::new(CopyPermalinkToLine),
+ );
match focus {
Some(focus) => builder.context(focus),
None => builder,
@@ -122,40 +122,29 @@ fn git_panel_context_menu(
ContextMenu::build(window, cx, move |context_menu, _, _| {
context_menu
.context(focus_handle)
- .map(|menu| {
- if state.has_unstaged_changes {
- menu.action("Stage All", StageAll.boxed_clone())
- } else {
- menu.disabled_action("Stage All", StageAll.boxed_clone())
- }
- })
- .map(|menu| {
- if state.has_staged_changes {
- menu.action("Unstage All", UnstageAll.boxed_clone())
- } else {
- menu.disabled_action("Unstage All", UnstageAll.boxed_clone())
- }
- })
+ .action_disabled_when(
+ !state.has_unstaged_changes,
+ "Stage All",
+ StageAll.boxed_clone(),
+ )
+ .action_disabled_when(
+ !state.has_staged_changes,
+ "Unstage All",
+ UnstageAll.boxed_clone(),
+ )
.separator()
.action("Open Diff", project_diff::Diff.boxed_clone())
.separator()
- .map(|menu| {
- if state.has_tracked_changes {
- menu.action("Discard Tracked Changes", RestoreTrackedFiles.boxed_clone())
- } else {
- menu.disabled_action(
- "Discard Tracked Changes",
- RestoreTrackedFiles.boxed_clone(),
- )
- }
- })
- .map(|menu| {
- if state.has_new_changes {
- menu.action("Trash Untracked Files", TrashUntrackedFiles.boxed_clone())
- } else {
- menu.disabled_action("Trash Untracked Files", TrashUntrackedFiles.boxed_clone())
- }
- })
+ .action_disabled_when(
+ !state.has_tracked_changes,
+ "Discard Tracked Changes",
+ RestoreTrackedFiles.boxed_clone(),
+ )
+ .action_disabled_when(
+ !state.has_new_changes,
+ "Trash Untracked Files",
+ TrashUntrackedFiles.boxed_clone(),
+ )
})
}
@@ -820,13 +820,11 @@ impl ProjectPanel {
.action("Copy", Box::new(Copy))
.action("Duplicate", Box::new(Duplicate))
// TODO: Paste should always be visible, cbut disabled when clipboard is empty
- .map(|menu| {
- if self.clipboard.as_ref().is_some() {
- menu.action("Paste", Box::new(Paste))
- } else {
- menu.disabled_action("Paste", Box::new(Paste))
- }
- })
+ .action_disabled_when(
+ self.clipboard.as_ref().is_none(),
+ "Paste",
+ Box::new(Paste),
+ )
.separator()
.action("Copy Path", Box::new(zed_actions::workspace::CopyPath))
.action(
@@ -9,7 +9,7 @@ use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
AppContext as _, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
FontWeight, Global, KeyContext, Keystroke, ModifiersChangedEvent, ScrollStrategy, StyledText,
- Subscription, WeakEntity, actions, div,
+ Subscription, WeakEntity, actions, div, transparent_black,
};
use language::{Language, LanguageConfig};
use settings::KeybindSource;
@@ -17,8 +17,8 @@ use settings::KeybindSource;
use util::ResultExt;
use ui::{
- ActiveTheme as _, App, BorrowAppContext, ParentElement as _, Render, SharedString, Styled as _,
- Window, prelude::*,
+ ActiveTheme as _, App, BorrowAppContext, ContextMenu, ParentElement as _, Render, SharedString,
+ Styled as _, Window, prelude::*, right_click_menu,
};
use workspace::{Item, ModalView, SerializableItem, Workspace, register_serializable_item};
@@ -30,6 +30,9 @@ use crate::{
actions!(zed, [OpenKeymapEditor]);
+const KEYMAP_EDITOR_NAMESPACE: &'static str = "keymap_editor";
+actions!(keymap_editor, [EditBinding, CopyAction, CopyContext]);
+
pub fn init(cx: &mut App) {
let keymap_event_channel = KeymapEventChannel::new();
cx.set_global(keymap_event_channel);
@@ -59,6 +62,7 @@ pub fn init(cx: &mut App) {
command_palette_hooks::CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_action_types(&keymap_ui_actions);
+ filter.hide_namespace(KEYMAP_EDITOR_NAMESPACE);
});
cx.observe_flag::<SettingsUiFeatureFlag, _>(
@@ -69,6 +73,7 @@ pub fn init(cx: &mut App) {
cx,
|filter, _cx| {
filter.show_action_types(keymap_ui_actions.iter());
+ filter.show_namespace(KEYMAP_EDITOR_NAMESPACE);
},
);
} else {
@@ -76,6 +81,7 @@ pub fn init(cx: &mut App) {
cx,
|filter, _cx| {
filter.hide_action_types(&keymap_ui_actions);
+ filter.hide_namespace(KEYMAP_EDITOR_NAMESPACE);
},
);
}
@@ -231,8 +237,8 @@ impl KeymapEditor {
let context = key_binding
.predicate()
- .map(|predicate| predicate.to_string())
- .unwrap_or_else(|| "<global>".to_string());
+ .map(|predicate| KeybindContextString::Local(predicate.to_string().into()))
+ .unwrap_or(KeybindContextString::Global);
let source = source.map(|source| (source, source.name().into()));
@@ -249,7 +255,7 @@ impl KeymapEditor {
ui_key_binding,
action: action_name.into(),
action_input,
- context: context.into(),
+ context: Some(context),
source,
});
string_match_candidates.push(string_match_candidate);
@@ -264,7 +270,7 @@ impl KeymapEditor {
ui_key_binding: None,
action: (*action_name).into(),
action_input: None,
- context: empty.clone(),
+ context: None,
source: None,
});
string_match_candidates.push(string_match_candidate);
@@ -345,6 +351,33 @@ impl KeymapEditor {
});
}
+ fn focus_search(
+ &mut self,
+ _: &search::FocusSearch,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ if !self
+ .filter_editor
+ .focus_handle(cx)
+ .contains_focused(window, cx)
+ {
+ window.focus(&self.filter_editor.focus_handle(cx));
+ } else {
+ self.filter_editor.update(cx, |editor, cx| {
+ editor.select_all(&Default::default(), window, cx);
+ });
+ }
+ self.selected_index.take();
+ }
+
+ fn selected_binding(&self) -> Option<&ProcessedKeybinding> {
+ self.selected_index
+ .and_then(|match_index| self.matches.get(match_index))
+ .map(|r#match| r#match.candidate_id)
+ .and_then(|keybind_index| self.keybindings.get(keybind_index))
+ }
+
fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
if let Some(selected) = self.selected_index {
let selected = selected + 1;
@@ -408,25 +441,18 @@ impl KeymapEditor {
}
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
- let Some(index) = self.selected_index else {
- return;
- };
- let keybind = self.keybindings[self.matches[index].candidate_id].clone();
-
- self.edit_keybinding(keybind, window, cx);
+ self.edit_selected_keybinding(window, cx);
}
- fn edit_keybinding(
- &mut self,
- keybind: ProcessedKeybinding,
- window: &mut Window,
- cx: &mut Context<Self>,
- ) {
+ fn edit_selected_keybinding(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+ let Some(keybind) = self.selected_binding() else {
+ return;
+ };
self.workspace
.update(cx, |workspace, cx| {
let fs = workspace.app_state().fs.clone();
workspace.toggle_modal(window, cx, |window, cx| {
- let modal = KeybindingEditorModal::new(keybind, fs, window, cx);
+ let modal = KeybindingEditorModal::new(keybind.clone(), fs, window, cx);
window.focus(&modal.focus_handle(cx));
modal
});
@@ -434,24 +460,40 @@ impl KeymapEditor {
.log_err();
}
- fn focus_search(
+ fn edit_binding(&mut self, _: &EditBinding, window: &mut Window, cx: &mut Context<Self>) {
+ self.edit_selected_keybinding(window, cx);
+ }
+
+ fn copy_context_to_clipboard(
&mut self,
- _: &search::FocusSearch,
- window: &mut Window,
+ _: &CopyContext,
+ _window: &mut Window,
cx: &mut Context<Self>,
) {
- if !self
- .filter_editor
- .focus_handle(cx)
- .contains_focused(window, cx)
- {
- window.focus(&self.filter_editor.focus_handle(cx));
- } else {
- self.filter_editor.update(cx, |editor, cx| {
- editor.select_all(&Default::default(), window, cx);
- });
- }
- self.selected_index.take();
+ let context = self
+ .selected_binding()
+ .and_then(|binding| binding.context.as_ref())
+ .and_then(KeybindContextString::local_str)
+ .map(|context| context.to_string());
+ let Some(context) = context else {
+ return;
+ };
+ cx.write_to_clipboard(gpui::ClipboardItem::new_string(context.clone()));
+ }
+
+ fn copy_action_to_clipboard(
+ &mut self,
+ _: &CopyAction,
+ _window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ let action = self
+ .selected_binding()
+ .map(|binding| binding.action.to_string());
+ let Some(action) = action else {
+ return;
+ };
+ cx.write_to_clipboard(gpui::ClipboardItem::new_string(action.clone()));
}
}
@@ -461,10 +503,43 @@ struct ProcessedKeybinding {
ui_key_binding: Option<ui::KeyBinding>,
action: SharedString,
action_input: Option<TextWithSyntaxHighlighting>,
- context: SharedString,
+ context: Option<KeybindContextString>,
source: Option<(KeybindSource, SharedString)>,
}
+#[derive(Clone, Debug, IntoElement)]
+enum KeybindContextString {
+ Global,
+ Local(SharedString),
+}
+
+impl KeybindContextString {
+ const GLOBAL: SharedString = SharedString::new_static("<global>");
+
+ pub fn local(&self) -> Option<&SharedString> {
+ match self {
+ KeybindContextString::Global => None,
+ KeybindContextString::Local(name) => Some(name),
+ }
+ }
+
+ pub fn local_str(&self) -> Option<&str> {
+ match self {
+ KeybindContextString::Global => None,
+ KeybindContextString::Local(name) => Some(name),
+ }
+ }
+}
+
+impl RenderOnce for KeybindContextString {
+ fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
+ match self {
+ KeybindContextString::Global => KeybindContextString::GLOBAL.clone(),
+ KeybindContextString::Local(name) => name,
+ }
+ }
+}
+
impl Item for KeymapEditor {
type Event = ();
@@ -486,6 +561,9 @@ impl Render for KeymapEditor {
.on_action(cx.listener(Self::select_last))
.on_action(cx.listener(Self::focus_search))
.on_action(cx.listener(Self::confirm))
+ .on_action(cx.listener(Self::edit_binding))
+ .on_action(cx.listener(Self::copy_action_to_clipboard))
+ .on_action(cx.listener(Self::copy_context_to_clipboard))
.size_full()
.bg(theme.colors().editor_background)
.id("keymap-editor")
@@ -514,10 +592,6 @@ impl Render for KeymapEditor {
.striped()
.column_widths([rems(16.), rems(16.), rems(16.), rems(32.), rems(8.)])
.header(["Action", "Arguments", "Keystrokes", "Context", "Source"])
- .selected_item_index(self.selected_index)
- .on_click_row(cx.processor(|this, row_index, _window, _cx| {
- this.selected_index = Some(row_index);
- }))
.uniform_list(
"keymap-editor-table",
row_count,
@@ -538,7 +612,12 @@ impl Render for KeymapEditor {
.map_or(gpui::Empty.into_any_element(), |input| {
input.into_any_element()
});
- let context = binding.context.clone().into_any_element();
+ let context = binding
+ .context
+ .clone()
+ .map_or(gpui::Empty.into_any_element(), |context| {
+ context.into_any_element()
+ });
let source = binding
.source
.clone()
@@ -549,6 +628,43 @@ impl Render for KeymapEditor {
})
.collect()
}),
+ )
+ .map_row(
+ cx.processor(|this, (row_index, row): (usize, Div), _window, cx| {
+ let is_selected = this.selected_index == Some(row_index);
+ let row = row
+ .id(("keymap-table-row", row_index))
+ .on_click(cx.listener(move |this, _event, _window, _cx| {
+ this.selected_index = Some(row_index);
+ }))
+ .border_2()
+ .border_color(transparent_black())
+ .when(is_selected, |row| {
+ row.border_color(cx.theme().colors().panel_focused_border)
+ });
+
+ right_click_menu(("keymap-table-row-menu", row_index))
+ .trigger({
+ let this = cx.weak_entity();
+ move |is_menu_open: bool, _window, cx| {
+ if is_menu_open {
+ this.update(cx, |this, cx| {
+ if this.selected_index != Some(row_index) {
+ this.selected_index = Some(row_index);
+ cx.notify();
+ }
+ })
+ .ok();
+ }
+ row
+ }
+ })
+ .menu({
+ let this = cx.weak_entity();
+ move |window, cx| build_keybind_context_menu(&this, window, cx)
+ })
+ .into_any_element()
+ }),
),
)
}
@@ -712,7 +828,7 @@ impl Render for KeybindingEditorModal {
.await
{
this.update(cx, |this, cx| {
- this.error = Some(err);
+ this.error = Some(err.to_string());
cx.notify();
})
.log_err();
@@ -741,54 +857,55 @@ async fn save_keybinding_update(
new_keystrokes: &[Keystroke],
fs: &Arc<dyn Fs>,
tab_size: usize,
-) -> Result<(), String> {
+) -> anyhow::Result<()> {
let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
.await
- .map_err(|err| format!("Failed to load keymap file: {}", err))?;
+ .context("Failed to load keymap file")?;
let existing_keystrokes = existing
.ui_key_binding
.as_ref()
.map(|keybinding| keybinding.key_binding.keystrokes())
.unwrap_or_default();
+ let context = existing
+ .context
+ .as_ref()
+ .and_then(KeybindContextString::local_str);
+
+ let input = existing
+ .action_input
+ .as_ref()
+ .map(|input| input.text.as_ref());
+
let operation = if existing.ui_key_binding.is_some() {
settings::KeybindUpdateOperation::Replace {
target: settings::KeybindUpdateTarget {
- context: Some(existing.context.as_ref()).filter(|context| !context.is_empty()),
+ context,
keystrokes: existing_keystrokes,
action_name: &existing.action,
use_key_equivalents: false,
- input: existing
- .action_input
- .as_ref()
- .map(|input| input.text.as_ref()),
+ input,
},
target_source: existing
.source
.map(|(source, _name)| source)
.unwrap_or(KeybindSource::User),
source: settings::KeybindUpdateTarget {
- context: Some(existing.context.as_ref()).filter(|context| !context.is_empty()),
+ context,
keystrokes: new_keystrokes,
action_name: &existing.action,
use_key_equivalents: false,
- input: existing
- .action_input
- .as_ref()
- .map(|input| input.text.as_ref()),
+ input,
},
}
} else {
- return Err(
- "Not Implemented: Creating new bindings from unbound actions is not supported yet."
- .to_string(),
- );
+ anyhow::bail!("Adding new bindings not implemented yet");
};
let updated_keymap_contents =
settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
- .map_err(|err| format!("Failed to update keybinding: {}", err))?;
+ .context("Failed to update keybinding")?;
fs.atomic_write(paths::keymap_file().clone(), updated_keymap_contents)
.await
- .map_err(|err| format!("Failed to write keymap file: {}", err))?;
+ .context("Failed to write keymap file")?;
Ok(())
}
@@ -903,6 +1020,36 @@ impl Render for KeybindInput {
}
}
+fn build_keybind_context_menu(
+ this: &WeakEntity<KeymapEditor>,
+ window: &mut Window,
+ cx: &mut App,
+) -> Entity<ContextMenu> {
+ ContextMenu::build(window, cx, |menu, _window, cx| {
+ let Some(this) = this.upgrade() else {
+ return menu;
+ };
+ let selected_binding = this.read_with(cx, |this, _cx| this.selected_binding().cloned());
+ let Some(selected_binding) = selected_binding else {
+ return menu;
+ };
+
+ let selected_binding_has_context = selected_binding
+ .context
+ .as_ref()
+ .and_then(KeybindContextString::local)
+ .is_some();
+
+ menu.action("Edit Binding", Box::new(EditBinding))
+ .action("Copy action", Box::new(CopyAction))
+ .action_disabled_when(
+ !selected_binding_has_context,
+ "Copy Context",
+ Box::new(CopyContext),
+ )
+ })
+}
+
impl SerializableItem for KeymapEditor {
fn serialized_item_kind() -> &'static str {
"KeymapEditor"
@@ -155,8 +155,6 @@ impl TableInteractionState {
self.vertical_scrollbar.hide(window, cx);
}
- // fn listener(this: Entity<Self>, fn: F) ->
-
pub fn listener<E: ?Sized>(
this: &Entity<Self>,
f: impl Fn(&mut Self, &E, &mut Window, &mut Context<Self>) + 'static,
@@ -353,9 +351,8 @@ 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]>,
- on_click_row: Option<Rc<dyn Fn(usize, &mut Window, &mut App)>>,
+ map_row: Option<Rc<dyn Fn((usize, Div), &mut Window, &mut App) -> AnyElement>>,
}
impl<const COLS: usize> Table<COLS> {
@@ -367,9 +364,8 @@ impl<const COLS: usize> Table<COLS> {
headers: None,
rows: TableContents::Vec(Vec::new()),
interaction_state: None,
- selected_item_index: None,
column_widths: None,
- on_click_row: None,
+ map_row: None,
}
}
@@ -418,11 +414,6 @@ 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
@@ -440,11 +431,11 @@ impl<const COLS: usize> Table<COLS> {
self
}
- pub fn on_click_row(
+ pub fn map_row(
mut self,
- callback: impl Fn(usize, &mut Window, &mut App) + 'static,
+ callback: impl Fn((usize, Div), &mut Window, &mut App) -> AnyElement + 'static,
) -> Self {
- self.on_click_row = Some(Rc::new(callback));
+ self.map_row = Some(Rc::new(callback));
self
}
}
@@ -465,7 +456,8 @@ pub fn render_row<const COLS: usize>(
row_index: usize,
items: [impl IntoElement; COLS],
table_context: TableRenderContext<COLS>,
- cx: &App,
+ window: &mut Window,
+ cx: &mut App,
) -> AnyElement {
let is_striped = table_context.striped;
let is_last = row_index == table_context.total_row_count - 1;
@@ -477,43 +469,33 @@ pub fn render_row<const COLS: usize>(
let column_widths = table_context
.column_widths
.map_or([None; COLS], |widths| widths.map(Some));
- let is_selected = table_context.selected_item_index == Some(row_index);
- let row = div()
- .w_full()
- .border_2()
- .border_color(transparent_black())
- .when(is_selected, |row| {
- row.border_color(cx.theme().colors().panel_focused_border)
- })
- .child(
- div()
- .w_full()
- .flex()
- .flex_row()
- .items_center()
- .justify_between()
- .px_1p5()
- .py_1()
- .when_some(bg, |row, bg| row.bg(bg))
- .when(!is_striped, |row| {
- row.border_b_1()
- .border_color(transparent_black())
- .when(!is_last, |row| row.border_color(cx.theme().colors().border))
- })
- .children(
- items
- .map(IntoElement::into_any_element)
- .into_iter()
- .zip(column_widths)
- .map(|(cell, width)| base_cell_style(width, cx).child(cell)),
- ),
- );
-
- if let Some(on_click) = table_context.on_click_row {
- row.id(("table-row", row_index))
- .on_click(move |_, window, cx| on_click(row_index, window, cx))
- .into_any_element()
+ let row = div().w_full().child(
+ div()
+ .w_full()
+ .flex()
+ .flex_row()
+ .items_center()
+ .justify_between()
+ .px_1p5()
+ .py_1()
+ .when_some(bg, |row, bg| row.bg(bg))
+ .when(!is_striped, |row| {
+ row.border_b_1()
+ .border_color(transparent_black())
+ .when(!is_last, |row| row.border_color(cx.theme().colors().border))
+ })
+ .children(
+ items
+ .map(IntoElement::into_any_element)
+ .into_iter()
+ .zip(column_widths)
+ .map(|(cell, width)| base_cell_style(width, cx).child(cell)),
+ ),
+ );
+
+ if let Some(map_row) = table_context.map_row {
+ map_row((row_index, row), window, cx)
} else {
row.into_any_element()
}
@@ -547,9 +529,8 @@ 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]>,
- pub on_click_row: Option<Rc<dyn Fn(usize, &mut Window, &mut App)>>,
+ pub map_row: Option<Rc<dyn Fn((usize, Div), &mut Window, &mut App) -> AnyElement>>,
}
impl<const COLS: usize> TableRenderContext<COLS> {
@@ -558,14 +539,13 @@ 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,
- on_click_row: table.on_click_row.clone(),
+ map_row: table.map_row.clone(),
}
}
}
impl<const COLS: usize> RenderOnce for Table<COLS> {
- fn render(mut self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
+ fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let table_context = TableRenderContext::new(&self);
let interaction_state = self.interaction_state.and_then(|state| state.upgrade());
@@ -598,7 +578,7 @@ impl<const COLS: usize> RenderOnce for Table<COLS> {
.map(|parent| match self.rows {
TableContents::Vec(items) => {
parent.children(items.into_iter().enumerate().map(|(index, row)| {
- render_row(index, row, table_context.clone(), cx)
+ render_row(index, row, table_context.clone(), window, cx)
}))
}
TableContents::UniformList(uniform_list_data) => parent.child(
@@ -617,6 +597,7 @@ impl<const COLS: usize> RenderOnce for Table<COLS> {
row_index,
row,
table_context.clone(),
+ window,
cx,
)
})
@@ -503,8 +503,9 @@ impl ContextMenu {
self
}
- pub fn disabled_action(
+ pub fn action_disabled_when(
mut self,
+ disabled: bool,
label: impl Into<SharedString>,
action: Box<dyn Action>,
) -> Self {
@@ -522,7 +523,7 @@ impl ContextMenu {
icon_size: IconSize::Small,
icon_position: IconPosition::End,
icon_color: None,
- disabled: true,
+ disabled,
documentation_aside: None,
end_slot_icon: None,
end_slot_title: None,
@@ -9,7 +9,7 @@ use gpui::{
pub struct RightClickMenu<M: ManagedView> {
id: ElementId,
- child_builder: Option<Box<dyn FnOnce(bool) -> AnyElement + 'static>>,
+ child_builder: Option<Box<dyn FnOnce(bool, &mut Window, &mut App) -> AnyElement + 'static>>,
menu_builder: Option<Rc<dyn Fn(&mut Window, &mut App) -> Entity<M> + 'static>>,
anchor: Option<Corner>,
attach: Option<Corner>,
@@ -23,11 +23,11 @@ impl<M: ManagedView> RightClickMenu<M> {
pub fn trigger<F, E>(mut self, e: F) -> Self
where
- F: FnOnce(bool) -> E + 'static,
+ F: FnOnce(bool, &mut Window, &mut App) -> E + 'static,
E: IntoElement + 'static,
{
- self.child_builder = Some(Box::new(move |is_menu_active| {
- e(is_menu_active).into_any_element()
+ self.child_builder = Some(Box::new(move |is_menu_active, window, cx| {
+ e(is_menu_active, window, cx).into_any_element()
}));
self
}
@@ -149,10 +149,9 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
element
});
- let mut child_element = this
- .child_builder
- .take()
- .map(|child_builder| (child_builder)(element_state.menu.borrow().is_some()));
+ let mut child_element = this.child_builder.take().map(|child_builder| {
+ (child_builder)(element_state.menu.borrow().is_some(), window, cx)
+ });
let child_layout_id = child_element
.as_mut()
@@ -47,12 +47,12 @@ impl Render for ContextMenuStory {
.justify_between()
.child(
right_click_menu("test2")
- .trigger(|_| Label::new("TOP LEFT"))
+ .trigger(|_, _, _| Label::new("TOP LEFT"))
.menu(move |window, cx| build_menu(window, cx, "top left")),
)
.child(
right_click_menu("test1")
- .trigger(|_| Label::new("BOTTOM LEFT"))
+ .trigger(|_, _, _| Label::new("BOTTOM LEFT"))
.anchor(Corner::BottomLeft)
.attach(Corner::TopLeft)
.menu(move |window, cx| build_menu(window, cx, "bottom left")),
@@ -65,13 +65,13 @@ impl Render for ContextMenuStory {
.justify_between()
.child(
right_click_menu("test3")
- .trigger(|_| Label::new("TOP RIGHT"))
+ .trigger(|_, _, _| Label::new("TOP RIGHT"))
.anchor(Corner::TopRight)
.menu(move |window, cx| build_menu(window, cx, "top right")),
)
.child(
right_click_menu("test4")
- .trigger(|_| Label::new("BOTTOM RIGHT"))
+ .trigger(|_, _, _| Label::new("BOTTOM RIGHT"))
.anchor(Corner::BottomRight)
.attach(Corner::TopRight)
.menu(move |window, cx| build_menu(window, cx, "bottom right")),
@@ -902,7 +902,7 @@ impl Render for PanelButtons {
})
.anchor(menu_anchor)
.attach(menu_attach)
- .trigger(move |is_active| {
+ .trigger(move |is_active, _window, _cx| {
IconButton::new(name, icon)
.icon_size(IconSize::Small)
.toggle_state(is_active_button)
@@ -2521,7 +2521,7 @@ impl Pane {
let pane = cx.entity().downgrade();
let menu_context = item.item_focus_handle(cx);
right_click_menu(ix)
- .trigger(|_| tab)
+ .trigger(|_, _, _| tab)
.menu(move |window, cx| {
let pane = pane.clone();
let menu_context = menu_context.clone();
@@ -4311,6 +4311,7 @@ mod tests {
"icon_theme_selector",
"jj",
"journal",
+ "keymap_editor",
"language_selector",
"lsp_tool",
"markdown",
@@ -258,18 +258,12 @@ impl Render for QuickActionBar {
.action("Next Problem", Box::new(GoToDiagnostic))
.action("Previous Problem", Box::new(GoToPreviousDiagnostic))
.separator()
- .map(|menu| {
- if has_diff_hunks {
- menu.action("Next Hunk", Box::new(GoToHunk))
- .action("Previous Hunk", Box::new(GoToPreviousHunk))
- } else {
- menu.disabled_action("Next Hunk", Box::new(GoToHunk))
- .disabled_action(
- "Previous Hunk",
- Box::new(GoToPreviousHunk),
- )
- }
- })
+ .action_disabled_when(!has_diff_hunks, "Next Hunk", Box::new(GoToHunk))
+ .action_disabled_when(
+ !has_diff_hunks,
+ "Previous Hunk",
+ Box::new(GoToPreviousHunk),
+ )
.separator()
.action("Move Line Up", Box::new(MoveLineUp))
.action("Move Line Down", Box::new(MoveLineDown))