refactor: reimplement pattern items as an enum

Update the way pattern items are implemented so that, instead of saving
these in an array with the character representation, the search option
and its value, it now is a `PatternItem` enum, and the enum implements
several methods:

- `character` - Returns the character representation for the pattern
item item, without the backslash.
- `search_option` - Returns the search option and its desired value.

The `TryFrom<&str>` trait is also implemented for `PatternItem` so that
we can simply build a `PatternItem` from a string like `"\\c"`.
This commit is contained in:
dinocosta 2025-08-19 19:05:30 +01:00
parent f7473c80ae
commit 835f613fd6
2 changed files with 72 additions and 71 deletions

View file

@ -1,9 +1,10 @@
mod registrar; mod registrar;
use crate::{ use crate::{
FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOption, FocusSearch, NextHistoryQuery, PatternItem, PreviousHistoryQuery, ReplaceAll, ReplaceNext,
SearchOptions, SearchSource, SelectAllMatches, SelectNextMatch, SelectPreviousMatch, SearchOption, SearchOptions, SearchSource, SelectAllMatches, SelectNextMatch,
ToggleCaseSensitive, ToggleRegex, ToggleReplace, ToggleSelection, ToggleWholeWord, SelectPreviousMatch, ToggleCaseSensitive, ToggleRegex, ToggleReplace, ToggleSelection,
ToggleWholeWord,
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;
@ -47,17 +48,6 @@ use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults};
const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50; const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50;
/// Array of supported pattern items and their corresponding search options and
/// value.
/// When any of the patterns is present in the search query, the corresponding
/// search option, and value, is applied.
// TODO: Should this be updated to an HashMap so we can easily determine the
// search option for a given pattern?
static PATTERN_ITEMS: [(&str, &SearchOptions, bool); 2] = [
("c", &SearchOptions::CASE_SENSITIVE, false),
("C", &SearchOptions::CASE_SENSITIVE, true),
];
/// Opens the buffer search interface with the specified configuration. /// Opens the buffer search interface with the specified configuration.
#[derive(PartialEq, Clone, Deserialize, JsonSchema, Action)] #[derive(PartialEq, Clone, Deserialize, JsonSchema, Action)]
#[action(namespace = buffer_search)] #[action(namespace = buffer_search)]
@ -123,11 +113,11 @@ 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 and its state derived from the pattern items in /// List of search options that were enabled or disabled by the pattern
/// the search query. Keeping track of these values allows us to determine /// items in the search query.
/// which search options are enabled/disabled by the pattern items, as well /// By toggling each search option in reverse order, one can obtain the
/// as reverting any changes made to the search options. /// original search options before pattern items were applied.
pattern_item_options: Vec<(SearchOptions, bool)>, pattern_item_options: Vec<SearchOptions>,
configured_options: SearchOptions, configured_options: SearchOptions,
query_error: Option<String>, query_error: Option<String>,
dismissed: bool, dismissed: bool,
@ -663,9 +653,9 @@ impl BufferSearchBar {
let pattern_items_regex = Regex::new(&format!( let pattern_items_regex = Regex::new(&format!(
r"(?<!\\)(\\[{}])", r"(?<!\\)(\\[{}])",
PATTERN_ITEMS PatternItem::all_variants()
.iter() .iter()
.map(|(pattern, _, _)| *pattern) .map(|item| item.character())
.collect::<String>() .collect::<String>()
)) ))
.unwrap(); .unwrap();
@ -1493,64 +1483,32 @@ impl BufferSearchBar {
// Determines which pattern items are present in the search query and // Determines which pattern items are present in the search query and
// updates the search options accordingly, only if the regex search option // updates the search options accordingly, only if the regex search option
// is enabled. // is enabled.
//
// TODO: How to deal with the case where cancelling pattern items are
// available? For example, `bananas\c\C` or `bananas\C\c`. We'd probably
// need to actually capture the matches with a Regex, so we can iterate over
// them in order and get the correct order, for example, finding `\c\C\c` in
// a string would eventually equate to `[(CASE_SENSITIVE, false),
// (CASE_INSENSITIVE, true), (CASE_SENSITIVE, false)]`
//
// TODO: Reimplement this so that we can simply use another `SearchOptions`
// for the pattern items, so that excluding that second one from the
// `self.search_options` leads to the search options before pattern items
// options were applied.
fn apply_pattern_items(&mut self, cx: &mut Context<Self>) { fn apply_pattern_items(&mut self, cx: &mut Context<Self>) {
if self.search_options.contains(SearchOptions::REGEX) { if self.search_options.contains(SearchOptions::REGEX) {
// Recalculate the search options before the pattern items were // Determine what the search options were before the pattern items
// applied, so we can reapply them and determine which ones are // were applied, so we can reapply them and determine which ones
// actually affecting the search options. // actually have an effect on the search options, which are the ones
// TODO: This can probably be improved by simply verifying if any new // we need to keep track of.
// pattern items are available, seeing as it should be incremental. let mut search_options = self.pattern_item_options.iter().rev().fold(
let mut search_options = self.search_options; self.search_options,
self.pattern_item_options |mut search_options, search_option| {
.iter()
.rev()
.for_each(|(search_option, _value)| {
// TODO: Do we actually care about the `value`? If the
// search option was added to `pattern_item_options`, we
// already know that it affected the `search_options` so
// simply toggleing it should have the desired effect of
// reverting the changes.
search_options.toggle(*search_option); search_options.toggle(*search_option);
}); search_options
},
);
let mut pattern_item_options = Vec::new();
let query = self.raw_query(cx); let query = self.raw_query(cx);
let mut pattern_item_options = Vec::new();
// TODO: Maybe avoid so many unwrap/expect calls here.
self.pattern_items_regex self.pattern_items_regex
.captures_iter(&query) .captures_iter(&query)
.map(|capture| capture.unwrap()) .filter_map(|capture| capture.ok()?.get(1))
.map(|capture| { .filter_map(|capture| PatternItem::try_from(capture.as_str()).ok())
let pattern_item = capture.get(1).unwrap().as_str(); .map(|pattern_item| pattern_item.search_option())
.for_each(|(search_option, value)| {
PATTERN_ITEMS if search_options.contains(search_option) != value {
.iter() search_options.toggle(search_option);
.find(|(pattern, _, _)| pattern_item.ends_with(*pattern)) pattern_item_options.push(search_option);
.expect("should only capture valid pattern items")
})
.for_each(|(_, search_option, value)| {
match (search_options.contains(**search_option), value) {
(true, false) => {
search_options.toggle(**search_option);
pattern_item_options.push((**search_option, false));
}
(false, true) => {
search_options.toggle(**search_option);
pattern_item_options.push((**search_option, true));
}
(_, _) => {}
} }
}); });

View file

@ -204,3 +204,46 @@ 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]
}
}