Refine search query suggestions (#3293)

This PR fixes some issues in response to feedback from Dan Abramov and
Jose Valim.

To do:

* [x] fix non-word search suggestions
* [x] add setting for disabling search suggestions

Release Notes:

- Fixed an issue where opening a search without text selected would
populate the search query with non-word characters adjacent to the
cursor.
- Added a setting, `seed_search_query_from_cursor`, which controls
whether the search query is automatically populated from the buffer when
starting a new buffer search or project search.

By default, the search query will always be set to the word under the
cursor. If you want to only populate the search query when text is
selected, you can add the following to your `~/.zed/settings.json`:

  ```json
  {
    "seed_search_query_from_cursor": "selection"
  }
  ```

If you don't want the search query to be automatically populated, even
when there is text selected, add the following:

  ```json
  {
    "seed_search_query_from_cursor": "never"
  }
  ```
This commit is contained in:
Max Brunsfeld 2023-11-09 14:25:20 -08:00
parent 67068faa1d
commit 2d3a34c864
11 changed files with 109 additions and 87 deletions

View file

@ -102,6 +102,16 @@
"selections": true "selections": true
}, },
"relative_line_numbers": false, "relative_line_numbers": false,
// When to populate a new search's query based on the text under the cursor.
// This setting can take the following three values:
//
// 1. Always populate the search query with the word under the cursor (default).
// "always"
// 2. Only populate the search query when there is text selected
// "selection"
// 3. Never populate the search query
// "never"
"seed_search_query_from_cursor": "always",
// Inlay hint related settings // Inlay hint related settings
"inlay_hints": { "inlay_hints": {
// Global switch to toggle hints on and off, switched off by default. // Global switch to toggle hints on and off, switched off by default.

View file

@ -2,7 +2,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::Setting; use settings::Setting;
#[derive(Deserialize)] #[derive(Clone, Deserialize)]
pub struct EditorSettings { pub struct EditorSettings {
pub cursor_blink: bool, pub cursor_blink: bool,
pub hover_popover_enabled: bool, pub hover_popover_enabled: bool,
@ -11,6 +11,15 @@ pub struct EditorSettings {
pub use_on_type_format: bool, pub use_on_type_format: bool,
pub scrollbar: Scrollbar, pub scrollbar: Scrollbar,
pub relative_line_numbers: bool, pub relative_line_numbers: bool,
pub seed_search_query_from_cursor: SeedQuerySetting,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SeedQuerySetting {
Always,
Selection,
Never,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@ -38,6 +47,7 @@ pub struct EditorSettingsContent {
pub use_on_type_format: Option<bool>, pub use_on_type_format: Option<bool>,
pub scrollbar: Option<ScrollbarContent>, pub scrollbar: Option<ScrollbarContent>,
pub relative_line_numbers: Option<bool>, pub relative_line_numbers: Option<bool>,
pub seed_search_query_from_cursor: Option<SeedQuerySetting>,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition, editor_settings::SeedQuerySetting, link_go_to_definition::hide_link_definition,
movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, EditorSettings, Event,
Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
}; };
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use collections::HashSet; use collections::HashSet;
@ -13,8 +13,8 @@ use gpui::{
ViewHandle, WeakViewHandle, ViewHandle, WeakViewHandle,
}; };
use language::{ use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
SelectionGoal, Point, SelectionGoal,
}; };
use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath}; use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
use rpc::proto::{self, update_view, PeerId}; use rpc::proto::{self, update_view, PeerId};
@ -937,25 +937,29 @@ impl SearchableItem for Editor {
} }
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String { fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
let display_map = self.snapshot(cx).display_snapshot; let setting = settings::get::<EditorSettings>(cx).seed_search_query_from_cursor;
let snapshot = &self.snapshot(cx).buffer_snapshot;
let selection = self.selections.newest::<usize>(cx); let selection = self.selections.newest::<usize>(cx);
if selection.start == selection.end {
let point = selection.start.to_display_point(&display_map); match setting {
let range = surrounding_word(&display_map, point); SeedQuerySetting::Never => String::new(),
let range = range.start.to_offset(&display_map, Bias::Left) SeedQuerySetting::Selection | SeedQuerySetting::Always if !selection.is_empty() => {
..range.end.to_offset(&display_map, Bias::Right); snapshot
let text: String = display_map.buffer_snapshot.text_for_range(range).collect();
if text.trim().is_empty() {
String::new()
} else {
text
}
} else {
display_map
.buffer_snapshot
.text_for_range(selection.start..selection.end) .text_for_range(selection.start..selection.end)
.collect() .collect()
} }
SeedQuerySetting::Selection => String::new(),
SeedQuerySetting::Always => {
let (range, kind) = snapshot.surrounding_word(selection.start);
if kind == Some(CharKind::Word) {
let text: String = snapshot.text_for_range(range).collect();
if !text.trim().is_empty() {
return text;
}
}
String::new()
}
}
} }
fn activate_match( fn activate_match(

View file

@ -11,6 +11,15 @@ pub struct EditorSettings {
pub use_on_type_format: bool, pub use_on_type_format: bool,
pub scrollbar: Scrollbar, pub scrollbar: Scrollbar,
pub relative_line_numbers: bool, pub relative_line_numbers: bool,
pub seed_search_query_from_cursor: SeedQuerySetting,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SeedQuerySetting {
Always,
Selection,
Never,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@ -38,6 +47,7 @@ pub struct EditorSettingsContent {
pub use_on_type_format: Option<bool>, pub use_on_type_format: Option<bool>,
pub scrollbar: Option<ScrollbarContent>, pub scrollbar: Option<ScrollbarContent>,
pub relative_line_numbers: Option<bool>, pub relative_line_numbers: Option<bool>,
pub seed_search_query_from_selection: Option<SeedQuerySetting>,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]

View file

@ -1,7 +1,8 @@
use crate::{ use crate::{
display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition, editor_settings::SeedQuerySetting, link_go_to_definition::hide_link_definition,
movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor,
Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, EditorSettings, Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot,
NavigationData, ToPoint as _,
}; };
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use collections::HashSet; use collections::HashSet;
@ -12,11 +13,12 @@ use gpui::{
VisualContext, WeakView, VisualContext, WeakView,
}; };
use language::{ use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
SelectionGoal, Point, SelectionGoal,
}; };
use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath}; use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
use rpc::proto::{self, update_view, PeerId}; use rpc::proto::{self, update_view, PeerId};
use settings::Settings;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
borrow::Cow, borrow::Cow,
@ -950,25 +952,29 @@ impl SearchableItem for Editor {
} }
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String { fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
let display_map = self.snapshot(cx).display_snapshot; let setting = EditorSettings::get_global(cx).seed_search_query_from_cursor;
let snapshot = &self.snapshot(cx).buffer_snapshot;
let selection = self.selections.newest::<usize>(cx); let selection = self.selections.newest::<usize>(cx);
if selection.start == selection.end {
let point = selection.start.to_display_point(&display_map); match setting {
let range = surrounding_word(&display_map, point); SeedQuerySetting::Never => String::new(),
let range = range.start.to_offset(&display_map, Bias::Left) SeedQuerySetting::Selection | SeedQuerySetting::Always if !selection.is_empty() => {
..range.end.to_offset(&display_map, Bias::Right); snapshot
let text: String = display_map.buffer_snapshot.text_for_range(range).collect();
if text.trim().is_empty() {
String::new()
} else {
text
}
} else {
display_map
.buffer_snapshot
.text_for_range(selection.start..selection.end) .text_for_range(selection.start..selection.end)
.collect() .collect()
} }
SeedQuerySetting::Selection => String::new(),
SeedQuerySetting::Always => {
let (range, kind) = snapshot.surrounding_word(selection.start);
if kind == Some(CharKind::Word) {
let text: String = snapshot.text_for_range(range).collect();
if !text.trim().is_empty() {
return text;
}
}
String::new()
}
}
} }
fn activate_match( fn activate_match(

View file

@ -136,7 +136,7 @@ impl ToJson for RectF {
} }
#[derive(Refineable, Debug)] #[derive(Refineable, Debug)]
#[refineable(debug)] #[refineable(Debug)]
pub struct Point<T: Clone + Default + Debug> { pub struct Point<T: Clone + Default + Debug> {
pub x: T, pub x: T,
pub y: T, pub y: T,
@ -161,7 +161,7 @@ impl<T: Clone + Default + Debug> Into<taffy::geometry::Point<T>> for Point<T> {
} }
#[derive(Refineable, Clone, Debug)] #[derive(Refineable, Clone, Debug)]
#[refineable(debug)] #[refineable(Debug)]
pub struct Size<T: Clone + Default + Debug> { pub struct Size<T: Clone + Default + Debug> {
pub width: T, pub width: T,
pub height: T, pub height: T,
@ -227,7 +227,7 @@ impl Size<Length> {
} }
#[derive(Clone, Default, Refineable, Debug)] #[derive(Clone, Default, Refineable, Debug)]
#[refineable(debug)] #[refineable(Debug)]
pub struct Edges<T: Clone + Default + Debug> { pub struct Edges<T: Clone + Default + Debug> {
pub top: T, pub top: T,
pub right: T, pub right: T,

View file

@ -9,7 +9,7 @@ use std::{
}; };
#[derive(Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash)]
#[refineable(debug)] #[refineable(Debug)]
#[repr(C)] #[repr(C)]
pub struct Point<T: Default + Clone + Debug> { pub struct Point<T: Default + Clone + Debug> {
pub x: T, pub x: T,
@ -140,7 +140,7 @@ impl<T: Clone + Default + Debug> Clone for Point<T> {
} }
#[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash, Serialize, Deserialize)] #[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash, Serialize, Deserialize)]
#[refineable(debug)] #[refineable(Debug)]
#[repr(C)] #[repr(C)]
pub struct Size<T: Clone + Default + Debug> { pub struct Size<T: Clone + Default + Debug> {
pub width: T, pub width: T,
@ -295,7 +295,7 @@ impl Size<Length> {
} }
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)] #[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
#[refineable(debug)] #[refineable(Debug)]
#[repr(C)] #[repr(C)]
pub struct Bounds<T: Clone + Default + Debug> { pub struct Bounds<T: Clone + Default + Debug> {
pub origin: Point<T>, pub origin: Point<T>,
@ -459,7 +459,7 @@ impl Bounds<Pixels> {
impl<T: Clone + Debug + Copy + Default> Copy for Bounds<T> {} impl<T: Clone + Debug + Copy + Default> Copy for Bounds<T> {}
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)] #[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
#[refineable(debug)] #[refineable(Debug)]
#[repr(C)] #[repr(C)]
pub struct Edges<T: Clone + Default + Debug> { pub struct Edges<T: Clone + Default + Debug> {
pub top: T, pub top: T,
@ -592,7 +592,7 @@ impl Edges<Pixels> {
} }
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)] #[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
#[refineable(debug)] #[refineable(Debug)]
#[repr(C)] #[repr(C)]
pub struct Corners<T: Clone + Default + Debug> { pub struct Corners<T: Clone + Default + Debug> {
pub top_left: T, pub top_left: T,

View file

@ -14,7 +14,7 @@ pub use taffy::style::{
pub type StyleCascade = Cascade<Style>; pub type StyleCascade = Cascade<Style>;
#[derive(Clone, Refineable, Debug)] #[derive(Clone, Refineable, Debug)]
#[refineable(debug)] #[refineable(Debug)]
pub struct Style { pub struct Style {
/// What layout strategy should be used? /// What layout strategy should be used?
pub display: Display, pub display: Display,
@ -129,7 +129,7 @@ pub struct BoxShadow {
} }
#[derive(Refineable, Clone, Debug)] #[derive(Refineable, Clone, Debug)]
#[refineable(debug)] #[refineable(Debug)]
pub struct TextStyle { pub struct TextStyle {
pub color: Hsla, pub color: Hsla,
pub font_family: SharedString, pub font_family: SharedString,
@ -353,7 +353,7 @@ impl Default for Style {
} }
#[derive(Refineable, Copy, Clone, Default, Debug, PartialEq, Eq)] #[derive(Refineable, Copy, Clone, Default, Debug, PartialEq, Eq)]
#[refineable(debug)] #[refineable(Debug)]
pub struct UnderlineStyle { pub struct UnderlineStyle {
pub thickness: Pixels, pub thickness: Pixels,
pub color: Option<Hsla>, pub color: Option<Hsla>,

View file

@ -19,8 +19,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
let refineable_attr = attrs.iter().find(|attr| attr.path.is_ident("refineable")); let refineable_attr = attrs.iter().find(|attr| attr.path.is_ident("refineable"));
let mut impl_debug_on_refinement = false; let mut impl_debug_on_refinement = false;
let mut derive_serialize_on_refinement = false; let mut refinement_traits_to_derive = vec![];
let mut derive_deserialize_on_refinement = false;
if let Some(refineable_attr) = refineable_attr { if let Some(refineable_attr) = refineable_attr {
if let Ok(syn::Meta::List(meta_list)) = refineable_attr.parse_meta() { if let Ok(syn::Meta::List(meta_list)) = refineable_attr.parse_meta() {
@ -29,16 +28,10 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
continue; continue;
}; };
if path.is_ident("debug") { if path.is_ident("Debug") {
impl_debug_on_refinement = true; impl_debug_on_refinement = true;
} } else {
refinement_traits_to_derive.push(path);
if path.is_ident("serialize") {
derive_serialize_on_refinement = true;
}
if path.is_ident("deserialize") {
derive_deserialize_on_refinement = true;
} }
} }
} }
@ -259,22 +252,14 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
quote! {} quote! {}
}; };
let derive_serialize = if derive_serialize_on_refinement { let mut derive_stream = quote! {};
quote! { #[derive(serde::Serialize)]} for trait_to_derive in refinement_traits_to_derive {
} else { derive_stream.extend(quote! { #[derive(#trait_to_derive)] })
quote! {} }
};
let derive_deserialize = if derive_deserialize_on_refinement {
quote! { #[derive(serde::Deserialize)]}
} else {
quote! {}
};
let gen = quote! { let gen = quote! {
#[derive(Clone)] #[derive(Clone)]
#derive_serialize #derive_stream
#derive_deserialize
pub struct #refinement_ident #impl_generics { pub struct #refinement_ident #impl_generics {
#( #field_visibilities #field_names: #wrapped_types ),* #( #field_visibilities #field_names: #wrapped_types ),*
} }

View file

@ -1,9 +1,7 @@
use std::sync::Arc; use crate::{PlayerColors, SyntaxTheme};
use gpui::Hsla; use gpui::Hsla;
use refineable::Refineable; use refineable::Refineable;
use std::sync::Arc;
use crate::{PlayerColors, SyntaxTheme};
#[derive(Clone)] #[derive(Clone)]
pub struct SystemColors { pub struct SystemColors {
@ -14,7 +12,7 @@ pub struct SystemColors {
} }
#[derive(Refineable, Clone, Debug)] #[derive(Refineable, Clone, Debug)]
#[refineable(debug)] #[refineable(Debug)]
pub struct StatusColors { pub struct StatusColors {
pub conflict: Hsla, pub conflict: Hsla,
pub created: Hsla, pub created: Hsla,
@ -30,7 +28,7 @@ pub struct StatusColors {
} }
#[derive(Refineable, Clone, Debug)] #[derive(Refineable, Clone, Debug)]
#[refineable(debug)] #[refineable(Debug)]
pub struct GitStatusColors { pub struct GitStatusColors {
pub conflict: Hsla, pub conflict: Hsla,
pub created: Hsla, pub created: Hsla,
@ -41,7 +39,7 @@ pub struct GitStatusColors {
} }
#[derive(Refineable, Clone, Debug)] #[derive(Refineable, Clone, Debug)]
#[refineable(debug, deserialize)] #[refineable(Debug, serde::Deserialize)]
pub struct ThemeColors { pub struct ThemeColors {
pub border: Hsla, pub border: Hsla,
/// Border color. Used for deemphasized borders, like a visual divider between two sections /// Border color. Used for deemphasized borders, like a visual divider between two sections

View file

@ -1,8 +1,7 @@
use crate::{Appearance, ThemeColors, ThemeColorsRefinement};
use refineable::Refineable; use refineable::Refineable;
use serde::Deserialize; use serde::Deserialize;
use crate::{Appearance, ThemeColors, ThemeColorsRefinement};
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct UserThemeFamily { pub struct UserThemeFamily {
pub name: String, pub name: String,
@ -18,7 +17,7 @@ pub struct UserTheme {
} }
#[derive(Refineable, Clone)] #[derive(Refineable, Clone)]
#[refineable(deserialize)] #[refineable(Deserialize)]
pub struct UserThemeStyles { pub struct UserThemeStyles {
#[refineable] #[refineable]
pub colors: ThemeColors, pub colors: ThemeColors,