diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 5ba0005289024d245b5cd13aa1425b761dd23374..e9016ec8270f32829e7c2b4b6526082a4f58a288 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -1205,6 +1205,7 @@ "alt-c": "keymap_editor::ToggleConflictFilter", "enter": "keymap_editor::EditBinding", "alt-enter": "keymap_editor::CreateBinding", + "ctrl-k": "keymap_editor::OpenCreateKeybindingModal", "ctrl-c": "keymap_editor::CopyAction", "ctrl-shift-c": "keymap_editor::CopyContext", "ctrl-t": "keymap_editor::ShowMatchingKeybinds", diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index f9adbcd49bf7b3eae74116ac3da64b3cdfd3b4de..7ec882dbfd432927e059650626036fee2041f73b 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -1306,6 +1306,7 @@ "cmd-alt-c": "keymap_editor::ToggleConflictFilter", "enter": "keymap_editor::EditBinding", "alt-enter": "keymap_editor::CreateBinding", + "cmd-k": "keymap_editor::OpenCreateKeybindingModal", "cmd-c": "keymap_editor::CopyAction", "cmd-shift-c": "keymap_editor::CopyContext", "cmd-t": "keymap_editor::ShowMatchingKeybinds", diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 931363319e7573c05b0d5d9f455c4e89f747ada9..4cf24f56ec53f9c31a7a9fc907f6abcdbf85da7a 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -1233,6 +1233,7 @@ "alt-c": "keymap_editor::ToggleConflictFilter", "enter": "keymap_editor::EditBinding", "alt-enter": "keymap_editor::CreateBinding", + "ctrl-k": "keymap_editor::OpenCreateKeybindingModal", "ctrl-c": "keymap_editor::CopyAction", "ctrl-shift-c": "keymap_editor::CopyContext", "ctrl-t": "keymap_editor::ShowMatchingKeybinds", diff --git a/crates/keymap_editor/src/action_completion_provider.rs b/crates/keymap_editor/src/action_completion_provider.rs new file mode 100644 index 0000000000000000000000000000000000000000..98428baeb2f7b419ba7354130e12f1a4710c8aea --- /dev/null +++ b/crates/keymap_editor/src/action_completion_provider.rs @@ -0,0 +1,136 @@ +use collections::HashMap; +use command_palette; +use editor::{CompletionProvider, Editor}; +use fuzzy::StringMatchCandidate; +use gpui::{Context, Entity, SharedString, Window}; +use language::{self, ToOffset}; +use project::{self, CompletionDisplayOptions}; + +pub struct ActionCompletionProvider { + action_names: Vec<&'static str>, + humanized_names: HashMap<&'static str, SharedString>, +} + +impl ActionCompletionProvider { + pub fn new( + action_names: Vec<&'static str>, + humanized_names: HashMap<&'static str, SharedString>, + ) -> Self { + Self { + action_names, + humanized_names, + } + } +} + +impl CompletionProvider for ActionCompletionProvider { + fn completions( + &self, + _excerpt_id: editor::ExcerptId, + buffer: &Entity, + buffer_position: language::Anchor, + _trigger: editor::CompletionContext, + _window: &mut Window, + cx: &mut Context, + ) -> gpui::Task>> { + let buffer = buffer.read(cx); + let mut count_back = 0; + + for char in buffer.reversed_chars_at(buffer_position) { + if char.is_ascii_alphanumeric() || char == '_' || char == ':' { + count_back += 1; + } else { + break; + } + } + + let start_anchor = buffer.anchor_before( + buffer_position + .to_offset(&buffer) + .saturating_sub(count_back), + ); + + let replace_range = start_anchor..buffer_position; + let snapshot = buffer.text_snapshot(); + let query: String = snapshot.text_for_range(replace_range.clone()).collect(); + let normalized_query = command_palette::normalize_action_query(&query); + + let candidates: Vec = self + .action_names + .iter() + .enumerate() + .map(|(ix, &name)| { + let humanized = self + .humanized_names + .get(name) + .cloned() + .unwrap_or_else(|| name.into()); + StringMatchCandidate::new(ix, &humanized) + }) + .collect(); + + let executor = cx.background_executor().clone(); + let executor_for_fuzzy = executor.clone(); + let action_names = self.action_names.clone(); + let humanized_names = self.humanized_names.clone(); + + executor.spawn(async move { + let matches = fuzzy::match_strings( + &candidates, + &normalized_query, + true, + true, + action_names.len(), + &Default::default(), + executor_for_fuzzy, + ) + .await; + + let completions: Vec = matches + .iter() + .take(50) + .map(|m| { + let action_name = action_names[m.candidate_id]; + let humanized = humanized_names + .get(action_name) + .cloned() + .unwrap_or_else(|| action_name.into()); + + project::Completion { + replace_range: replace_range.clone(), + label: language::CodeLabel::plain(humanized.to_string(), None), + new_text: action_name.to_string(), + documentation: None, + source: project::CompletionSource::Custom, + icon_path: None, + match_start: None, + snippet_deduplication_key: None, + insert_text_mode: None, + confirm: None, + } + }) + .collect(); + + Ok(vec![project::CompletionResponse { + completions, + display_options: CompletionDisplayOptions { + dynamic_width: true, + }, + is_incomplete: false, + }]) + }) + } + + fn is_completion_trigger( + &self, + _buffer: &Entity, + _position: language::Anchor, + text: &str, + _trigger_in_words: bool, + _cx: &mut Context, + ) -> bool { + text.chars().last().is_some_and(|last_char| { + last_char.is_ascii_alphanumeric() || last_char == '_' || last_char == ':' + }) + } +} diff --git a/crates/keymap_editor/src/keymap_editor.rs b/crates/keymap_editor/src/keymap_editor.rs index be20feaf5f8c1feea5b08fa3a6a3b542b26ef5ce..6f3521877e1384e46aed81c94294177620eba04b 100644 --- a/crates/keymap_editor/src/keymap_editor.rs +++ b/crates/keymap_editor/src/keymap_editor.rs @@ -7,6 +7,7 @@ use std::{ time::{Duration, Instant}, }; +mod action_completion_provider; mod ui_components; use anyhow::{Context as _, anyhow}; @@ -45,6 +46,7 @@ pub use ui_components::*; use zed_actions::{ChangeKeybinding, OpenKeymap}; use crate::{ + action_completion_provider::ActionCompletionProvider, persistence::KEYBINDING_EDITORS, ui_components::keystroke_input::{ ClearKeystrokes, KeystrokeInput, StartRecording, StopRecording, @@ -60,6 +62,8 @@ actions!( EditBinding, /// Creates a new key binding for the selected action. CreateBinding, + /// Creates a new key binding from scratch, prompting for the action. + OpenCreateKeybindingModal, /// Deletes the selected key binding. DeleteBinding, /// Copies the action name to clipboard. @@ -426,6 +430,7 @@ struct KeymapEditor { humanized_action_names: HumanizedActionNameCache, current_widths: Entity>, show_hover_menus: bool, + actions_with_schemas: HashSet<&'static str>, /// In order for the JSON LSP to run in the actions arguments editor, we /// require a backing file In order to avoid issues (primarily log spam) /// with drop order between the buffer, file, worktree, etc, we create a @@ -552,6 +557,7 @@ impl KeymapEditor { search_query_debounce: None, humanized_action_names: HumanizedActionNameCache::new(cx), show_hover_menus: true, + actions_with_schemas: HashSet::default(), action_args_temp_dir: None, action_args_temp_dir_worktree: None, current_widths: cx.new(|cx| TableColumnWidths::new(cx)), @@ -720,7 +726,11 @@ impl KeymapEditor { zed_keybind_context_language: Arc, humanized_action_names: &HumanizedActionNameCache, cx: &mut App, - ) -> (Vec, Vec) { + ) -> ( + Vec, + Vec, + HashSet<&'static str>, + ) { let key_bindings_ptr = cx.key_bindings(); let lock = key_bindings_ptr.borrow(); let key_bindings = lock.bindings(); @@ -797,8 +807,11 @@ impl KeymapEditor { processed_bindings.push(ProcessedBinding::Unmapped(action_information)); string_match_candidates.push(string_match_candidate); } - - (processed_bindings, string_match_candidates) + ( + processed_bindings, + string_match_candidates, + actions_with_schemas, + ) } fn on_keymap_changed(&mut self, window: &mut Window, cx: &mut Context) { @@ -809,16 +822,18 @@ impl KeymapEditor { load_keybind_context_language(workspace.clone(), cx).await; let (action_query, keystroke_query) = this.update(cx, |this, cx| { - let (key_bindings, string_match_candidates) = Self::process_bindings( - json_language, - zed_keybind_context_language, - &this.humanized_action_names, - cx, - ); + let (key_bindings, string_match_candidates, actions_with_schemas) = + Self::process_bindings( + json_language, + zed_keybind_context_language, + &this.humanized_action_names, + cx, + ); this.keybinding_conflict_state = ConflictState::new(&key_bindings); this.keybindings = key_bindings; + this.actions_with_schemas = actions_with_schemas; this.string_match_candidates = Arc::new(string_match_candidates); this.matches = this .string_match_candidates @@ -1245,6 +1260,51 @@ impl KeymapEditor { self.open_edit_keybinding_modal(true, window, cx); } + fn open_create_keybinding_modal( + &mut self, + _: &OpenCreateKeybindingModal, + window: &mut Window, + cx: &mut Context, + ) { + let keymap_editor = cx.entity(); + + let action_information = ActionInformation::new( + gpui::NoAction.name(), + None, + &HashSet::default(), + cx.action_documentation(), + &self.humanized_action_names, + ); + + let dummy_binding = ProcessedBinding::Unmapped(action_information); + let dummy_index = self.keybindings.len(); + + let temp_dir = self.action_args_temp_dir.as_ref().map(|dir| dir.path()); + + self.workspace + .update(cx, |workspace, cx| { + let fs = workspace.app_state().fs.clone(); + let workspace_weak = cx.weak_entity(); + workspace.toggle_modal(window, cx, |window, cx| { + let modal = KeybindingEditorModal::new( + true, + dummy_binding, + dummy_index, + keymap_editor, + temp_dir, + workspace_weak, + fs, + window, + cx, + ); + + window.focus(&modal.focus_handle(cx), cx); + modal + }); + }) + .log_err(); + } + fn delete_binding(&mut self, _: &DeleteBinding, window: &mut Window, cx: &mut Context) { let Some(to_remove) = self.selected_binding().cloned() else { return; @@ -1650,6 +1710,7 @@ impl Render for KeymapEditor { .on_action(cx.listener(Self::focus_search)) .on_action(cx.listener(Self::edit_binding)) .on_action(cx.listener(Self::create_binding)) + .on_action(cx.listener(Self::open_create_keybinding_modal)) .on_action(cx.listener(Self::delete_binding)) .on_action(cx.listener(Self::copy_action_to_clipboard)) .on_action(cx.listener(Self::copy_context_to_clipboard)) @@ -1690,7 +1751,7 @@ impl Render for KeymapEditor { .child( h_flex() .gap_1() - .min_w_64() + .min_w_96() .child( IconButton::new( "KeymapEditorToggleFiltersIcon", @@ -1765,7 +1826,7 @@ impl Render for KeymapEditor { .child( h_flex() .w_full() - .pl_2() + .px_1p5() .gap_1() .justify_end() .child( @@ -1809,15 +1870,30 @@ impl Render for KeymapEditor { ), ) .child( - Button::new("edit-in-json", "Edit in keymap.json") - .style(ButtonStyle::Outlined) + Button::new("edit-in-json", "Edit in JSON") + .style(ButtonStyle::Subtle) .on_click(|_, window, cx| { window.dispatch_action( zed_actions::OpenKeymapFile.boxed_clone(), cx, ); }) - ), + ) + .child( + Button::new("create", "Create Keybinding") + .style(ButtonStyle::Outlined) + .key_binding( + ui::KeyBinding::for_action_in(&OpenCreateKeybindingModal, &focus_handle, cx) + .map(|kb| kb.size(rems_from_px(10.))), + ) + .on_click(|_, window, cx| { + window.dispatch_action( + OpenCreateKeybindingModal.boxed_clone(), + cx, + ); + }) + ) + ) ), ) @@ -1828,7 +1904,7 @@ impl Render for KeymapEditor { h_flex() .gap_2() .child(self.keystroke_editor.clone()) - .child(div().min_w_64()), // Spacer div to align with the search input + .child(div().min_w_96()), // Spacer div to align with the search input ) }, ), @@ -2177,7 +2253,10 @@ struct KeybindingEditorModal { editing_keybind_idx: usize, keybind_editor: Entity, context_editor: Entity, + action_editor: Option>, action_arguments_editor: Option>, + action_name_to_static: HashMap, + selected_action_name: Option<&'static str>, fs: Arc, error: Option, keymap_editor: Entity, @@ -2191,6 +2270,9 @@ impl EventEmitter for KeybindingEditorModal {} impl Focusable for KeybindingEditorModal { fn focus_handle(&self, cx: &App) -> FocusHandle { + if let Some(action_editor) = &self.action_editor { + return action_editor.focus_handle(cx); + } self.keybind_editor.focus_handle(cx) } } @@ -2250,16 +2332,53 @@ impl KeybindingEditorModal { input }); - let action_arguments_editor = editing_keybind.action().has_schema.then(|| { - let arguments = editing_keybind - .action() - .arguments - .as_ref() - .map(|args| args.text.clone()); + let has_action_editor = create && editing_keybind.action().name == gpui::NoAction.name(); + + let (action_editor, action_name_to_static) = if has_action_editor { + let actions: Vec<&'static str> = cx.all_action_names().to_vec(); + + let humanized_names: HashMap<&'static str, SharedString> = actions + .iter() + .map(|&name| (name, command_palette::humanize_action_name(name).into())) + .collect(); + + let action_name_to_static: HashMap = actions + .iter() + .map(|&name| (name.to_string(), name)) + .collect(); + + let editor = cx.new(|cx| { + let input = InputField::new(window, cx, "Type an action name") + .label("Action") + .label_size(LabelSize::Default); + + input.editor().update(cx, |editor, _cx| { + editor.set_completion_provider(Some(std::rc::Rc::new( + ActionCompletionProvider::new(actions, humanized_names), + ))); + }); + + input + }); + + (Some(editor), action_name_to_static) + } else { + (None, HashMap::default()) + }; + + let action_has_schema = editing_keybind.action().has_schema; + let action_name_for_args = editing_keybind.action().name; + let action_args = editing_keybind + .action() + .arguments + .as_ref() + .map(|args| args.text.clone()); + + let action_arguments_editor = action_has_schema.then(|| { cx.new(|cx| { ActionArgumentsEditor::new( - editing_keybind.action().name, - arguments, + action_name_for_args, + action_args.clone(), action_args_temp_dir, workspace.clone(), window, @@ -2269,6 +2388,7 @@ impl KeybindingEditorModal { }); let focus_state = KeybindingEditorModalFocusState::new( + action_editor.as_ref().map(|e| e.focus_handle(cx)), keybind_editor.focus_handle(cx), action_arguments_editor .as_ref() @@ -2283,7 +2403,10 @@ impl KeybindingEditorModal { fs, keybind_editor, context_editor, + action_editor, action_arguments_editor, + action_name_to_static, + selected_action_name: None, error: None, keymap_editor, workspace, @@ -2291,6 +2414,76 @@ impl KeybindingEditorModal { } } + fn add_action_arguments_input(&mut self, window: &mut Window, cx: &mut Context) { + let Some(action_editor) = &self.action_editor else { + return; + }; + + let action_name_str = action_editor.read(cx).editor.read(cx).text(cx); + let current_action = self.action_name_to_static.get(&action_name_str).copied(); + + if current_action == self.selected_action_name { + return; + } + + self.selected_action_name = current_action; + + let Some(action_name) = current_action else { + if self.action_arguments_editor.is_some() { + self.action_arguments_editor = None; + self.rebuild_focus_state(cx); + cx.notify(); + } + return; + }; + + let (action_has_schema, temp_dir) = { + let keymap_editor = self.keymap_editor.read(cx); + let has_schema = keymap_editor.actions_with_schemas.contains(action_name); + let temp_dir = keymap_editor + .action_args_temp_dir + .as_ref() + .map(|dir| dir.path().to_path_buf()); + (has_schema, temp_dir) + }; + + let currently_has_editor = self.action_arguments_editor.is_some(); + + if action_has_schema && !currently_has_editor { + let workspace = self.workspace.clone(); + + let new_editor = cx.new(|cx| { + ActionArgumentsEditor::new( + action_name, + None, + temp_dir.as_deref(), + workspace, + window, + cx, + ) + }); + + self.action_arguments_editor = Some(new_editor); + self.rebuild_focus_state(cx); + cx.notify(); + } else if !action_has_schema && currently_has_editor { + self.action_arguments_editor = None; + self.rebuild_focus_state(cx); + cx.notify(); + } + } + + fn rebuild_focus_state(&mut self, cx: &App) { + self.focus_state = KeybindingEditorModalFocusState::new( + self.action_editor.as_ref().map(|e| e.focus_handle(cx)), + self.keybind_editor.focus_handle(cx), + self.action_arguments_editor + .as_ref() + .map(|args_editor| args_editor.focus_handle(cx)), + self.context_editor.focus_handle(cx), + ); + } + fn set_error(&mut self, error: InputError, cx: &mut Context) -> bool { if self .error @@ -2305,7 +2498,25 @@ impl KeybindingEditorModal { } } + fn get_selected_action_name(&self, cx: &App) -> anyhow::Result<&'static str> { + if let Some(selector) = self.action_editor.as_ref() { + let action_name_str = selector.read(cx).editor.read(cx).text(cx); + + if action_name_str.is_empty() { + anyhow::bail!("Action name is required"); + } + + self.action_name_to_static + .get(&action_name_str) + .copied() + .ok_or_else(|| anyhow::anyhow!("Action '{}' not found", action_name_str)) + } else { + Ok(self.editing_keybind.action().name) + } + } + fn validate_action_arguments(&self, cx: &App) -> anyhow::Result> { + let action_name = self.get_selected_action_name(cx)?; let action_arguments = self .action_arguments_editor .as_ref() @@ -2319,7 +2530,7 @@ impl KeybindingEditorModal { }) .transpose()?; - cx.build_action(self.editing_keybind.action().name, value) + cx.build_action(action_name, value) .context("Failed to validate action arguments")?; Ok(action_arguments) } @@ -2419,13 +2630,31 @@ impl KeybindingEditorModal { let create = self.creating; let keyboard_mapper = cx.keyboard_mapper().clone(); - cx.spawn(async move |this, cx| { - let action_name = existing_keybind.action().name; - let humanized_action_name = existing_keybind.action().humanized_name.clone(); + let action_name = self + .get_selected_action_name(cx) + .map_err(InputError::error)?; + + let humanized_action_name: SharedString = + command_palette::humanize_action_name(action_name).into(); + + let action_information = ActionInformation::new( + action_name, + None, + &HashSet::default(), + cx.action_documentation(), + &self.keymap_editor.read(cx).humanized_action_names, + ); + let keybind_for_save = if create { + ProcessedBinding::Unmapped(action_information) + } else { + existing_keybind + }; + + cx.spawn(async move |this, cx| { match save_keybinding_update( create, - existing_keybind, + keybind_for_save, &action_mapping, new_action_args.as_deref(), &fs, @@ -2474,13 +2703,57 @@ impl KeybindingEditorModal { Ok(()) } + fn is_any_editor_showing_completions(&self, window: &Window, cx: &App) -> bool { + let is_editor_showing_completions = + |focus_handle: &FocusHandle, editor_entity: &Entity| -> bool { + focus_handle.contains_focused(window, cx) + && editor_entity.read_with(cx, |editor, _cx| { + editor + .context_menu() + .borrow() + .as_ref() + .is_some_and(|menu| menu.visible()) + }) + }; + + self.action_editor.as_ref().is_some_and(|action_editor| { + let focus_handle = action_editor.read(cx).focus_handle(cx); + let editor_entity = action_editor.read(cx).editor(); + is_editor_showing_completions(&focus_handle, editor_entity) + }) || { + let focus_handle = self.context_editor.read(cx).focus_handle(cx); + let editor_entity = self.context_editor.read(cx).editor(); + is_editor_showing_completions(&focus_handle, editor_entity) + } || self + .action_arguments_editor + .as_ref() + .is_some_and(|args_editor| { + let focus_handle = args_editor.read(cx).focus_handle(cx); + let editor_entity = &args_editor.read(cx).editor; + is_editor_showing_completions(&focus_handle, editor_entity) + }) + } + fn key_context(&self) -> KeyContext { let mut key_context = KeyContext::new_with_defaults(); key_context.add("KeybindEditorModal"); key_context } + fn key_context_internal(&self, window: &Window, cx: &App) -> KeyContext { + let mut key_context = self.key_context(); + + if self.is_any_editor_showing_completions(window, cx) { + key_context.add("showing_completions"); + } + + key_context + } + fn focus_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context) { + if self.is_any_editor_showing_completions(window, cx) { + return; + } self.focus_state.focus_next(window, cx); } @@ -2490,6 +2763,9 @@ impl KeybindingEditorModal { window: &mut Window, cx: &mut Context, ) { + if self.is_any_editor_showing_completions(window, cx) { + return; + } self.focus_state.focus_previous(window, cx); } @@ -2548,18 +2824,24 @@ impl KeybindingEditorModal { } impl Render for KeybindingEditorModal { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + self.add_action_arguments_input(window, cx); + let theme = cx.theme().colors(); let matching_bindings_count = self.get_matching_bindings_count(cx); + let key_context = self.key_context_internal(window, cx); + let showing_completions = key_context.contains("showing_completions"); v_flex() .w(rems(34.)) .elevation_3(cx) - .key_context(self.key_context()) - .on_action(cx.listener(Self::focus_next)) - .on_action(cx.listener(Self::focus_prev)) + .key_context(key_context) .on_action(cx.listener(Self::confirm)) .on_action(cx.listener(Self::cancel)) + .when(!showing_completions, |this| { + this.on_action(cx.listener(Self::focus_next)) + .on_action(cx.listener(Self::focus_prev)) + }) .child( Modal::new("keybinding_editor_modal", None) .header( @@ -2571,25 +2853,36 @@ impl Render for KeybindingEditorModal { .gap_0p5() .border_b_1() .border_color(theme.border_variant) - .child(Label::new( - self.editing_keybind.action().humanized_name.clone(), - )) - .when_some( - self.editing_keybind.action().documentation, - |this, docs| { - this.child( - Label::new(docs) - .size(LabelSize::Small) - .color(Color::Muted), - ) - }, - ), + .when(!self.creating, |this| { + this.child(Label::new( + self.editing_keybind.action().humanized_name.clone(), + )) + .when_some( + self.editing_keybind.action().documentation, + |this, docs| { + this.child( + Label::new(docs) + .size(LabelSize::Small) + .color(Color::Muted), + ) + }, + ) + }) + .when(self.creating, |this| { + this.child(Label::new("Create Keybinding")) + }), ), ) .section( Section::new().child( v_flex() .gap_2p5() + .when_some( + self.creating + .then_some(()) + .and_then(|_| self.action_editor.as_ref()), + |this, selector| this.child(selector.clone()), + ) .child( v_flex() .gap_1() @@ -2678,15 +2971,21 @@ struct KeybindingEditorModalFocusState { impl KeybindingEditorModalFocusState { fn new( + action_editor: Option, keystrokes: FocusHandle, - action_input: Option, + action_arguments: Option, context: FocusHandle, ) -> Self { Self { handles: Vec::from_iter( - [Some(keystrokes), action_input, Some(context)] - .into_iter() - .flatten(), + [ + action_editor, + Some(keystrokes), + action_arguments, + Some(context), + ] + .into_iter() + .flatten(), ), } } @@ -2916,23 +3215,23 @@ impl ActionArgumentsEditor { } impl Render for ActionArgumentsEditor { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let background_color; - let border_color; + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let settings = theme::ThemeSettings::get_global(cx); + let colors = cx.theme().colors(); + + let border_color = if self.is_loading { + colors.border_disabled + } else if self.focus_handle.contains_focused(window, cx) { + colors.border_focused + } else { + colors.border_variant + }; + let text_style = { - let colors = cx.theme().colors(); - let settings = theme::ThemeSettings::get_global(cx); - background_color = colors.editor_background; - border_color = if self.is_loading { - colors.border_disabled - } else { - colors.border_variant - }; TextStyleRefinement { font_size: Some(rems(0.875).into()), font_weight: Some(settings.buffer_font.weight), line_height: Some(relative(1.2)), - font_style: Some(gpui::FontStyle::Normal), color: self.is_loading.then_some(colors.text_disabled), ..Default::default() } @@ -2941,20 +3240,17 @@ impl Render for ActionArgumentsEditor { self.editor .update(cx, |editor, _| editor.set_text_style_refinement(text_style)); - v_flex().w_full().child( - h_flex() - .min_h_8() - .min_w_48() - .px_2() - .py_1p5() - .flex_grow() - .rounded_lg() - .bg(background_color) - .border_1() - .border_color(border_color) - .track_focus(&self.focus_handle) - .child(self.editor.clone()), - ) + h_flex() + .min_h_8() + .min_w_48() + .px_2() + .flex_grow() + .rounded_md() + .bg(cx.theme().colors().editor_background) + .border_1() + .border_color(border_color) + .track_focus(&self.focus_handle) + .child(self.editor.clone()) } }