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
},
"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_hints": {
// 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 settings::Setting;
#[derive(Deserialize)]
#[derive(Clone, Deserialize)]
pub struct EditorSettings {
pub cursor_blink: bool,
pub hover_popover_enabled: bool,
@ -11,6 +11,15 @@ pub struct EditorSettings {
pub use_on_type_format: bool,
pub scrollbar: Scrollbar,
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)]
@ -38,6 +47,7 @@ pub struct EditorSettingsContent {
pub use_on_type_format: Option<bool>,
pub scrollbar: Option<ScrollbarContent>,
pub relative_line_numbers: Option<bool>,
pub seed_search_query_from_cursor: Option<SeedQuerySetting>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]

View file

@ -1,7 +1,7 @@
use crate::{
display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition,
movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor,
Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
editor_settings::SeedQuerySetting, link_go_to_definition::hide_link_definition,
persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, EditorSettings, Event,
ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
};
use anyhow::{Context, Result};
use collections::HashSet;
@ -13,8 +13,8 @@ use gpui::{
ViewHandle, WeakViewHandle,
};
use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
SelectionGoal,
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
Point, SelectionGoal,
};
use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
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 {
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);
if selection.start == selection.end {
let point = selection.start.to_display_point(&display_map);
let range = surrounding_word(&display_map, point);
let range = range.start.to_offset(&display_map, Bias::Left)
..range.end.to_offset(&display_map, Bias::Right);
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
match setting {
SeedQuerySetting::Never => String::new(),
SeedQuerySetting::Selection | SeedQuerySetting::Always if !selection.is_empty() => {
snapshot
.text_for_range(selection.start..selection.end)
.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(

View file

@ -11,6 +11,15 @@ pub struct EditorSettings {
pub use_on_type_format: bool,
pub scrollbar: Scrollbar,
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)]
@ -38,6 +47,7 @@ pub struct EditorSettingsContent {
pub use_on_type_format: Option<bool>,
pub scrollbar: Option<ScrollbarContent>,
pub relative_line_numbers: Option<bool>,
pub seed_search_query_from_selection: Option<SeedQuerySetting>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]

View file

@ -1,7 +1,8 @@
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,
Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
EditorSettings, Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot,
NavigationData, ToPoint as _,
};
use anyhow::{anyhow, Context, Result};
use collections::HashSet;
@ -12,11 +13,12 @@ use gpui::{
VisualContext, WeakView,
};
use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
SelectionGoal,
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
Point, SelectionGoal,
};
use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
use rpc::proto::{self, update_view, PeerId};
use settings::Settings;
use smallvec::SmallVec;
use std::{
borrow::Cow,
@ -950,25 +952,29 @@ impl SearchableItem for Editor {
}
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);
if selection.start == selection.end {
let point = selection.start.to_display_point(&display_map);
let range = surrounding_word(&display_map, point);
let range = range.start.to_offset(&display_map, Bias::Left)
..range.end.to_offset(&display_map, Bias::Right);
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
match setting {
SeedQuerySetting::Never => String::new(),
SeedQuerySetting::Selection | SeedQuerySetting::Always if !selection.is_empty() => {
snapshot
.text_for_range(selection.start..selection.end)
.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(

View file

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

View file

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

View file

@ -14,7 +14,7 @@ pub use taffy::style::{
pub type StyleCascade = Cascade<Style>;
#[derive(Clone, Refineable, Debug)]
#[refineable(debug)]
#[refineable(Debug)]
pub struct Style {
/// What layout strategy should be used?
pub display: Display,
@ -129,7 +129,7 @@ pub struct BoxShadow {
}
#[derive(Refineable, Clone, Debug)]
#[refineable(debug)]
#[refineable(Debug)]
pub struct TextStyle {
pub color: Hsla,
pub font_family: SharedString,
@ -353,7 +353,7 @@ impl Default for Style {
}
#[derive(Refineable, Copy, Clone, Default, Debug, PartialEq, Eq)]
#[refineable(debug)]
#[refineable(Debug)]
pub struct UnderlineStyle {
pub thickness: Pixels,
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 mut impl_debug_on_refinement = false;
let mut derive_serialize_on_refinement = false;
let mut derive_deserialize_on_refinement = false;
let mut refinement_traits_to_derive = vec![];
if let Some(refineable_attr) = refineable_attr {
if let Ok(syn::Meta::List(meta_list)) = refineable_attr.parse_meta() {
@ -29,16 +28,10 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
continue;
};
if path.is_ident("debug") {
if path.is_ident("Debug") {
impl_debug_on_refinement = true;
}
if path.is_ident("serialize") {
derive_serialize_on_refinement = true;
}
if path.is_ident("deserialize") {
derive_deserialize_on_refinement = true;
} else {
refinement_traits_to_derive.push(path);
}
}
}
@ -259,22 +252,14 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
quote! {}
};
let derive_serialize = if derive_serialize_on_refinement {
quote! { #[derive(serde::Serialize)]}
} else {
quote! {}
};
let derive_deserialize = if derive_deserialize_on_refinement {
quote! { #[derive(serde::Deserialize)]}
} else {
quote! {}
};
let mut derive_stream = quote! {};
for trait_to_derive in refinement_traits_to_derive {
derive_stream.extend(quote! { #[derive(#trait_to_derive)] })
}
let gen = quote! {
#[derive(Clone)]
#derive_serialize
#derive_deserialize
#derive_stream
pub struct #refinement_ident #impl_generics {
#( #field_visibilities #field_names: #wrapped_types ),*
}

View file

@ -1,9 +1,7 @@
use std::sync::Arc;
use crate::{PlayerColors, SyntaxTheme};
use gpui::Hsla;
use refineable::Refineable;
use crate::{PlayerColors, SyntaxTheme};
use std::sync::Arc;
#[derive(Clone)]
pub struct SystemColors {
@ -14,7 +12,7 @@ pub struct SystemColors {
}
#[derive(Refineable, Clone, Debug)]
#[refineable(debug)]
#[refineable(Debug)]
pub struct StatusColors {
pub conflict: Hsla,
pub created: Hsla,
@ -30,7 +28,7 @@ pub struct StatusColors {
}
#[derive(Refineable, Clone, Debug)]
#[refineable(debug)]
#[refineable(Debug)]
pub struct GitStatusColors {
pub conflict: Hsla,
pub created: Hsla,
@ -41,7 +39,7 @@ pub struct GitStatusColors {
}
#[derive(Refineable, Clone, Debug)]
#[refineable(debug, deserialize)]
#[refineable(Debug, serde::Deserialize)]
pub struct ThemeColors {
pub border: Hsla,
/// 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 serde::Deserialize;
use crate::{Appearance, ThemeColors, ThemeColorsRefinement};
#[derive(Deserialize)]
pub struct UserThemeFamily {
pub name: String,
@ -18,7 +17,7 @@ pub struct UserTheme {
}
#[derive(Refineable, Clone)]
#[refineable(deserialize)]
#[refineable(Deserialize)]
pub struct UserThemeStyles {
#[refineable]
pub colors: ThemeColors,