Merge pull request #1198 from zed-industries/keyboard-toggle-search-options

Toggle search options via the keyboard
This commit is contained in:
Antonio Scandurra 2022-06-16 14:49:37 +02:00 committed by GitHub
commit cef85f5d84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 169 additions and 59 deletions

View file

@ -15,7 +15,7 @@
"shift-cmd-}": "pane::ActivateNextItem", "shift-cmd-}": "pane::ActivateNextItem",
"cmd-w": "pane::CloseActiveItem", "cmd-w": "pane::CloseActiveItem",
"cmd-shift-W": "workspace::CloseWindow", "cmd-shift-W": "workspace::CloseWindow",
"alt-cmd-w": "pane::CloseInactiveItems", "alt-cmd-t": "pane::CloseInactiveItems",
"cmd-s": "workspace::Save", "cmd-s": "workspace::Save",
"cmd-shift-S": "workspace::SaveAs", "cmd-shift-S": "workspace::SaveAs",
"cmd-=": "zed::IncreaseBufferFontSize", "cmd-=": "zed::IncreaseBufferFontSize",
@ -141,14 +141,6 @@
] ]
} }
}, },
{
"context": "Pane",
"bindings": {
"cmd-f": "project_search::ToggleFocus",
"cmd-g": "search::SelectNextMatch",
"cmd-shift-G": "search::SelectPrevMatch"
}
},
{ {
"context": "BufferSearchBar", "context": "BufferSearchBar",
"bindings": { "bindings": {
@ -158,6 +150,17 @@
"shift-enter": "search::SelectPrevMatch" "shift-enter": "search::SelectPrevMatch"
} }
}, },
{
"context": "Pane",
"bindings": {
"cmd-f": "project_search::ToggleFocus",
"cmd-g": "search::SelectNextMatch",
"cmd-shift-G": "search::SelectPrevMatch",
"alt-cmd-c": "search::ToggleCaseSensitive",
"alt-cmd-w": "search::ToggleWholeWord",
"alt-cmd-r": "search::ToggleRegex"
}
},
// Bindings from VS Code // Bindings from VS Code
{ {
"context": "Editor", "context": "Editor",

View file

@ -1,12 +1,13 @@
use crate::{ use crate::{
active_match_index, match_index_for_direction, query_suggestion_for_editor, Direction, active_match_index, match_index_for_direction, query_suggestion_for_editor, Direction,
SearchOption, SelectNextMatch, SelectPrevMatch, SearchOption, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex,
ToggleWholeWord,
}; };
use collections::HashMap; use collections::HashMap;
use editor::{Anchor, Autoscroll, Editor}; use editor::{Anchor, Autoscroll, Editor};
use gpui::{ use gpui::{
actions, elements::*, impl_actions, impl_internal_actions, platform::CursorStyle, AppContext, actions, elements::*, impl_actions, platform::CursorStyle, Action, AppContext, Entity,
Entity, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle,
WeakViewHandle, WeakViewHandle,
}; };
use language::OffsetRangeExt; use language::OffsetRangeExt;
@ -21,12 +22,8 @@ pub struct Deploy {
pub focus: bool, pub focus: bool,
} }
#[derive(Clone, PartialEq)]
pub struct ToggleSearchOption(pub SearchOption);
actions!(buffer_search, [Dismiss, FocusEditor]); actions!(buffer_search, [Dismiss, FocusEditor]);
impl_actions!(buffer_search, [Deploy]); impl_actions!(buffer_search, [Deploy]);
impl_internal_actions!(buffer_search, [ToggleSearchOption]);
pub enum Event { pub enum Event {
UpdateLocation, UpdateLocation,
@ -36,12 +33,28 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(BufferSearchBar::deploy); cx.add_action(BufferSearchBar::deploy);
cx.add_action(BufferSearchBar::dismiss); cx.add_action(BufferSearchBar::dismiss);
cx.add_action(BufferSearchBar::focus_editor); cx.add_action(BufferSearchBar::focus_editor);
cx.add_action(BufferSearchBar::toggle_search_option);
cx.add_action(BufferSearchBar::select_next_match); cx.add_action(BufferSearchBar::select_next_match);
cx.add_action(BufferSearchBar::select_prev_match); cx.add_action(BufferSearchBar::select_prev_match);
cx.add_action(BufferSearchBar::select_next_match_on_pane); cx.add_action(BufferSearchBar::select_next_match_on_pane);
cx.add_action(BufferSearchBar::select_prev_match_on_pane); cx.add_action(BufferSearchBar::select_prev_match_on_pane);
cx.add_action(BufferSearchBar::handle_editor_cancel); cx.add_action(BufferSearchBar::handle_editor_cancel);
add_toggle_option_action::<ToggleCaseSensitive>(SearchOption::CaseSensitive, cx);
add_toggle_option_action::<ToggleWholeWord>(SearchOption::WholeWord, cx);
add_toggle_option_action::<ToggleRegex>(SearchOption::Regex, cx);
}
fn add_toggle_option_action<A: Action>(option: SearchOption, cx: &mut MutableAppContext) {
cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext<Pane>| {
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
if search_bar.update(cx, |search_bar, cx| search_bar.show(false, false, cx)) {
search_bar.update(cx, |search_bar, cx| {
search_bar.toggle_search_option(option, cx);
});
return;
}
}
cx.propagate_action();
});
} }
pub struct BufferSearchBar { pub struct BufferSearchBar {
@ -215,16 +228,18 @@ impl BufferSearchBar {
cx.notify(); cx.notify();
} }
fn show(&mut self, focus: bool, cx: &mut ViewContext<Self>) -> bool { fn show(&mut self, focus: bool, suggest_query: bool, cx: &mut ViewContext<Self>) -> bool {
let editor = if let Some(editor) = self.active_editor.clone() { let editor = if let Some(editor) = self.active_editor.clone() {
editor editor
} else { } else {
return false; return false;
}; };
let text = query_suggestion_for_editor(&editor, cx); if suggest_query {
if !text.is_empty() { let text = query_suggestion_for_editor(&editor, cx);
self.set_query(&text, cx); if !text.is_empty() {
self.set_query(&text, cx);
}
} }
if focus { if focus {
@ -253,11 +268,12 @@ impl BufferSearchBar {
fn render_search_option( fn render_search_option(
&self, &self,
icon: &str, icon: &str,
search_option: SearchOption, option: SearchOption,
cx: &mut RenderContext<Self>, cx: &mut RenderContext<Self>,
) -> ElementBox { ) -> ElementBox {
let is_active = self.is_search_option_enabled(search_option); let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
MouseEventHandler::new::<Self, _, _>(search_option as usize, cx, |state, cx| { let is_active = self.is_search_option_enabled(option);
MouseEventHandler::new::<Self, _, _>(option as usize, cx, |state, cx| {
let style = &cx let style = &cx
.global::<Settings>() .global::<Settings>()
.theme .theme
@ -269,8 +285,15 @@ impl BufferSearchBar {
.with_style(style.container) .with_style(style.container)
.boxed() .boxed()
}) })
.on_click(move |_, _, cx| cx.dispatch_action(ToggleSearchOption(search_option))) .on_click(move |_, _, cx| cx.dispatch_any_action(option.to_toggle_action()))
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.with_tooltip::<Self, _>(
option as usize,
format!("Toggle {}", option.label()),
Some(option.to_toggle_action()),
tooltip_style,
cx,
)
.boxed() .boxed()
} }
@ -280,6 +303,20 @@ impl BufferSearchBar {
direction: Direction, direction: Direction,
cx: &mut RenderContext<Self>, cx: &mut RenderContext<Self>,
) -> ElementBox { ) -> ElementBox {
let action: Box<dyn Action>;
let tooltip;
match direction {
Direction::Prev => {
action = Box::new(SelectPrevMatch);
tooltip = "Select Previous Match";
}
Direction::Next => {
action = Box::new(SelectNextMatch);
tooltip = "Select Next Match";
}
};
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
enum NavButton {} enum NavButton {}
MouseEventHandler::new::<NavButton, _, _>(direction as usize, cx, |state, cx| { MouseEventHandler::new::<NavButton, _, _>(direction as usize, cx, |state, cx| {
let style = &cx let style = &cx
@ -293,17 +330,24 @@ impl BufferSearchBar {
.with_style(style.container) .with_style(style.container)
.boxed() .boxed()
}) })
.on_click(move |_, _, cx| match direction { .on_click({
Direction::Prev => cx.dispatch_action(SelectPrevMatch), let action = action.boxed_clone();
Direction::Next => cx.dispatch_action(SelectNextMatch), move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.with_tooltip::<NavButton, _>(
direction as usize,
tooltip.to_string(),
Some(action),
tooltip_style,
cx,
)
.boxed() .boxed()
} }
fn deploy(pane: &mut Pane, action: &Deploy, cx: &mut ViewContext<Pane>) { fn deploy(pane: &mut Pane, action: &Deploy, cx: &mut ViewContext<Pane>) {
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() { if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
if search_bar.update(cx, |search_bar, cx| search_bar.show(action.focus, cx)) { if search_bar.update(cx, |search_bar, cx| search_bar.show(action.focus, true, cx)) {
return; return;
} }
} }
@ -334,18 +378,14 @@ impl BufferSearchBar {
} }
} }
fn toggle_search_option( fn toggle_search_option(&mut self, search_option: SearchOption, cx: &mut ViewContext<Self>) {
&mut self,
ToggleSearchOption(search_option): &ToggleSearchOption,
cx: &mut ViewContext<Self>,
) {
let value = match search_option { let value = match search_option {
SearchOption::WholeWord => &mut self.whole_word, SearchOption::WholeWord => &mut self.whole_word,
SearchOption::CaseSensitive => &mut self.case_sensitive, SearchOption::CaseSensitive => &mut self.case_sensitive,
SearchOption::Regex => &mut self.regex, SearchOption::Regex => &mut self.regex,
}; };
*value = !*value; *value = !*value;
self.update_matches(true, cx); self.update_matches(false, cx);
cx.notify(); cx.notify();
} }
@ -591,7 +631,7 @@ mod tests {
let search_bar = cx.add_view(Default::default(), |cx| { let search_bar = cx.add_view(Default::default(), |cx| {
let mut search_bar = BufferSearchBar::new(cx); let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx); search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(false, cx); search_bar.show(false, true, cx);
search_bar search_bar
}); });
@ -619,7 +659,7 @@ mod tests {
// Switch to a case sensitive search. // Switch to a case sensitive search.
search_bar.update(cx, |search_bar, cx| { search_bar.update(cx, |search_bar, cx| {
search_bar.toggle_search_option(&ToggleSearchOption(SearchOption::CaseSensitive), cx); search_bar.toggle_search_option(SearchOption::CaseSensitive, cx);
}); });
editor.next_notification(&cx).await; editor.next_notification(&cx).await;
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
@ -676,7 +716,7 @@ mod tests {
// Switch to a whole word search. // Switch to a whole word search.
search_bar.update(cx, |search_bar, cx| { search_bar.update(cx, |search_bar, cx| {
search_bar.toggle_search_option(&ToggleSearchOption(SearchOption::WholeWord), cx); search_bar.toggle_search_option(SearchOption::WholeWord, cx);
}); });
editor.next_notification(&cx).await; editor.next_notification(&cx).await;
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {

View file

@ -1,13 +1,14 @@
use crate::{ use crate::{
active_match_index, match_index_for_direction, query_suggestion_for_editor, Direction, active_match_index, match_index_for_direction, query_suggestion_for_editor, Direction,
SearchOption, SelectNextMatch, SelectPrevMatch, ToggleSearchOption, SearchOption, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex,
ToggleWholeWord,
}; };
use collections::HashMap; use collections::HashMap;
use editor::{Anchor, Autoscroll, Editor, MultiBuffer, SelectAll}; use editor::{Anchor, Autoscroll, Editor, MultiBuffer, SelectAll};
use gpui::{ use gpui::{
actions, elements::*, platform::CursorStyle, AppContext, ElementBox, Entity, ModelContext, actions, elements::*, platform::CursorStyle, Action, AppContext, ElementBox, Entity,
ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ModelContext, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View,
ViewHandle, WeakModelHandle, WeakViewHandle, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
}; };
use menu::Confirm; use menu::Confirm;
use project::{search::SearchQuery, Project}; use project::{search::SearchQuery, Project};
@ -35,11 +36,26 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(ProjectSearchView::deploy); cx.add_action(ProjectSearchView::deploy);
cx.add_action(ProjectSearchBar::search); cx.add_action(ProjectSearchBar::search);
cx.add_action(ProjectSearchBar::search_in_new); cx.add_action(ProjectSearchBar::search_in_new);
cx.add_action(ProjectSearchBar::toggle_search_option);
cx.add_action(ProjectSearchBar::select_next_match); cx.add_action(ProjectSearchBar::select_next_match);
cx.add_action(ProjectSearchBar::select_prev_match); cx.add_action(ProjectSearchBar::select_prev_match);
cx.add_action(ProjectSearchBar::toggle_focus); cx.add_action(ProjectSearchBar::toggle_focus);
cx.capture_action(ProjectSearchBar::tab); cx.capture_action(ProjectSearchBar::tab);
add_toggle_option_action::<ToggleCaseSensitive>(SearchOption::CaseSensitive, cx);
add_toggle_option_action::<ToggleWholeWord>(SearchOption::WholeWord, cx);
add_toggle_option_action::<ToggleRegex>(SearchOption::Regex, cx);
}
fn add_toggle_option_action<A: Action>(option: SearchOption, cx: &mut MutableAppContext) {
cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext<Pane>| {
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<ProjectSearchBar>() {
if search_bar.update(cx, |search_bar, cx| {
search_bar.toggle_search_option(option, cx)
}) {
return;
}
}
cx.propagate_action();
});
} }
struct ProjectSearch { struct ProjectSearch {
@ -653,11 +669,7 @@ impl ProjectSearchBar {
} }
} }
fn toggle_search_option( fn toggle_search_option(&mut self, option: SearchOption, cx: &mut ViewContext<Self>) -> bool {
&mut self,
ToggleSearchOption(option): &ToggleSearchOption,
cx: &mut ViewContext<Self>,
) {
if let Some(search_view) = self.active_project_search.as_ref() { if let Some(search_view) = self.active_project_search.as_ref() {
search_view.update(cx, |search_view, cx| { search_view.update(cx, |search_view, cx| {
let value = match option { let value = match option {
@ -669,6 +681,9 @@ impl ProjectSearchBar {
search_view.search(cx); search_view.search(cx);
}); });
cx.notify(); cx.notify();
true
} else {
false
} }
} }
@ -678,6 +693,20 @@ impl ProjectSearchBar {
direction: Direction, direction: Direction,
cx: &mut RenderContext<Self>, cx: &mut RenderContext<Self>,
) -> ElementBox { ) -> ElementBox {
let action: Box<dyn Action>;
let tooltip;
match direction {
Direction::Prev => {
action = Box::new(SelectPrevMatch);
tooltip = "Select Previous Match";
}
Direction::Next => {
action = Box::new(SelectNextMatch);
tooltip = "Select Next Match";
}
};
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
enum NavButton {} enum NavButton {}
MouseEventHandler::new::<NavButton, _, _>(direction as usize, cx, |state, cx| { MouseEventHandler::new::<NavButton, _, _>(direction as usize, cx, |state, cx| {
let style = &cx let style = &cx
@ -691,11 +720,18 @@ impl ProjectSearchBar {
.with_style(style.container) .with_style(style.container)
.boxed() .boxed()
}) })
.on_click(move |_, _, cx| match direction { .on_click({
Direction::Prev => cx.dispatch_action(SelectPrevMatch), let action = action.boxed_clone();
Direction::Next => cx.dispatch_action(SelectNextMatch), move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.with_tooltip::<NavButton, _>(
direction as usize,
tooltip.to_string(),
Some(action),
tooltip_style,
cx,
)
.boxed() .boxed()
} }
@ -705,8 +741,9 @@ impl ProjectSearchBar {
option: SearchOption, option: SearchOption,
cx: &mut RenderContext<Self>, cx: &mut RenderContext<Self>,
) -> ElementBox { ) -> ElementBox {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let is_active = self.is_option_enabled(option, cx); let is_active = self.is_option_enabled(option, cx);
MouseEventHandler::new::<ProjectSearchBar, _, _>(option as usize, cx, |state, cx| { MouseEventHandler::new::<Self, _, _>(option as usize, cx, |state, cx| {
let style = &cx let style = &cx
.global::<Settings>() .global::<Settings>()
.theme .theme
@ -718,8 +755,15 @@ impl ProjectSearchBar {
.with_style(style.container) .with_style(style.container)
.boxed() .boxed()
}) })
.on_click(move |_, _, cx| cx.dispatch_action(ToggleSearchOption(option))) .on_click(move |_, _, cx| cx.dispatch_any_action(option.to_toggle_action()))
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.with_tooltip::<Self, _>(
option as usize,
format!("Toggle {}", option.label()),
Some(option.to_toggle_action()),
tooltip_style,
cx,
)
.boxed() .boxed()
} }

View file

@ -1,6 +1,6 @@
pub use buffer_search::BufferSearchBar; pub use buffer_search::BufferSearchBar;
use editor::{display_map::ToDisplayPoint, Anchor, Bias, Editor, MultiBufferSnapshot}; use editor::{display_map::ToDisplayPoint, Anchor, Bias, Editor, MultiBufferSnapshot};
use gpui::{actions, impl_internal_actions, MutableAppContext, ViewHandle}; use gpui::{actions, Action, MutableAppContext, ViewHandle};
pub use project_search::{ProjectSearchBar, ProjectSearchView}; pub use project_search::{ProjectSearchBar, ProjectSearchView};
use std::{ use std::{
cmp::{self, Ordering}, cmp::{self, Ordering},
@ -15,11 +15,16 @@ pub fn init(cx: &mut MutableAppContext) {
project_search::init(cx); project_search::init(cx);
} }
#[derive(Clone, PartialEq)] actions!(
pub struct ToggleSearchOption(pub SearchOption); search,
[
actions!(search, [SelectNextMatch, SelectPrevMatch]); ToggleWholeWord,
impl_internal_actions!(search, [ToggleSearchOption]); ToggleCaseSensitive,
ToggleRegex,
SelectNextMatch,
SelectPrevMatch
]
);
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub enum SearchOption { pub enum SearchOption {
@ -28,6 +33,24 @@ pub enum SearchOption {
Regex, Regex,
} }
impl SearchOption {
pub fn label(&self) -> &'static str {
match self {
SearchOption::WholeWord => "Match Whole Word",
SearchOption::CaseSensitive => "Match Case",
SearchOption::Regex => "Use Regular Expression",
}
}
pub fn to_toggle_action(&self) -> Box<dyn Action> {
match self {
SearchOption::WholeWord => Box::new(ToggleWholeWord),
SearchOption::CaseSensitive => Box::new(ToggleCaseSensitive),
SearchOption::Regex => Box::new(ToggleRegex),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
pub enum Direction { pub enum Direction {
Prev, Prev,