summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJustin Chen <ctj12461@163.com>2023-08-13 23:12:59 +0800
committerGitHub <noreply@github.com>2023-08-13 11:12:59 -0400
commit7ab12658f911bebfa12f836b2a6449e08611e437 (patch)
treec5b00ec5342ff424b05d0f3c1dd283115c73bceb /src
parent82e231ac004e1f25f0a447bc601e5e3a10dca4d2 (diff)
feat: Add case sensitivity support for searching, selecting and filtering files (#393)
* feat: Add struct and enum definitons for choosing case sensitivity of search * feat: Implement `FromStr` for CaseSensitivity * feat: Add a command to change the configuration of case sensitivity * feat: Add case sensitivity support for search operations * feat: Add case sensitivity support for selection operations * docs: Add explanations for the new feature * feat: Add case sensitivity support for searching with fzf * docs: Add explanations for the new feature * docs: Update documents * feat: Refactor and add case sensitivity support for the filter operation * refactor: Extract codes related to constructing the context for searching * refactor: Extract the common component of searching, selecting and filtering files * refactor: Change the module path and name * feat: Use separate options for case sensitivity configurations * feat: Add support for changing case sensitivity configurations at runtime * docs: Add explanations for the new command
Diffstat (limited to 'src')
-rw-r--r--src/commands/case_sensitivity.rs26
-rw-r--r--src/commands/filter.rs14
-rw-r--r--src/commands/mod.rs1
-rw-r--r--src/commands/search.rs86
-rw-r--r--src/commands/search_fzf.rs26
-rw-r--r--src/commands/search_glob.rs56
-rw-r--r--src/commands/search_string.rs70
-rw-r--r--src/commands/select.rs13
-rw-r--r--src/commands/subdir_fzf.rs22
-rw-r--r--src/config/general/app.rs11
-rw-r--r--src/config/general/app_raw.rs6
-rw-r--r--src/config/general/mod.rs1
-rw-r--r--src/config/general/search_raw.rs60
-rw-r--r--src/config/option/display_option.rs44
-rw-r--r--src/config/option/mod.rs2
-rw-r--r--src/config/option/search_option.rs44
-rw-r--r--src/context/app_context.rs13
-rw-r--r--src/context/matcher.rs108
-rw-r--r--src/context/mod.rs2
-rw-r--r--src/key_command/command.rs8
-rw-r--r--src/key_command/constants.rs1
-rw-r--r--src/key_command/impl_appcommand.rs1
-rw-r--r--src/key_command/impl_appexecute.rs4
-rw-r--r--src/key_command/impl_comment.rs1
-rw-r--r--src/key_command/impl_from_str.rs35
-rw-r--r--src/ui/widgets/tui_footer.rs4
-rw-r--r--src/util/mod.rs1
-rw-r--r--src/util/search.rs7
28 files changed, 496 insertions, 171 deletions
diff --git a/src/commands/case_sensitivity.rs b/src/commands/case_sensitivity.rs
new file mode 100644
index 0000000..c604b74
--- /dev/null
+++ b/src/commands/case_sensitivity.rs
@@ -0,0 +1,26 @@
+use crate::config::option::CaseSensitivity;
+use crate::context::AppContext;
+use crate::error::JoshutoResult;
+
+#[derive(Clone, Copy, Debug)]
+pub enum SetType {
+ String,
+ Glob,
+ Fzf,
+}
+
+pub fn set_case_sensitivity(
+ context: &mut AppContext,
+ case_sensitivity: CaseSensitivity,
+ set_type: SetType,
+) -> JoshutoResult {
+ let options = context.config_mut().search_options_mut();
+
+ match set_type {
+ SetType::String => options.string_case_sensitivity = case_sensitivity,
+ SetType::Glob => options.glob_case_sensitivity = case_sensitivity,
+ SetType::Fzf => options.fzf_case_sensitivity = case_sensitivity,
+ }
+
+ Ok(())
+}
diff --git a/src/commands/filter.rs b/src/commands/filter.rs
index 2ea818d..fbf1d30 100644
--- a/src/commands/filter.rs
+++ b/src/commands/filter.rs
@@ -1,15 +1,23 @@
-use crate::context::AppContext;
+use crate::context::{AppContext, MatchContext};
use crate::error::JoshutoResult;
use super::reload;
-pub fn filter(context: &mut AppContext, arg: &str) -> JoshutoResult {
+pub fn filter(context: &mut AppContext, pattern: &str) -> JoshutoResult {
+ let case_sensitivity = context
+ .config_ref()
+ .search_options_ref()
+ .string_case_sensitivity;
+
+ let filter_context = MatchContext::new_string(pattern, case_sensitivity);
+
let curr_tab = context.tab_context_mut().curr_tab_mut();
let path = curr_tab.cwd().to_path_buf();
+
curr_tab
.option_mut()
.dirlist_options_mut(&path)
- .set_filter_string(arg);
+ .set_filter_context(filter_context);
if let Some(list) = curr_tab.curr_list_mut() {
list.depreciate();
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index 0f821bd..4753380 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -1,5 +1,6 @@
pub mod bookmark;
pub mod bulk_rename;
+pub mod case_sensitivity;
pub mod change_directory;
pub mod command_line;
pub mod cursor_move;
diff --git a/src/commands/search.rs b/src/commands/search.rs
index 71beae1..41c2a8d 100644
--- a/src/commands/search.rs
+++ b/src/commands/search.rs
@@ -1,41 +1,87 @@
-use crate::context::AppContext;
+use crate::context::{AppContext, MatchContext};
use crate::error::JoshutoResult;
-use crate::util::search::SearchPattern;
+use crate::tab::JoshutoTab;
use super::cursor_move;
-use super::search_glob;
-use super::search_string;
pub fn search_next(context: &mut AppContext) -> JoshutoResult {
if let Some(search_context) = context.get_search_context() {
- let index = match search_context {
- SearchPattern::Glob(s) => {
- search_glob::search_glob_fwd(context.tab_context_ref().curr_tab_ref(), s)
- }
- SearchPattern::String(s) => {
- search_string::search_string_fwd(context.tab_context_ref().curr_tab_ref(), s)
- }
+ if search_context.is_none() {
+ return Ok(());
+ }
+
+ let curr_tab = &context.tab_context_ref().curr_tab_ref();
+ let index = curr_tab.curr_list_ref().and_then(|c| c.get_index());
+
+ let offset = match index {
+ Some(index) => index + 1,
+ None => return Ok(()),
};
- if let Some(index) = index {
+
+ if let Some(index) = search_next_impl(curr_tab, search_context, offset) {
cursor_move::cursor_move(context, index);
}
}
+
Ok(())
}
+pub(super) fn search_next_impl(
+ curr_tab: &JoshutoTab,
+ match_context: &MatchContext,
+ offset: usize,
+) -> Option<usize> {
+ let curr_list = curr_tab.curr_list_ref()?;
+ let contents_len = curr_list.contents.len();
+
+ for i in 0..contents_len {
+ let file_name = curr_list.contents[(offset + i) % contents_len].file_name();
+
+ if match_context.is_match(file_name) {
+ return Some((offset + i) % contents_len);
+ }
+ }
+
+ None
+}
+
pub fn search_prev(context: &mut AppContext) -> JoshutoResult {
if let Some(search_context) = context.get_search_context() {
- let index = match search_context {
- SearchPattern::Glob(s) => {
- search_glob::search_glob_rev(context.tab_context_ref().curr_tab_ref(), s)
- }
- SearchPattern::String(s) => {
- search_string::search_string_rev(context.tab_context_ref().curr_tab_ref(), s)
- }
+ if search_context.is_none() {
+ return Ok(());
+ }
+
+ let curr_tab = &context.tab_context_ref().curr_tab_ref();
+ let index = curr_tab.curr_list_ref().and_then(|c| c.get_index());
+
+ let offset = match index {
+ Some(index) => index,
+ None => return Ok(()),
};
- if let Some(index) = index {
+
+ if let Some(index) = search_prev_impl(curr_tab, search_context, offset) {
cursor_move::cursor_move(context, index);
}
}
+
Ok(())
}
+
+fn search_prev_impl(
+ curr_tab: &JoshutoTab,
+ match_context: &MatchContext,
+ offset: usize,
+) -> Option<usize> {
+ let curr_list = curr_tab.curr_list_ref()?;
+ let contents_len = curr_list.contents.len();
+
+ for i in (0..contents_len).rev() {
+ let file_name = curr_list.contents[(offset + i) % contents_len].file_name();
+
+ if match_context.is_match(file_name) {
+ return Some((offset + i) % contents_len);
+ }
+ }
+
+ None
+}
diff --git a/src/commands/search_fzf.rs b/src/commands/search_fzf.rs
index f845c60..9d86b7b 100644
--- a/src/commands/search_fzf.rs
+++ b/src/commands/search_fzf.rs
@@ -3,6 +3,7 @@ use std::io::Write;
use std::process::{Command, Stdio};
use crate::commands::cursor_move;
+use crate::config::option::CaseSensitivity;
use crate::context::AppContext;
use crate::error::{JoshutoError, JoshutoErrorKind, JoshutoResult};
use crate::ui::AppBackend;
@@ -31,11 +32,26 @@ pub fn search_fzf(context: &mut AppContext, backend: &mut AppBackend) -> Joshuto
backend.terminal_drop();
- let mut fzf = match Command::new("fzf")
- .stdin(Stdio::piped())
- .stdout(Stdio::piped())
- .spawn()
- {
+ let mut cmd = Command::new("fzf");
+ cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
+
+ let case_sensitivity = context
+ .config_ref()
+ .search_options_ref()
+ .fzf_case_sensitivity;
+
+ match case_sensitivity {
+ CaseSensitivity::Insensitive => {
+ cmd.arg("-i");
+ }
+ CaseSensitivity::Sensitive => {
+ cmd.arg("+i");
+ }
+ // fzf uses smart-case match by default
+ CaseSensitivity::Smart => {}
+ }
+
+ let mut fzf = match cmd.spawn() {
Ok(child) => child,
Err(e) => {
backend.terminal_restore()?;
diff --git a/src/commands/search_glob.rs b/src/commands/search_glob.rs
index 30962c1..c833b8e 100644
--- a/src/commands/search_glob.rs
+++ b/src/commands/search_glob.rs
@@ -1,49 +1,29 @@
-use globset::{GlobBuilder, GlobMatcher};
-
-use crate::context::AppContext;
+use crate::context::{AppContext, MatchContext};
use crate::error::JoshutoResult;
-use crate::tab::JoshutoTab;
-use crate::util::search::SearchPattern;
use super::cursor_move;
+use super::search;
-pub fn search_glob_fwd(curr_tab: &JoshutoTab, glob: &GlobMatcher) -> Option<usize> {
- let curr_list = curr_tab.curr_list_ref()?;
+pub fn search_glob(context: &mut AppContext, pattern: &str) -> JoshutoResult {
+ let case_sensitivity = context
+ .config_ref()
+ .search_options_ref()
+ .glob_case_sensitivity;
- let offset = curr_list.get_index()? + 1;
- let contents_len = curr_list.len();
- for i in 0..contents_len {
- let file_name = curr_list.contents[(offset + i) % contents_len].file_name();
- if glob.is_match(file_name) {
- return Some((offset + i) % contents_len);
- }
- }
- None
-}
-pub fn search_glob_rev(curr_tab: &JoshutoTab, glob: &GlobMatcher) -> Option<usize> {
- let curr_list = curr_tab.curr_list_ref()?;
+ let search_context = MatchContext::new_glob(pattern, case_sensitivity)?;
- let offset = curr_list.get_index()?;
- let contents_len = curr_list.len();
- for i in (0..contents_len).rev() {
- let file_name = curr_list.contents[(offset + i) % contents_len].file_name();
- if glob.is_match(file_name) {
- return Some((offset + i) % contents_len);
- }
- }
- None
-}
+ let curr_tab = &context.tab_context_ref().curr_tab_ref();
+ let index = curr_tab.curr_list_ref().and_then(|c| c.get_index());
-pub fn search_glob(context: &mut AppContext, pattern: &str) -> JoshutoResult {
- let glob = GlobBuilder::new(pattern)
- .case_insensitive(true)
- .build()?
- .compile_matcher();
+ let offset = match index {
+ Some(index) => index + 1,
+ None => return Ok(()),
+ };
- let index = search_glob_fwd(context.tab_context_ref().curr_tab_ref(), &glob);
- if let Some(index) = index {
- cursor_move::cursor_move(context, index);
+ if let Some(new_index) = search::search_next_impl(curr_tab, &search_context, offset) {
+ cursor_move::cursor_move(context, new_index);
}
- context.set_search_context(SearchPattern::Glob(glob));
+
+ context.set_search_context(search_context);
Ok(())
}
diff --git a/src/commands/search_string.rs b/src/commands/search_string.rs
index 53d54ea..084fc89 100644
--- a/src/commands/search_string.rs
+++ b/src/commands/search_string.rs
@@ -1,65 +1,29 @@
-use crate::context::AppContext;
-use crate::tab::JoshutoTab;
-use crate::util::search::SearchPattern;
+use crate::context::{AppContext, MatchContext};
use super::cursor_move;
+use super::search;
-pub fn search_string_fwd(curr_tab: &JoshutoTab, pattern: &str) -> Option<usize> {
- let curr_list = curr_tab.curr_list_ref()?;
+pub fn search_string(context: &mut AppContext, pattern: &str, incremental: bool) {
+ let case_sensitivity = context
+ .config_ref()
+ .search_options_ref()
+ .string_case_sensitivity;
- let offset = curr_list.get_index()? + 1;
- let contents_len = curr_list.contents.len();
- for i in 0..contents_len {
- let file_name_lower = curr_list.contents[(offset + i) % contents_len]
- .file_name()
- .to_lowercase();
- if file_name_lower.contains(pattern) {
- return Some((offset + i) % contents_len);
- }
- }
- None
-}
+ let search_context = MatchContext::new_string(pattern, case_sensitivity);
-pub fn search_string_start(curr_tab: &JoshutoTab, pattern: &str) -> Option<usize> {
- let curr_list = curr_tab.curr_list_ref()?;
+ let curr_tab = context.tab_context_ref().curr_tab_ref();
- let contents_len = curr_list.contents.len();
- for i in 0..contents_len {
- let file_name_lower = curr_list.contents[i].file_name().to_lowercase();
- if file_name_lower.contains(pattern) {
- return Some(i);
+ if incremental {
+ if let Some(new_index) = search::search_next_impl(curr_tab, &search_context, 0) {
+ cursor_move::cursor_move(context, new_index);
}
- }
- None
-}
+ } else if let Some(index) = curr_tab.curr_list_ref().and_then(|c| c.get_index()) {
+ let offset = index + 1;
-pub fn search_string_rev(curr_tab: &JoshutoTab, pattern: &str) -> Option<usize> {
- let curr_list = curr_tab.curr_list_ref()?;
-
- let offset = curr_list.get_index()?;
- let contents_len = curr_list.contents.len();
- for i in (0..contents_len).rev() {
- let file_name_lower = curr_list.contents[(offset + i) % contents_len]
- .file_name()
- .to_lowercase();
- if file_name_lower.contains(pattern) {
- return Some((offset + i) % contents_len);
+ if let Some(new_index) = search::search_next_impl(curr_tab, &search_context, offset) {
+ cursor_move::cursor_move(context, new_index);
}
}
- None
-}
-
-pub fn search_string(context: &mut AppContext, pattern: &str, incremental: bool) {
- let pattern = pattern.to_lowercase();
- let curr_tab = context.tab_context_ref().curr_tab_ref();
- let index = if incremental {
- search_string_start(curr_tab, pattern.as_str())
- } else {
- search_string_fwd(curr_tab, pattern.as_str())
- };
- if let Some(index) = index {
- cursor_move::cursor_move(context, index);
- }
- context.set_search_context(SearchPattern::String(pattern));
+ context.set_search_context(search_context);
}
diff --git a/src/commands/select.rs b/src/commands/select.rs
index 3bfa227..0ab3023 100644
--- a/src/commands/select.rs
+++ b/src/commands/select.rs
@@ -1,7 +1,5 @@
-use globset::Glob;
-
use crate::config::option::SelectOption;
-use crate::context::AppContext;
+use crate::context::{AppContext, MatchContext};
use crate::error::JoshutoResult;
use super::cursor_move;
@@ -54,13 +52,18 @@ fn select_with_pattern(
pattern: &str,
options: &SelectOption,
) -> JoshutoResult {
- let glob = Glob::new(pattern)?.compile_matcher();
+ let case_sensitivity = context
+ .config_ref()
+ .search_options_ref()
+ .glob_case_sensitivity;
+
+ let select_context = MatchContext::new_glob(pattern, case_sensitivity)?;
if let Some(curr_list) = context.tab_context_mut().curr_tab_mut().curr_list_mut() {
let mut found = 0;
curr_list
.iter_mut()
- .filter(|e| glob.is_match(e.file_name()))
+ .filter(|e| select_context.is_match(e.file_name()))
.for_each(|e| {
found += 1;
if options.reverse {
diff --git a/src/commands/subdir_fzf.rs b/src/commands/subdir_fzf.rs
index fee12e9..6bae11a 100644
--- a/src/commands/subdir_fzf.rs
+++ b/src/commands/subdir_fzf.rs
@@ -1,6 +1,7 @@
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
+use crate::config::option::CaseSensitivity;
use crate::context::AppContext;
use crate::error::JoshutoResult;
use crate::ui::AppBackend;
@@ -10,7 +11,26 @@ use super::change_directory::change_directory;
pub fn subdir_fzf(context: &mut AppContext, backend: &mut AppBackend) -> JoshutoResult {
backend.terminal_drop();
- let fzf = Command::new("fzf").stdout(Stdio::piped()).spawn()?;
+ let mut cmd = Command::new("fzf");
+ cmd.stdout(Stdio::piped());
+
+ let case_sensitivity = context
+ .config_ref()
+ .search_options_ref()
+ .fzf_case_sensitivity;
+
+ match case_sensitivity {
+ CaseSensitivity::Insensitive => {
+ cmd.arg("-i");
+ }
+ CaseSensitivity::Sensitive => {
+ cmd.arg("+i");
+ }
+ // fzf uses smart-case match by default
+ CaseSensitivity::Smart => {}
+ }
+
+ let fzf = cmd.spawn()?;
let fzf_output = fzf.wait_with_output();
diff --git a/src/config/general/app.rs b/src/config/general/app.rs
index 9ee900e..65306b7 100644
--- a/src/config/general/app.rs
+++ b/src/config/general/app.rs
@@ -3,7 +3,7 @@ use std::collections::HashMap;
use super::app_raw::AppConfigRaw;
use super::DEFAULT_CONFIG_FILE_PATH;
-use crate::config::option::{DisplayOption, PreviewOption, TabOption};
+use crate::config::option::{DisplayOption, PreviewOption, SearchOption, TabOption};
use crate::error::JoshutoResult;
#[derive(Debug, Clone)]
@@ -15,6 +15,7 @@ pub struct AppConfig {
pub cmd_aliases: HashMap<String, String>,
pub _display_options: DisplayOption,
pub _preview_options: PreviewOption,
+ pub _search_options: SearchOption,
pub _tab_options: TabOption,
}
@@ -38,6 +39,14 @@ impl AppConfig {
&mut self._preview_options
}
+ pub fn search_options_ref(&self) -> &SearchOption {
+ &self._search_options
+ }
+
+ pub fn search_options_mut(&mut self) -> &mut SearchOption {
+ &mut self._search_options
+ }
+
pub fn tab_options_ref(&self) -> &TabOption {
&self._tab_options
}
diff --git a/src/config/general/app_raw.rs b/src/config/general/app_raw.rs
index bebcb9c..58ab74a 100644
--- a/src/config/general/app_raw.rs
+++ b/src/config/general/app_raw.rs
@@ -3,11 +3,12 @@ use std::convert::From;
use serde_derive::Deserialize;
-use crate::config::option::{DisplayOption, PreviewOption, TabOption};
+use crate::config::option::{DisplayOption, PreviewOption, SearchOption, TabOption};
use crate::config::{parse_config_or_default, AppConfig, TomlConfigFile};
use super::display_raw::DisplayOptionRaw;
use super::preview_raw::PreviewOptionRaw;
+use super::search_raw::SearchOptionRaw;
use super::tab_raw::TabOptionRaw;
const fn default_true() -> bool {
@@ -35,6 +36,8 @@ pub struct AppConfigRaw {
pub display_options: DisplayOptionRaw,
#[serde(default, rename = "preview")]
pub preview_options: PreviewOptionRaw,
+ #[serde(default, rename = "search")]
+ pub search_options: SearchOptionRaw,
#[serde(default, rename = "tab")]
pub tab_options: TabOptionRaw,
}
@@ -49,6 +52,7 @@ impl From<AppConfigRaw> for AppConfig {
cmd_aliases: raw.cmd_aliases,
_display_options: DisplayOption::from(raw.display_options),
_preview_options: PreviewOption::from(raw.preview_options),
+ _search_options: SearchOption::from(raw.search_options),
_tab_options: TabOption::from(raw.tab_options),
}
}
diff --git a/src/config/general/mod.rs b/src/config/general/mod.rs
index 7b85b7f..e5b2538 100644
--- a/src/config/general/mod.rs
+++ b/src/config/general/mod.rs
@@ -3,6 +3,7 @@ pub mod app;
mod app_raw;
mod display_raw;
mod preview_raw;
+mod search_raw;
mod sort_raw;
mod tab_raw;
diff --git a/src/config/general/search_raw.rs b/src/config/general/search_raw.rs
new file mode 100644
index 0000000..cd9d397
--- /dev/null
+++ b/src/config/general/search_raw.rs
@@ -0,0 +1,60 @@
+use std::convert::From;
+use std::str::FromStr;
+
+use serde_derive::Deserialize;
+
+use crate::config::option::{CaseSensitivity, SearchOption};
+
+fn default_string_case_sensitivity() -> String {
+ "insensitive".to_string()
+}
+
+fn default_glob_case_sensitivity() -> String {
+ "sensitive".to_string()
+}
+
+fn default_fzf_case_sensitivity() -> String {
+ "insensitive".to_string()
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct SearchOptionRaw {
+ #[serde(default = "default_string_case_sensitivity")]
+ pub string_case_sensitivity: String,
+
+ #[serde(default = "default_glob_case_sensitivity")]
+ pub glob_case_sensitivity: String,
+
+ #[serde(default = "default_fzf_case_sensitivity")]
+ pub fzf_case_sensitivity: String,
+}
+
+impl std::default::Default for SearchOptionRaw {
+ fn default() -> Self {
+ SearchOptionRaw {
+ string_case_sensitivity: default_string_case_sensitivity(),
+ glob_case_sensitivity: default_glob_case_sensitivity(),
+ fzf_case_sensitivity: default_fzf_case_sensitivity(),
+ }
+ }
+}
+
+impl From<SearchOptionRaw> for SearchOption {
+ fn from(raw: SearchOptionRaw) -> Self {
+ let string_case_sensitivity =
+ CaseSensitivity::from_str(raw.string_case_sensitivity.as_str())
+ .unwrap_or(CaseSensitivity::Insensitive);
+
+ let glob_case_sensitivity = CaseSensitivity::from_str(raw.glob_case_sensitivity.as_str())
+ .unwrap_or(CaseSensitivity::Sensitive);
+
+ let fzf_case_sensitivity = CaseSensitivity::from_str(raw.fzf_case_sensitivity.as_str())
+ .unwrap_or(CaseSensitivity::Insensitive);
+
+ Self {
+ string_case_sensitivity,
+ glob_case_sensitivity,
+ fzf_case_sensitivity,
+ }
+ }
+}
diff --git a/src/config/option/display_option.rs b/src/config/option/display_option.rs
index 24fa89c..393c5f0 100644
--- a/src/config/option/display_option.rs
+++ b/src/config/option/display_option.rs
@@ -4,6 +4,7 @@ use ratatui::layout::Constraint;
use crate::config::option::LineMode;
use crate::config::option::SortOption;
+use crate::context::MatchContext;
#[derive(Clone, Copy, Debug)]
pub enum DisplayMode {
@@ -36,7 +37,7 @@ pub struct DisplayOption {
/// Display options valid pre JoshutoDirList in a JoshutoTab
#[derive(Clone, Debug, Default)]
pub struct DirListDisplayOptions {
- filter_string: String,
+ filter_context: MatchContext,
depth: u8,
}
@@ -67,12 +68,12 @@ impl LineNumberStyle {
}
impl DirListDisplayOptions {
- pub fn set_filter_string(&mut self, pattern: &str) {
- self.filter_string = pattern.to_owned();
+ pub fn set_filter_context(&mut self, filter_context: MatchContext) {
+ self.filter_context = filter_context;
}
- pub fn filter_string_ref(&self) -> &str {
- &self.filter_string
+ pub fn filter_context_ref(&self) -> &MatchContext {
+ &self.filter_context
}
pub fn set_depth(&mut self, depth: u8) {
@@ -192,18 +193,6 @@ impl std::default::Default for DisplayOption {
}
}
-fn has_str(entry: &walkdir::DirEntry, pat: &str) -> bool {
- entry
- .file_name()
- .to_str()
- .map(|s| {
- s.to_ascii_lowercase()
- .as_str()
- .contains(pat.to_ascii_lowercase().as_str())
- })
- .unwrap_or(false)
-}
-
fn is_hidden(entry: &walkdir::DirEntry) -> bool {
entry
.file_name()
@@ -217,13 +206,18 @@ fn filter(
opt: &DisplayOption,
dirlist_opts: &DirListDisplayOptions,
) -> bool {
- if opt.show_hidden() && dirlist_opts.filter_string_ref().is_empty() {
- true
- } else if dirlist_opts.filter_string_ref().is_empty() {
- !is_hidden(entry)
- } else if opt.show_hidden() || !is_hidden(entry) {
- has_str(entry, dirlist_opts.filter_string_ref())
- } else {
- false
+ if !opt.show_hidden() && is_hidden(entry) {
+ return false;
}
+
+ let file_name = match entry.file_name().to_str() {
+ Some(s) => s,
+ None => return false,
+ };
+
+ if !dirlist_opts.filter_context_ref().is_match(file_name) {
+ return false;
+ }
+
+ true
}
diff --git a/src/config/option/mod.rs b/src/config/option/mod.rs
index fe53e57..c666223 100644
--- a/src/config/option/mod.rs
+++ b/src/config/option/mod.rs
@@ -2,6 +2,7 @@ pub mod display_option;
pub mod linemodes;
pub mod new_tab_option;
pub mod preview_option;
+pub mod search_option;
pub mod select_option;
pub mod sort_option;
pub mod sort_type;
@@ -11,6 +12,7 @@ pub use self::display_option::*;
pub use self::linemodes::*;
pub use self::new_tab_option::*;
pub use self::preview_option::*;
+pub use self::search_option::*;
pub use self::select_option::*;
pub use self::sort_option::*;
pub use self::sort_type::*;
diff --git a/src/config/option/search_option.rs b/src/config/option/search_option.rs
new file mode 100644
index 0000000..0fa889b
--- /dev/null
+++ b/src/config/option/search_option.rs
@@ -0,0 +1,44 @@
+use std::str::FromStr;
+
+use crate::error::{JoshutoError, JoshutoErrorKind, JoshutoResult};
+
+/// Search and selection options globally valid for Joshuto (for all tabs)
+#[derive(Clone, Debug)]
+pub struct SearchOption {
+ pub string_case_sensitivity: CaseSensitivity,
+ pub glob_case_sensitivity: CaseSensitivity,
+ pub fzf_case_sensitivity: CaseSensitivity,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum CaseSensitivity {
+ Insensitive,
+ Sensitive,
+ Smart,
+}
+
+impl std::default::Default for SearchOption {
+ fn default() -> Self {
+ Self {
+ string_case_sensitivity: CaseSensitivity::Insensitive,
+ glob_case_sensitivity: CaseSensitivity::Sensitive,
+ fzf_case_sensitivity: CaseSensitivity::Insensitive,
+ }
+ }
+}
+
+impl FromStr for CaseSensitivity {
+ type Err = JoshutoError;
+
+ fn from_str(s: &str) -> JoshutoResult<Self> {
+ match s {
+ "insensitive" => Ok(Self::Insensitive),
+ "sensitive" => Ok(Self::Sensitive),
+ "smart" => Ok(Self::Smart),
+ otherwise => Err(JoshutoError::new(
+ JoshutoErrorKind::InvalidParameters,
+ format!("Case sensitivity '{otherwise}' unknown"),
+ )),
+ }
+ }
+}
diff --git a/src/context/app_context.rs b/src/context/app_context.rs
index b283783..ce4793c 100644
--- a/src/context/app_context.rs
+++ b/src/context/app_context.rs
@@ -6,13 +6,12 @@ use std::thread;
use crate::commands::quit::QuitAction;
use crate::config;
use crate::context::{
- CommandLineContext, LocalStateContext, MessageQueue, PreviewContext, TabContext, UiContext,
- WorkerContext,
+ CommandLineContext, LocalStateContext, MatchContext, MessageQueue, PreviewContext, TabContext,
+ UiContext, WorkerContext,
};
use crate::event::{AppEvent, Events};
use crate::ui::views;
use crate::ui::PreviewArea;
-use crate::util::search::SearchPattern;
use crate::Args;
use notify::{RecursiveMode, Watcher};
use std::path;
@@ -30,7 +29,7 @@ pub struct AppContext {
// context related to local file state
local_state: Option<LocalStateContext>,
// context related to searching
- search_context: Option<SearchPattern>,
+ search_context: Option<MatchContext>,
// message queue for displaying messages
message_queue: MessageQueue,
// context related to io workers
@@ -246,11 +245,11 @@ impl AppContext {
self.local_state.take()
}
- pub fn get_search_context(&self) -> Option<&SearchPattern> {
+ pub fn get_search_context(&self) -> Option<&MatchContext> {
self.search_context.as_ref()
}
- pub fn set_search_context(&mut self, pattern: SearchPattern) {
- self.search_context = Some(pattern);
+ pub fn set_search_context(&mut self, context: MatchContext) {
+ self.search_context = Some(context);
}
pub fn preview_context_ref(&self) -> &PreviewContext {
diff --git a/src/context/matcher.rs b/src/context/matcher.rs
new file mode 100644
index 0000000..31bda66
--- /dev/null
+++ b/