refactor: introduce pattern_items module
This commit is contained in:
parent
835f613fd6
commit
7f9e589491
3 changed files with 268 additions and 92 deletions
|
@ -1,10 +1,10 @@
|
||||||
mod registrar;
|
mod registrar;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
FocusSearch, NextHistoryQuery, PatternItem, PreviousHistoryQuery, ReplaceAll, ReplaceNext,
|
FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOption,
|
||||||
SearchOption, SearchOptions, SearchSource, SelectAllMatches, SelectNextMatch,
|
SearchOptions, SearchSource, SelectAllMatches, SelectNextMatch, SelectPreviousMatch,
|
||||||
SelectPreviousMatch, ToggleCaseSensitive, ToggleRegex, ToggleReplace, ToggleSelection,
|
ToggleCaseSensitive, ToggleRegex, ToggleReplace, ToggleSelection, ToggleWholeWord,
|
||||||
ToggleWholeWord,
|
pattern_items::PatternItems,
|
||||||
search_bar::{ActionButtonState, input_base_styles, render_action_button, render_text_input},
|
search_bar::{ActionButtonState, input_base_styles, render_action_button, render_text_input},
|
||||||
};
|
};
|
||||||
use any_vec::AnyVec;
|
use any_vec::AnyVec;
|
||||||
|
@ -14,7 +14,6 @@ use editor::{
|
||||||
DisplayPoint, Editor, EditorSettings,
|
DisplayPoint, Editor, EditorSettings,
|
||||||
actions::{Backtab, Tab},
|
actions::{Backtab, Tab},
|
||||||
};
|
};
|
||||||
use fancy_regex::Regex;
|
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, App, ClickEvent, Context, Entity, EventEmitter, Focusable, InteractiveElement as _,
|
Action, App, ClickEvent, Context, Entity, EventEmitter, Focusable, InteractiveElement as _,
|
||||||
|
@ -113,11 +112,10 @@ pub struct BufferSearchBar {
|
||||||
pending_search: Option<Task<()>>,
|
pending_search: Option<Task<()>>,
|
||||||
search_options: SearchOptions,
|
search_options: SearchOptions,
|
||||||
default_options: SearchOptions,
|
default_options: SearchOptions,
|
||||||
/// List of search options that were enabled or disabled by the pattern
|
/// Pattern items that have been applied from the query to the search
|
||||||
/// items in the search query.
|
/// options. Tracking these allows us to revert the search options to their
|
||||||
/// By toggling each search option in reverse order, one can obtain the
|
/// original state as pattern items are removed from the query.
|
||||||
/// original search options before pattern items were applied.
|
pattern_items: PatternItems,
|
||||||
pattern_item_options: Vec<SearchOptions>,
|
|
||||||
configured_options: SearchOptions,
|
configured_options: SearchOptions,
|
||||||
query_error: Option<String>,
|
query_error: Option<String>,
|
||||||
dismissed: bool,
|
dismissed: bool,
|
||||||
|
@ -129,7 +127,6 @@ pub struct BufferSearchBar {
|
||||||
editor_scroll_handle: ScrollHandle,
|
editor_scroll_handle: ScrollHandle,
|
||||||
editor_needed_width: Pixels,
|
editor_needed_width: Pixels,
|
||||||
regex_language: Option<Arc<Language>>,
|
regex_language: Option<Arc<Language>>,
|
||||||
pattern_items_regex: Regex,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferSearchBar {
|
impl BufferSearchBar {
|
||||||
|
@ -651,15 +648,6 @@ impl BufferSearchBar {
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
let pattern_items_regex = Regex::new(&format!(
|
|
||||||
r"(?<!\\)(\\[{}])",
|
|
||||||
PatternItem::all_variants()
|
|
||||||
.iter()
|
|
||||||
.map(|item| item.character())
|
|
||||||
.collect::<String>()
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
query_editor,
|
query_editor,
|
||||||
query_editor_focused: false,
|
query_editor_focused: false,
|
||||||
|
@ -672,7 +660,7 @@ impl BufferSearchBar {
|
||||||
default_options: search_options,
|
default_options: search_options,
|
||||||
configured_options: search_options,
|
configured_options: search_options,
|
||||||
search_options,
|
search_options,
|
||||||
pattern_item_options: Vec::new(),
|
pattern_items: Default::default(),
|
||||||
pending_search: None,
|
pending_search: None,
|
||||||
query_error: None,
|
query_error: None,
|
||||||
dismissed: true,
|
dismissed: true,
|
||||||
|
@ -688,7 +676,6 @@ impl BufferSearchBar {
|
||||||
editor_scroll_handle: ScrollHandle::new(),
|
editor_scroll_handle: ScrollHandle::new(),
|
||||||
editor_needed_width: px(0.),
|
editor_needed_width: px(0.),
|
||||||
regex_language: None,
|
regex_language: None,
|
||||||
pattern_items_regex,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -836,9 +823,7 @@ impl BufferSearchBar {
|
||||||
|
|
||||||
/// Returns the sanitized query string with pattern items removed.
|
/// Returns the sanitized query string with pattern items removed.
|
||||||
pub fn query(&self, cx: &App) -> String {
|
pub fn query(&self, cx: &App) -> String {
|
||||||
self.pattern_items_regex
|
PatternItems::clean_query(&self.raw_query(cx))
|
||||||
.replace_all(&self.raw_query(cx), "")
|
|
||||||
.into_owned()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn replacement(&self, cx: &mut App) -> String {
|
pub fn replacement(&self, cx: &mut App) -> String {
|
||||||
|
@ -1489,31 +1474,10 @@ impl BufferSearchBar {
|
||||||
// were applied, so we can reapply them and determine which ones
|
// were applied, so we can reapply them and determine which ones
|
||||||
// actually have an effect on the search options, which are the ones
|
// actually have an effect on the search options, which are the ones
|
||||||
// we need to keep track of.
|
// we need to keep track of.
|
||||||
let mut search_options = self.pattern_item_options.iter().rev().fold(
|
|
||||||
self.search_options,
|
|
||||||
|mut search_options, search_option| {
|
|
||||||
search_options.toggle(*search_option);
|
|
||||||
search_options
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let query = self.raw_query(cx);
|
let query = self.raw_query(cx);
|
||||||
let mut pattern_item_options = Vec::new();
|
let search_options = self.pattern_items.revert(self.search_options);
|
||||||
|
self.pattern_items = PatternItems::from_search_options(search_options, &query);
|
||||||
self.pattern_items_regex
|
self.set_search_options(self.pattern_items.apply(search_options), cx);
|
||||||
.captures_iter(&query)
|
|
||||||
.filter_map(|capture| capture.ok()?.get(1))
|
|
||||||
.filter_map(|capture| PatternItem::try_from(capture.as_str()).ok())
|
|
||||||
.map(|pattern_item| pattern_item.search_option())
|
|
||||||
.for_each(|(search_option, value)| {
|
|
||||||
if search_options.contains(search_option) != value {
|
|
||||||
search_options.toggle(search_option);
|
|
||||||
pattern_item_options.push(search_option);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.pattern_item_options = pattern_item_options;
|
|
||||||
self.set_search_options(search_options, cx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
254
crates/search/src/pattern_items.rs
Normal file
254
crates/search/src/pattern_items.rs
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
use crate::SearchOptions;
|
||||||
|
use anyhow;
|
||||||
|
use fancy_regex::Regex;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
/// A `PatternItem` is a character, preceded by a backslash, that can be used to
|
||||||
|
/// modify the search options.
|
||||||
|
/// For example, using `\c` in a search query will make the search
|
||||||
|
/// case-insensitive, while `\C` will make it case-sensitive.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
enum PatternItem {
|
||||||
|
CaseSensitiveFalse,
|
||||||
|
CaseSensitiveTrue,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Regex for matching pattern items in a search query.
|
||||||
|
pub static PATTERN_ITEMS_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||||
|
Regex::new(&format!(
|
||||||
|
r"(?<!\\)(\\[{}])",
|
||||||
|
PatternItem::all_variants()
|
||||||
|
.iter()
|
||||||
|
.map(|item| item.character())
|
||||||
|
.collect::<String>()
|
||||||
|
))
|
||||||
|
.expect("Failed to compile pattern items regex")
|
||||||
|
});
|
||||||
|
|
||||||
|
impl TryFrom<&str> for PatternItem {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(str: &str) -> Result<Self, Self::Error> {
|
||||||
|
match str {
|
||||||
|
"\\c" => Ok(Self::CaseSensitiveFalse),
|
||||||
|
"\\C" => Ok(Self::CaseSensitiveTrue),
|
||||||
|
_ => anyhow::bail!("Invalid pattern item: {}", str),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PatternItem {
|
||||||
|
/// Representation of the pattern item as a single character, without the
|
||||||
|
/// backslash.
|
||||||
|
pub fn character(&self) -> char {
|
||||||
|
match self {
|
||||||
|
Self::CaseSensitiveFalse => 'c',
|
||||||
|
Self::CaseSensitiveTrue => 'C',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn search_option(&self) -> (SearchOptions, bool) {
|
||||||
|
match self {
|
||||||
|
Self::CaseSensitiveFalse => (SearchOptions::CASE_SENSITIVE, false),
|
||||||
|
Self::CaseSensitiveTrue => (SearchOptions::CASE_SENSITIVE, true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all_variants() -> &'static [Self] {
|
||||||
|
&[Self::CaseSensitiveFalse, Self::CaseSensitiveTrue]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct PatternItems {
|
||||||
|
items: Vec<PatternItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PatternItems {
|
||||||
|
/// Builds the list of pattern items that, from the provided search options
|
||||||
|
/// and query, do actually affect the search options.
|
||||||
|
/// For example, if search options is `SearchOptions::CASE_SENSITIVE`, and
|
||||||
|
/// the query only contains `\C`, then the pattern item will not have an
|
||||||
|
/// effect on the sarch options.
|
||||||
|
pub fn from_search_options(search_options: SearchOptions, query: &str) -> Self {
|
||||||
|
let mut search_options = search_options;
|
||||||
|
let mut pattern_items: Vec<PatternItem> = Vec::new();
|
||||||
|
|
||||||
|
Self::extract_from_query(query)
|
||||||
|
.iter()
|
||||||
|
.for_each(|pattern_item| {
|
||||||
|
let (search_option, value) = pattern_item.search_option();
|
||||||
|
|
||||||
|
if search_options.contains(search_option) != value {
|
||||||
|
search_options.toggle(search_option);
|
||||||
|
pattern_items.push(pattern_item.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
items: pattern_items,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replaces all pattern items in the provided string with an empty string.
|
||||||
|
pub fn clean_query(str: &str) -> String {
|
||||||
|
PATTERN_ITEMS_REGEX.replace_all(str, "").into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates what the provided search options looked liked before the
|
||||||
|
/// pattern items were applied.
|
||||||
|
pub fn revert(&self, search_options: SearchOptions) -> SearchOptions {
|
||||||
|
let mut result = search_options;
|
||||||
|
|
||||||
|
self.items
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.map(PatternItem::search_option)
|
||||||
|
.for_each(|(search_option, value)| result.set(search_option, !value));
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the search options after applying the pattern items.
|
||||||
|
pub fn apply(&self, search_options: SearchOptions) -> SearchOptions {
|
||||||
|
let mut search_options = search_options;
|
||||||
|
|
||||||
|
self.items
|
||||||
|
.iter()
|
||||||
|
.map(PatternItem::search_option)
|
||||||
|
.for_each(|(search_option, value)| search_options.set(search_option, value));
|
||||||
|
|
||||||
|
search_options
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts all pattern items from the provided string.
|
||||||
|
fn extract_from_query(str: &str) -> Vec<PatternItem> {
|
||||||
|
PATTERN_ITEMS_REGEX
|
||||||
|
.captures_iter(str)
|
||||||
|
.filter_map(|capture| capture.ok()?.get(1))
|
||||||
|
.filter_map(|capture| PatternItem::try_from(capture.as_str()).ok())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clean_query() {
|
||||||
|
let query = "Main\\c\\C";
|
||||||
|
let cleaned_query = PatternItems::clean_query(query);
|
||||||
|
assert_eq!(cleaned_query, "Main");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_apply() {
|
||||||
|
let search_options = SearchOptions::CASE_SENSITIVE;
|
||||||
|
let query = "Main\\C";
|
||||||
|
let pattern_items = PatternItems::from_search_options(search_options, query);
|
||||||
|
assert_eq!(pattern_items.apply(search_options), search_options);
|
||||||
|
|
||||||
|
let search_options = SearchOptions::CASE_SENSITIVE;
|
||||||
|
let query = "Main\\c";
|
||||||
|
let pattern_items = PatternItems::from_search_options(search_options, query);
|
||||||
|
assert_eq!(pattern_items.apply(search_options), SearchOptions::NONE);
|
||||||
|
|
||||||
|
let search_options = SearchOptions::CASE_SENSITIVE;
|
||||||
|
let query = "Main\\c\\C";
|
||||||
|
let pattern_items = PatternItems::from_search_options(search_options, query);
|
||||||
|
assert_eq!(pattern_items.apply(search_options), search_options);
|
||||||
|
|
||||||
|
let search_options = SearchOptions::NONE;
|
||||||
|
let query = "Main\\c\\C";
|
||||||
|
let pattern_items = PatternItems::from_search_options(search_options, query);
|
||||||
|
assert_eq!(
|
||||||
|
pattern_items.apply(search_options),
|
||||||
|
SearchOptions::CASE_SENSITIVE
|
||||||
|
);
|
||||||
|
|
||||||
|
let search_options = SearchOptions::CASE_SENSITIVE;
|
||||||
|
let query = "Main\\c\\C\\c";
|
||||||
|
let pattern_items = PatternItems::from_search_options(search_options, query);
|
||||||
|
assert_eq!(pattern_items.apply(search_options), SearchOptions::NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_revert() {
|
||||||
|
let search_options = SearchOptions::CASE_SENSITIVE;
|
||||||
|
let query = "Main\\c";
|
||||||
|
let pattern_items = PatternItems::from_search_options(search_options, query);
|
||||||
|
let updated_search_options = pattern_items.apply(search_options);
|
||||||
|
assert_eq!(updated_search_options, SearchOptions::NONE);
|
||||||
|
assert_eq!(pattern_items.revert(updated_search_options), search_options);
|
||||||
|
|
||||||
|
let search_options = SearchOptions::CASE_SENSITIVE;
|
||||||
|
let query = "Main\\c";
|
||||||
|
let pattern_items = PatternItems::from_search_options(search_options, query);
|
||||||
|
let updated_search_options = pattern_items.apply(search_options);
|
||||||
|
assert_eq!(updated_search_options, SearchOptions::NONE);
|
||||||
|
assert_eq!(pattern_items.revert(updated_search_options), search_options);
|
||||||
|
|
||||||
|
let search_options = SearchOptions::CASE_SENSITIVE;
|
||||||
|
let query = "Main\\c\\C";
|
||||||
|
let pattern_items = PatternItems::from_search_options(search_options, query);
|
||||||
|
let updated_search_options = pattern_items.apply(search_options);
|
||||||
|
assert_eq!(updated_search_options, search_options);
|
||||||
|
assert_eq!(pattern_items.revert(updated_search_options), search_options);
|
||||||
|
|
||||||
|
let search_options = SearchOptions::NONE;
|
||||||
|
let query = "Main\\c\\C";
|
||||||
|
let pattern_items = PatternItems::from_search_options(search_options, query);
|
||||||
|
let updated_search_options = pattern_items.apply(search_options);
|
||||||
|
assert_eq!(updated_search_options, SearchOptions::CASE_SENSITIVE);
|
||||||
|
assert_eq!(pattern_items.revert(updated_search_options), search_options);
|
||||||
|
|
||||||
|
let search_options = SearchOptions::CASE_SENSITIVE;
|
||||||
|
let query = "Main\\c\\C\\c";
|
||||||
|
let pattern_items = PatternItems::from_search_options(search_options, query);
|
||||||
|
let updated_search_options = pattern_items.apply(search_options);
|
||||||
|
assert_eq!(updated_search_options, SearchOptions::NONE);
|
||||||
|
assert_eq!(pattern_items.revert(updated_search_options), search_options);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_from_query() {
|
||||||
|
let query = "Main\\c\\C\\c";
|
||||||
|
let pattern_items = PatternItems::extract_from_query(query);
|
||||||
|
assert_eq!(
|
||||||
|
pattern_items,
|
||||||
|
vec![
|
||||||
|
PatternItem::CaseSensitiveFalse,
|
||||||
|
PatternItem::CaseSensitiveTrue,
|
||||||
|
PatternItem::CaseSensitiveFalse
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_search_options() {
|
||||||
|
let search_options = SearchOptions::CASE_SENSITIVE;
|
||||||
|
let query = "Main\\C";
|
||||||
|
let pattern_items = PatternItems::from_search_options(search_options, query);
|
||||||
|
|
||||||
|
assert_eq!(pattern_items.items, vec![]);
|
||||||
|
|
||||||
|
let search_options = SearchOptions::CASE_SENSITIVE;
|
||||||
|
let query = "Main\\c";
|
||||||
|
let pattern_items = PatternItems::from_search_options(search_options, query);
|
||||||
|
|
||||||
|
assert_eq!(pattern_items.items, vec![PatternItem::CaseSensitiveFalse]);
|
||||||
|
|
||||||
|
let search_options = SearchOptions::CASE_SENSITIVE;
|
||||||
|
let query = "Main\\C\\c\\C";
|
||||||
|
let pattern_items = PatternItems::from_search_options(search_options, query);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
pattern_items.items,
|
||||||
|
vec![
|
||||||
|
PatternItem::CaseSensitiveFalse,
|
||||||
|
PatternItem::CaseSensitiveTrue
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ pub use search_status_button::SEARCH_ICON;
|
||||||
use crate::project_search::ProjectSearchBar;
|
use crate::project_search::ProjectSearchBar;
|
||||||
|
|
||||||
pub mod buffer_search;
|
pub mod buffer_search;
|
||||||
|
pub mod pattern_items;
|
||||||
pub mod project_search;
|
pub mod project_search;
|
||||||
pub(crate) mod search_bar;
|
pub(crate) mod search_bar;
|
||||||
pub mod search_status_button;
|
pub mod search_status_button;
|
||||||
|
@ -204,46 +205,3 @@ pub(crate) fn show_no_more_matches(window: &mut Window, cx: &mut App) {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `PatternItem` is a character, preceded by a backslash, that can be used to
|
|
||||||
/// modify the search options.
|
|
||||||
/// For example, using `\c` in a search query will make the search
|
|
||||||
/// case-insensitive, while `\C` will make it case-sensitive.
|
|
||||||
enum PatternItem {
|
|
||||||
CaseSensitiveFalse,
|
|
||||||
CaseSensitiveTrue,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&str> for PatternItem {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(str: &str) -> Result<Self, Self::Error> {
|
|
||||||
match str {
|
|
||||||
"\\c" => Ok(Self::CaseSensitiveFalse),
|
|
||||||
"\\C" => Ok(Self::CaseSensitiveTrue),
|
|
||||||
_ => anyhow::bail!("Invalid pattern item: {}", str),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PatternItem {
|
|
||||||
/// Representation of the pattern item as a single character, without the
|
|
||||||
/// backslash.
|
|
||||||
fn character(&self) -> char {
|
|
||||||
match self {
|
|
||||||
Self::CaseSensitiveFalse => 'c',
|
|
||||||
Self::CaseSensitiveTrue => 'C',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_option(&self) -> (SearchOptions, bool) {
|
|
||||||
match self {
|
|
||||||
Self::CaseSensitiveFalse => (SearchOptions::CASE_SENSITIVE, false),
|
|
||||||
Self::CaseSensitiveTrue => (SearchOptions::CASE_SENSITIVE, true),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn all_variants() -> &'static [Self] {
|
|
||||||
&[Self::CaseSensitiveFalse, Self::CaseSensitiveTrue]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue