diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 8d4871d956..d33df02747 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -196,7 +196,7 @@ } }, { - "context": "BufferSearchBar && in_replace", + "context": "BufferSearchBar && in_replace > Editor", "bindings": { "enter": "search::ReplaceNext", "ctrl-enter": "search::ReplaceAll" diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index a980ae14e2..b405ee1852 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -232,7 +232,7 @@ } }, { - "context": "BufferSearchBar && in_replace", + "context": "BufferSearchBar && in_replace > Editor", "bindings": { "enter": "search::ReplaceNext", "cmd-enter": "search::ReplaceAll" diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs index 93ebfa0643..09b29c0436 100644 --- a/crates/breadcrumbs/src/breadcrumbs.rs +++ b/crates/breadcrumbs/src/breadcrumbs.rs @@ -1,7 +1,7 @@ use editor::Editor; use gpui::{ - Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription, - ViewContext, + Element, EventEmitter, FocusableView, IntoElement, ParentElement, Render, StyledText, + Subscription, ViewContext, }; use itertools::Itertools; use std::cmp; @@ -90,17 +90,30 @@ impl Render for Breadcrumbs { ButtonLike::new("toggle outline view") .child(breadcrumbs_stack) .style(ButtonStyle::Transparent) - .on_click(move |_, cx| { - if let Some(editor) = editor.upgrade() { - outline::toggle(editor, &editor::actions::ToggleOutline, cx) + .on_click({ + let editor = editor.clone(); + move |_, cx| { + if let Some(editor) = editor.upgrade() { + outline::toggle(editor, &editor::actions::ToggleOutline, cx) + } } }) - .tooltip(|cx| { - Tooltip::for_action( - "Show symbol outline", - &editor::actions::ToggleOutline, - cx, - ) + .tooltip(move |cx| { + if let Some(editor) = editor.upgrade() { + let focus_handle = editor.read(cx).focus_handle(cx); + Tooltip::for_action_in( + "Show symbol outline", + &editor::actions::ToggleOutline, + &focus_handle, + cx, + ) + } else { + Tooltip::for_action( + "Show symbol outline", + &editor::actions::ToggleOutline, + cx, + ) + } }), ), None => element diff --git a/crates/quick_action_bar/src/quick_action_bar.rs b/crates/quick_action_bar/src/quick_action_bar.rs index 57418b54b7..fb05065a19 100644 --- a/crates/quick_action_bar/src/quick_action_bar.rs +++ b/crates/quick_action_bar/src/quick_action_bar.rs @@ -8,8 +8,8 @@ use editor::actions::{ use editor::{Editor, EditorSettings}; use gpui::{ - Action, AnchorCorner, ClickEvent, ElementId, EventEmitter, InteractiveElement, ParentElement, - Render, Styled, Subscription, View, ViewContext, WeakView, + Action, AnchorCorner, ClickEvent, ElementId, EventEmitter, FocusHandle, FocusableView, + InteractiveElement, ParentElement, Render, Styled, Subscription, View, ViewContext, WeakView, }; use search::{buffer_search, BufferSearchBar}; use settings::{Settings, SettingsStore}; @@ -110,12 +110,15 @@ impl Render for QuickActionBar { ) }; + let focus_handle = editor.read(cx).focus_handle(cx); + let search_button = editor.is_singleton(cx).then(|| { QuickActionBarButton::new( "toggle buffer search", IconName::MagnifyingGlass, !self.buffer_search_bar.read(cx).is_dismissed(), Box::new(buffer_search::Deploy::find()), + focus_handle.clone(), "Buffer Search", { let buffer_search_bar = self.buffer_search_bar.clone(); @@ -133,6 +136,7 @@ impl Render for QuickActionBar { IconName::ZedAssistant, false, Box::new(InlineAssist::default()), + focus_handle.clone(), "Inline Assist", { let workspace = self.workspace.clone(); @@ -321,6 +325,7 @@ struct QuickActionBarButton { icon: IconName, toggled: bool, action: Box, + focus_handle: FocusHandle, tooltip: SharedString, on_click: Box, } @@ -331,6 +336,7 @@ impl QuickActionBarButton { icon: IconName, toggled: bool, action: Box, + focus_handle: FocusHandle, tooltip: impl Into, on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static, ) -> Self { @@ -339,6 +345,7 @@ impl QuickActionBarButton { icon, toggled, action, + focus_handle, tooltip: tooltip.into(), on_click: Box::new(on_click), } @@ -355,7 +362,9 @@ impl RenderOnce for QuickActionBarButton { .icon_size(IconSize::Small) .style(ButtonStyle::Subtle) .selected(self.toggled) - .tooltip(move |cx| Tooltip::for_action(tooltip.clone(), &*action, cx)) + .tooltip(move |cx| { + Tooltip::for_action_in(tooltip.clone(), &*action, &self.focus_handle, cx) + }) .on_click(move |event, cx| (self.on_click)(event, cx)) } } diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 9ba7dfd796..6e660a963b 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -13,9 +13,10 @@ use editor::{ }; use futures::channel::oneshot; use gpui::{ - actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusableView, Hsla, - InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle, - Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext as _, WindowContext, + actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusHandle, + FocusableView, Hsla, InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, + Render, ScrollHandle, Styled, Subscription, Task, TextStyle, View, ViewContext, + VisualContext as _, WindowContext, }; use project::{ search::SearchQuery, @@ -142,6 +143,8 @@ impl Render for BufferSearchBar { return div().id("search_bar"); } + let focus_handle = self.focus_handle(cx); + let narrow_mode = self.scroll_handle.bounds().size.width / cx.rem_size() < 340. / BASE_REM_SIZE_IN_PX; let hide_inline_icons = self.editor_needed_width @@ -217,6 +220,7 @@ impl Render for BufferSearchBar { div.children(supported_options.case.then(|| { self.render_search_option_button( SearchOptions::CASE_SENSITIVE, + focus_handle.clone(), cx.listener(|this, _, cx| { this.toggle_case_sensitive(&ToggleCaseSensitive, cx) }), @@ -225,6 +229,7 @@ impl Render for BufferSearchBar { .children(supported_options.word.then(|| { self.render_search_option_button( SearchOptions::WHOLE_WORD, + focus_handle.clone(), cx.listener(|this, _, cx| { this.toggle_whole_word(&ToggleWholeWord, cx) }), @@ -233,6 +238,7 @@ impl Render for BufferSearchBar { .children(supported_options.regex.then(|| { self.render_search_option_button( SearchOptions::REGEX, + focus_handle.clone(), cx.listener(|this, _, cx| this.toggle_regex(&ToggleRegex, cx)), ) })) @@ -250,7 +256,17 @@ impl Render for BufferSearchBar { })) .selected(self.replace_enabled) .size(ButtonSize::Compact) - .tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx)), + .tooltip({ + let focus_handle = focus_handle.clone(); + move |cx| { + Tooltip::for_action_in( + "Toggle replace", + &ToggleReplace, + &focus_handle, + cx, + ) + } + }), ) }) .when(supported_options.selection, |this| { @@ -268,8 +284,16 @@ impl Render for BufferSearchBar { })) .selected(self.selection_search_enabled) .size(ButtonSize::Compact) - .tooltip(|cx| { - Tooltip::for_action("Toggle search selection", &ToggleSelection, cx) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |cx| { + Tooltip::for_action_in( + "Toggle search selection", + &ToggleSelection, + &focus_handle, + cx, + ) + } }), ) }) @@ -280,8 +304,16 @@ impl Render for BufferSearchBar { IconButton::new("select-all", ui::IconName::SelectAll) .on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone())) .size(ButtonSize::Compact) - .tooltip(|cx| { - Tooltip::for_action("Select all matches", &SelectAllMatches, cx) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |cx| { + Tooltip::for_action_in( + "Select all matches", + &SelectAllMatches, + &focus_handle, + cx, + ) + } }), ) .child(render_nav_button( @@ -289,12 +321,14 @@ impl Render for BufferSearchBar { self.active_match_index.is_some(), "Select previous match", &SelectPrevMatch, + focus_handle.clone(), )) .child(render_nav_button( ui::IconName::ChevronRight, self.active_match_index.is_some(), "Select next match", &SelectNextMatch, + focus_handle.clone(), )) .when(!narrow_mode, |this| { this.child(h_flex().ml_2().min_w(rems_from_px(40.)).child( @@ -335,8 +369,16 @@ impl Render for BufferSearchBar { .flex_none() .child( IconButton::new("search-replace-next", ui::IconName::ReplaceNext) - .tooltip(move |cx| { - Tooltip::for_action("Replace next", &ReplaceNext, cx) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |cx| { + Tooltip::for_action_in( + "Replace next match", + &ReplaceNext, + &focus_handle, + cx, + ) + } }) .on_click( cx.listener(|this, _, cx| this.replace_next(&ReplaceNext, cx)), @@ -344,8 +386,16 @@ impl Render for BufferSearchBar { ) .child( IconButton::new("search-replace-all", ui::IconName::ReplaceAll) - .tooltip(move |cx| { - Tooltip::for_action("Replace all", &ReplaceAll, cx) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |cx| { + Tooltip::for_action_in( + "Replace all matches", + &ReplaceAll, + &focus_handle, + cx, + ) + } }) .on_click( cx.listener(|this, _, cx| this.replace_all(&ReplaceAll, cx)), @@ -719,10 +769,11 @@ impl BufferSearchBar { fn render_search_option_button( &self, option: SearchOptions, + focus_handle: FocusHandle, action: impl Fn(&ClickEvent, &mut WindowContext) + 'static, ) -> impl IntoElement { let is_active = self.search_options.contains(option); - option.as_button(is_active, action) + option.as_button(is_active, focus_handle, action) } pub fn focus_editor(&mut self, _: &FocusEditor, cx: &mut ViewContext) { @@ -1122,6 +1173,7 @@ impl BufferSearchBar { }); cx.focus(handle); } + fn toggle_replace(&mut self, _: &ToggleReplace, cx: &mut ViewContext) { if self.active_searchable_item.is_some() { self.replace_enabled = !self.replace_enabled; @@ -1134,6 +1186,7 @@ impl BufferSearchBar { cx.notify(); } } + fn replace_next(&mut self, _: &ReplaceNext, cx: &mut ViewContext) { let mut should_propagate = true; if !self.dismissed && self.active_search.is_some() { @@ -1161,6 +1214,7 @@ impl BufferSearchBar { cx.stop_propagation(); } } + pub fn replace_all(&mut self, _: &ReplaceAll, cx: &mut ViewContext) { if !self.dismissed && self.active_search.is_some() { if let Some(searchable_item) = self.active_searchable_item.as_ref() { diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index ea94d27daf..12e6ccc12d 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1551,6 +1551,7 @@ impl Render for ProjectSearchBar { return div(); }; let search = search.read(cx); + let focus_handle = search.focus_handle(cx); let query_column = h_flex() .flex_1() @@ -1571,18 +1572,21 @@ impl Render for ProjectSearchBar { h_flex() .child(SearchOptions::CASE_SENSITIVE.as_button( self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx), + focus_handle.clone(), cx.listener(|this, _, cx| { this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx); }), )) .child(SearchOptions::WHOLE_WORD.as_button( self.is_option_enabled(SearchOptions::WHOLE_WORD, cx), + focus_handle.clone(), cx.listener(|this, _, cx| { this.toggle_search_option(SearchOptions::WHOLE_WORD, cx); }), )) .child(SearchOptions::REGEX.as_button( self.is_option_enabled(SearchOptions::REGEX, cx), + focus_handle.clone(), cx.listener(|this, _, cx| { this.toggle_search_option(SearchOptions::REGEX, cx); }), @@ -1603,7 +1607,17 @@ impl Render for ProjectSearchBar { .map(|search| search.read(cx).filters_enabled) .unwrap_or_default(), ) - .tooltip(|cx| Tooltip::for_action("Toggle filters", &ToggleFilters, cx)), + .tooltip({ + let focus_handle = focus_handle.clone(); + move |cx| { + Tooltip::for_action_in( + "Toggle filters", + &ToggleFilters, + &focus_handle, + cx, + ) + } + }), ) .child( IconButton::new("project-search-toggle-replace", IconName::Replace) @@ -1616,7 +1630,17 @@ impl Render for ProjectSearchBar { .map(|search| search.read(cx).replace_enabled) .unwrap_or_default(), ) - .tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx)), + .tooltip({ + let focus_handle = focus_handle.clone(); + move |cx| { + Tooltip::for_action_in( + "Toggle replace", + &ToggleReplace, + &focus_handle, + cx, + ) + } + }), ), ); @@ -1650,8 +1674,16 @@ impl Render for ProjectSearchBar { }) } })) - .tooltip(|cx| { - Tooltip::for_action("Go to previous match", &SelectPrevMatch, cx) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |cx| { + Tooltip::for_action_in( + "Go to previous match", + &SelectPrevMatch, + &focus_handle, + cx, + ) + } }), ) .child( @@ -1664,7 +1696,17 @@ impl Render for ProjectSearchBar { }) } })) - .tooltip(|cx| Tooltip::for_action("Go to next match", &SelectNextMatch, cx)), + .tooltip({ + let focus_handle = focus_handle.clone(); + move |cx| { + Tooltip::for_action_in( + "Go to next match", + &SelectNextMatch, + &focus_handle, + cx, + ) + } + }), ) .child( h_flex() @@ -1702,6 +1744,7 @@ impl Render for ProjectSearchBar { .border_color(cx.theme().colors().border) .rounded_lg() .child(self.render_text_input(&search.replacement_editor, cx)); + let focus_handle = search.replacement_editor.read(cx).focus_handle(cx); let replace_actions = h_flex().when(search.replace_enabled, |this| { this.child( IconButton::new("project-search-replace-next", IconName::ReplaceNext) @@ -1712,7 +1755,17 @@ impl Render for ProjectSearchBar { }) } })) - .tooltip(|cx| Tooltip::for_action("Replace next match", &ReplaceNext, cx)), + .tooltip({ + let focus_handle = focus_handle.clone(); + move |cx| { + Tooltip::for_action_in( + "Replace next match", + &ReplaceNext, + &focus_handle, + cx, + ) + } + }), ) .child( IconButton::new("project-search-replace-all", IconName::ReplaceAll) @@ -1723,7 +1776,17 @@ impl Render for ProjectSearchBar { }) } })) - .tooltip(|cx| Tooltip::for_action("Replace all matches", &ReplaceAll, cx)), + .tooltip({ + let focus_handle = focus_handle.clone(); + move |cx| { + Tooltip::for_action_in( + "Replace all matches", + &ReplaceAll, + &focus_handle, + cx, + ) + } + }), ) }); h_flex() @@ -1790,6 +1853,7 @@ impl Render for ProjectSearchBar { search .search_options .contains(SearchOptions::INCLUDE_IGNORED), + focus_handle.clone(), cx.listener(|this, _, cx| { this.toggle_search_option(SearchOptions::INCLUDE_IGNORED, cx); }), diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index b99672c532..d13a12576b 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -1,7 +1,7 @@ use bitflags::bitflags; pub use buffer_search::BufferSearchBar; use editor::SearchSettings; -use gpui::{actions, Action, AppContext, IntoElement}; +use gpui::{actions, Action, AppContext, FocusHandle, IntoElement}; use project::search::SearchQuery; pub use project_search::ProjectSearchView; use ui::{prelude::*, Tooltip}; @@ -106,6 +106,7 @@ impl SearchOptions { pub fn as_button( &self, active: bool, + focus_handle: FocusHandle, action: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static, ) -> impl IntoElement { IconButton::new(self.label(), self.icon()) @@ -115,7 +116,7 @@ impl SearchOptions { .tooltip({ let action = self.to_toggle_action(); let label = self.label(); - move |cx| Tooltip::for_action(label, &*action, cx) + move |cx| Tooltip::for_action_in(label, &*action, &focus_handle, cx) }) } } diff --git a/crates/search/src/search_bar.rs b/crates/search/src/search_bar.rs index 0594036c25..102f04c4b9 100644 --- a/crates/search/src/search_bar.rs +++ b/crates/search/src/search_bar.rs @@ -1,4 +1,4 @@ -use gpui::{Action, IntoElement}; +use gpui::{Action, FocusHandle, IntoElement}; use ui::IconButton; use ui::{prelude::*, Tooltip}; @@ -7,12 +7,13 @@ pub(super) fn render_nav_button( active: bool, tooltip: &'static str, action: &'static dyn Action, + focus_handle: FocusHandle, ) -> impl IntoElement { IconButton::new( SharedString::from(format!("search-nav-button-{}", action.name())), icon, ) .on_click(|_, cx| cx.dispatch_action(action.boxed_clone())) - .tooltip(move |cx| Tooltip::for_action(tooltip, action, cx)) + .tooltip(move |cx| Tooltip::for_action_in(tooltip, action, &focus_handle, cx)) .disabled(!active) } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 72f8606fa2..7d95613804 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -166,7 +166,16 @@ impl TerminalPanel { pub fn asssistant_enabled(&mut self, enabled: bool, cx: &mut ViewContext) { self.assistant_enabled = enabled; if enabled { - self.assistant_tab_bar_button = Some(cx.new_view(|_| InlineAssistTabBarButton).into()); + let focus_handle = self + .pane + .read(cx) + .active_item() + .map(|item| item.focus_handle(cx)) + .unwrap_or(self.focus_handle(cx)); + self.assistant_tab_bar_button = Some( + cx.new_view(move |_| InlineAssistTabBarButton { focus_handle }) + .into(), + ); } else { self.assistant_tab_bar_button = None; } @@ -859,16 +868,21 @@ impl Panel for TerminalPanel { } } -struct InlineAssistTabBarButton; +struct InlineAssistTabBarButton { + focus_handle: FocusHandle, +} impl Render for InlineAssistTabBarButton { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let focus_handle = self.focus_handle.clone(); IconButton::new("terminal_inline_assistant", IconName::ZedAssistant) .icon_size(IconSize::Small) .on_click(cx.listener(|_, _, cx| { cx.dispatch_action(InlineAssist::default().boxed_clone()); })) - .tooltip(move |cx| Tooltip::for_action("Inline Assist", &InlineAssist::default(), cx)) + .tooltip(move |cx| { + Tooltip::for_action_in("Inline Assist", &InlineAssist::default(), &focus_handle, cx) + }) } }