summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustin Chen <ctj12461@163.com>2023-08-25 01:28:24 +0800
committerGitHub <noreply@github.com>2023-08-24 13:28:24 -0400
commit2c4138dd165adb6043cce6633741bab9595ad687 (patch)
treed028b0e1fa2842f532ed4f15ef70153495dccaea
parent88328d90876efaec407033b96b89da48063cf055 (diff)
feat: regex support (#411)
* feat: add the regex support for the matcher * feat: add functions for searching with regex * feat: add commands for searching with regex and change the case sensitivity * docs: add explanations for the new feature
-rw-r--r--Cargo.lock9
-rw-r--r--Cargo.toml1
-rw-r--r--config/joshuto.toml2
-rw-r--r--docs/configuration/joshuto.toml.md2
-rw-r--r--docs/configuration/keymap.toml.md5
-rw-r--r--src/commands/case_sensitivity.rs2
-rw-r--r--src/commands/mod.rs1
-rw-r--r--src/commands/search_regex.rs29
-rw-r--r--src/config/general/search_raw.rs12
-rw-r--r--src/config/option/search_option.rs2
-rw-r--r--src/context/matcher.rs37
-rw-r--r--src/error/error_kind.rs2
-rw-r--r--src/error/error_type.rs10
-rw-r--r--src/key_command/command.rs3
-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.rs1
-rw-r--r--src/key_command/impl_comment.rs1
-rw-r--r--src/key_command/impl_display.rs1
-rw-r--r--src/key_command/impl_from_str.rs11
20 files changed, 129 insertions, 4 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 961db46..5f7ad03 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -469,6 +469,7 @@ dependencies = [
"phf",
"rand",
"ratatui",
+ "regex",
"rustyline",
"serde",
"serde_derive",
@@ -847,9 +848,9 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.9.1"
+version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
+checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
dependencies = [
"aho-corasick",
"memchr",
@@ -859,9 +860,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.3.3"
+version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
+checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
dependencies = [
"aho-corasick",
"memchr",
diff --git a/Cargo.toml b/Cargo.toml
index b285561..f97d570 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -38,6 +38,7 @@ whoami = "^1"
xdg = "^2"
walkdir = "^2"
bitflags = { version = "^2", features = ["serde"] }
+regex = "1.9.3"
[dependencies.nix]
version = "^0"
diff --git a/config/joshuto.toml b/config/joshuto.toml
index 9c875d4..9219ca0 100644
--- a/config/joshuto.toml
+++ b/config/joshuto.toml
@@ -44,6 +44,8 @@ string_case_sensitivity = "insensitive"
# see above
glob_case_sensitivity = "sensitive"
# see above
+regex_case_sensitivity = "sensitive"
+# see above
fzf_case_sensitivity = "insensitive"
[tab]
diff --git a/docs/configuration/joshuto.toml.md b/docs/configuration/joshuto.toml.md
index 685ddde..31ba9f5 100644
--- a/docs/configuration/joshuto.toml.md
+++ b/docs/configuration/joshuto.toml.md
@@ -99,6 +99,8 @@ reverse = false
string_case_sensitivity = "insensitive"
# For glob matching
glob_case_sensitivity = "sensitive"
+# For regex matching
+regex_case_sensitivity = "sensitive"
# For matching with fzf
fzf_case_sensitivity = "insensitive"
diff --git a/docs/configuration/keymap.toml.md b/docs/configuration/keymap.toml.md
index b926c1a..68a020b 100644
--- a/docs/configuration/keymap.toml.md
+++ b/docs/configuration/keymap.toml.md
@@ -319,6 +319,10 @@ function joshuto() {
- `:search_glob *.png`
+### `search_regex`: search the current directory via regex (exact match)
+
+- `:search_regex .+\.(jpg|png|gif)`
+
### `search_next`: go to next search result in the current directory
### `search_prev`: go to previous search result in the current directory
@@ -359,6 +363,7 @@ When disabling, the current “visual mode selection” is turned into normal se
- Options
- `--type=string`: change configurations of operations using substring matching
- `--type=glob`: change configurations of operations using glob matching
+ - `--type=regex`: change configurations of operations using regex
- `--type=fzf`: change configurations of operations using fzf
- when no option is added, type is set to `string` by default
- Value
diff --git a/src/commands/case_sensitivity.rs b/src/commands/case_sensitivity.rs
index c604b74..658c7fe 100644
--- a/src/commands/case_sensitivity.rs
+++ b/src/commands/case_sensitivity.rs
@@ -6,6 +6,7 @@ use crate::error::JoshutoResult;
pub enum SetType {
String,
Glob,
+ Regex,
Fzf,
}
@@ -19,6 +20,7 @@ pub fn set_case_sensitivity(
match set_type {
SetType::String => options.string_case_sensitivity = case_sensitivity,
SetType::Glob => options.glob_case_sensitivity = case_sensitivity,
+ SetType::Regex => options.regex_case_sensitivity = case_sensitivity,
SetType::Fzf => options.fzf_case_sensitivity = case_sensitivity,
}
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index 4753380..dcf3946 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -22,6 +22,7 @@ pub mod rename_file;
pub mod search;
pub mod search_fzf;
pub mod search_glob;
+pub mod search_regex;
pub mod search_string;
pub mod select;
pub mod set_mode;
diff --git a/src/commands/search_regex.rs b/src/commands/search_regex.rs
new file mode 100644
index 0000000..90cc8f3
--- /dev/null
+++ b/src/commands/search_regex.rs
@@ -0,0 +1,29 @@
+use crate::context::{AppContext, MatchContext};
+use crate::error::JoshutoResult;
+
+use super::cursor_move;
+use super::search;
+
+pub fn search_regex(context: &mut AppContext, pattern: &str) -> JoshutoResult {
+ let case_sensitivity = context
+ .config_ref()
+ .search_options_ref()
+ .regex_case_sensitivity;
+
+ let search_context = MatchContext::new_regex(pattern, case_sensitivity)?;
+
+ 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(new_index) = search::search_next_impl(curr_tab, &search_context, offset) {
+ cursor_move::cursor_move(context, new_index);
+ }
+
+ context.set_search_context(search_context);
+ Ok(())
+}
diff --git a/src/config/general/search_raw.rs b/src/config/general/search_raw.rs
index cd9d397..672cd38 100644
--- a/src/config/general/search_raw.rs
+++ b/src/config/general/search_raw.rs
@@ -13,6 +13,10 @@ fn default_glob_case_sensitivity() -> String {
"sensitive".to_string()
}
+fn default_regex_case_sensitivity() -> String {
+ "sensitive".to_string()
+}
+
fn default_fzf_case_sensitivity() -> String {
"insensitive".to_string()
}
@@ -25,6 +29,9 @@ pub struct SearchOptionRaw {
#[serde(default = "default_glob_case_sensitivity")]
pub glob_case_sensitivity: String,
+ #[serde(default = "default_regex_case_sensitivity")]
+ pub regex_case_sensitivity: String,
+
#[serde(default = "default_fzf_case_sensitivity")]
pub fzf_case_sensitivity: String,
}
@@ -34,6 +41,7 @@ impl std::default::Default for SearchOptionRaw {
SearchOptionRaw {
string_case_sensitivity: default_string_case_sensitivity(),
glob_case_sensitivity: default_glob_case_sensitivity(),
+ regex_case_sensitivity: default_regex_case_sensitivity(),
fzf_case_sensitivity: default_fzf_case_sensitivity(),
}
}
@@ -48,12 +56,16 @@ impl From<SearchOptionRaw> for SearchOption {
let glob_case_sensitivity = CaseSensitivity::from_str(raw.glob_case_sensitivity.as_str())
.unwrap_or(CaseSensitivity::Sensitive);
+ let regex_case_sensitivity = CaseSensitivity::from_str(raw.regex_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,
+ regex_case_sensitivity,
fzf_case_sensitivity,
}
}
diff --git a/src/config/option/search_option.rs b/src/config/option/search_option.rs
index 0fa889b..a7cac37 100644
--- a/src/config/option/search_option.rs
+++ b/src/config/option/search_option.rs
@@ -7,6 +7,7 @@ use crate::error::{JoshutoError, JoshutoErrorKind, JoshutoResult};
pub struct SearchOption {
pub string_case_sensitivity: CaseSensitivity,
pub glob_case_sensitivity: CaseSensitivity,
+ pub regex_case_sensitivity: CaseSensitivity,
pub fzf_case_sensitivity: CaseSensitivity,
}
@@ -22,6 +23,7 @@ impl std::default::Default for SearchOption {
Self {
string_case_sensitivity: CaseSensitivity::Insensitive,
glob_case_sensitivity: CaseSensitivity::Sensitive,
+ regex_case_sensitivity: CaseSensitivity::Sensitive,
fzf_case_sensitivity: CaseSensitivity::Insensitive,
}
}
diff --git a/src/context/matcher.rs b/src/context/matcher.rs
index 31bda66..6c73161 100644
--- a/src/context/matcher.rs
+++ b/src/context/matcher.rs
@@ -1,6 +1,7 @@
use std::fmt::{Display, Formatter, Result as FmtResult};
use globset::{GlobBuilder, GlobMatcher};
+use regex::{Regex, RegexBuilder};
use crate::config::option::CaseSensitivity;
use crate::error::JoshutoResult;
@@ -8,6 +9,7 @@ use crate::error::JoshutoResult;
#[derive(Clone, Debug, Default)]
pub enum MatchContext {
Glob(GlobMatcher),
+ Regex(Regex),
String {
pattern: String,
actual_case_sensitivity: CaseSensitivity,
@@ -44,6 +46,32 @@ impl MatchContext {
Ok(Self::Glob(glob))
}
+ pub fn new_regex(pattern: &str, case_sensitivity: CaseSensitivity) -> JoshutoResult<Self> {
+ let pattern_lower = pattern.to_lowercase();
+
+ let (pattern, actual_case_sensitivity) = match case_sensitivity {
+ CaseSensitivity::Insensitive => (pattern_lower.as_str(), CaseSensitivity::Insensitive),
+ CaseSensitivity::Sensitive => (pattern, CaseSensitivity::Sensitive),
+ // Determine the actual case sensitivity by whether an uppercase letter occurs.
+ CaseSensitivity::Smart => {
+ if pattern_lower == pattern {
+ (pattern_lower.as_str(), CaseSensitivity::Insensitive)
+ } else {
+ (pattern, CaseSensitivity::Sensitive)
+ }
+ }
+ };
+
+ let re = RegexBuilder::new(pattern)
+ .case_insensitive(matches!(
+ actual_case_sensitivity,
+ CaseSensitivity::Insensitive
+ ))
+ .build()?;
+
+ Ok(Self::Regex(re))
+ }
+
pub fn new_string(pattern: &str, case_sensitivity: CaseSensitivity) -> Self {
let (pattern, actual_case_sensitivity) = match case_sensitivity {
CaseSensitivity::Insensitive => (pattern.to_lowercase(), CaseSensitivity::Insensitive),
@@ -68,6 +96,7 @@ impl MatchContext {
pub fn is_match(&self, main: &str) -> bool {
match self {
Self::Glob(glob_matcher) => Self::is_match_glob(main, glob_matcher),
+ Self::Regex(regex) => Self::is_match_regex(main, regex),
Self::String {
pattern,
actual_case_sensitivity,
@@ -80,6 +109,13 @@ impl MatchContext {
glob_matcher.is_match(main)
}
+ fn is_match_regex(main: &str, regex: &Regex) -> bool {
+ match regex.find(main) {
+ Some(res) => res.range() == (0..main.len()),
+ None => false,
+ }
+ }
+
fn is_match_string(
main: &str,
pattern: &str,
@@ -101,6 +137,7 @@ impl Display for MatchContext {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Self::Glob(glob_matcher) => write!(f, "{}", glob_matcher.glob().glob()),
+ Self::Regex(regex) => write!(f, "{}", regex.as_str()),
Self::String { pattern, .. } => write!(f, "{pattern}"),
Self::None => Ok(()),
}
diff --git a/src/error/error_kind.rs b/src/error/error_kind.rs
index 9afab66..a878b84 100644
--- a/src/error/error_kind.rs
+++ b/src/error/error_kind.rs
@@ -18,6 +18,8 @@ pub enum JoshutoErrorKind {
Glob,
+ Regex,
+
InvalidParameters,
UnrecognizedArgument,
diff --git a/src/error/error_type.rs b/src/error/error_type.rs
index a195ef6..ce96916 100644
--- a/src/error/error_type.rs
+++ b/src/error/error_type.rs
@@ -46,6 +46,16 @@ impl From<globset::Error> for JoshutoError {
}
}
+impl From<regex::Error> for JoshutoError {
+ fn from(err: regex::Error) -> Self {
+ let cause = err.to_string();
+ Self {
+ _kind: JoshutoErrorKind::Regex,
+ _cause: cause,
+ }
+ }
+}
+
impl From<std::env::VarError> for JoshutoError {
fn from(err: std::env::VarError) -> Self {
let cause = err.to_string();
diff --git a/src/key_command/command.rs b/src/key_command/command.rs
index d4710eb..4469ebe 100644
--- a/src/key_command/command.rs
+++ b/src/key_command/command.rs
@@ -102,6 +102,9 @@ pub enum Command {
SearchGlob {
pattern: String,
},
+ SearchRegex {
+ pattern: String,
+ },
SearchString {
pattern: String,
},
diff --git a/src/key_command/constants.rs b/src/key_command/constants.rs
index 4c808db..356997a 100644
--- a/src/key_command/constants.rs
+++ b/src/key_command/constants.rs
@@ -58,6 +58,7 @@ cmd_constants![
(CMD_SEARCH_STRING, "search"),
(CMD_SEARCH_INCREMENTAL, "search_inc"),
(CMD_SEARCH_GLOB, "search_glob"),
+ (CMD_SEARCH_REGEX, "search_regex"),
(CMD_SEARCH_NEXT, "search_next"),
(CMD_SEARCH_PREV, "search_prev"),
(CMD_SELECT_FILES, "select"),
diff --git a/src/key_command/impl_appcommand.rs b/src/key_command/impl_appcommand.rs
index e6595a8..655ff64 100644
--- a/src/key_command/impl_appcommand.rs
+++ b/src/key_command/impl_appcommand.rs
@@ -61,6 +61,7 @@ impl AppCommand for Command {
Self::SearchString { .. } => CMD_SEARCH_STRING,
Self::SearchIncremental { .. } => CMD_SEARCH_INCREMENTAL,
Self::SearchGlob { .. } => CMD_SEARCH_GLOB,
+ Self::SearchRegex { .. } => CMD_SEARCH_REGEX,
Self::SearchNext => CMD_SEARCH_NEXT,
Self::SearchPrev => CMD_SEARCH_PREV,
diff --git a/src/key_command/impl_appexecute.rs b/src/key_command/impl_appexecute.rs
index 1786e96..c4571ea 100644
--- a/src/key_command/impl_appexecute.rs
+++ b/src/key_command/impl_appexecute.rs
@@ -105,6 +105,7 @@ impl AppExecute for Command {
}
Self::TouchFile { file_name } => touch_file::touch_file(context, file_name),
Self::SearchGlob { pattern } => search_glob::search_glob(context, pattern.as_str()),
+ Self::SearchRegex { pattern } => search_regex::search_regex(context, pattern.as_str()),
Self::SearchString { pattern } => {
search_string::search_string(context, pattern.as_str(), false);
Ok(())
diff --git a/src/key_command/impl_comment.rs b/src/key_command/impl_comment.rs
index 85be947..9549621 100644
--- a/src/key_command/impl_comment.rs
+++ b/src/key_command/impl_comment.rs
@@ -91,6 +91,7 @@ impl CommandComment for Command {
Self::SearchString { .. } => "Search",
Self::SearchIncremental { .. } => "Search as you type",
Self::SearchGlob { .. } => "Search with globbing",
+ Self::SearchRegex { .. } => "Search with regex",
Self::SearchNext => "Next search entry",
Self::SearchPrev => "Previous search entry",
diff --git a/src/key_command/impl_display.rs b/src/key_command/impl_display.rs
index b15fd43..d00c88e 100644
--- a/src/key_command/impl_display.rs
+++ b/src/key_command/impl_display.rs
@@ -46,6 +46,7 @@ impl std::fmt::Display for Command {
Self::RenameFile { new_name } => write!(f, "{} {:?}", self.command(), new_name),
Self::SearchGlob { pattern } => write!(f, "{} {}", self.command(), pattern),
+ Self::SearchRegex { pattern } => write!(f, "{} {}", self.command(), pattern),
Self::SearchString { pattern } => write!(f, "{} {}", self.command(), pattern),
Self::SelectFiles { pattern, options } => {
write!(f, "{} {} {}", self.command(), pattern, options)
diff --git a/src/key_command/impl_from_str.rs b/src/key_command/impl_from_str.rs
index 0b76d38..1080198 100644
--- a/src/key_command/impl_from_str.rs
+++ b/src/key_command/impl_from_str.rs
@@ -320,6 +320,16 @@ impl std::str::FromStr for Command {
pattern: arg.to_string(),
}),
}
+ } else if command == CMD_SEARCH_REGEX {
+ match arg {
+ "" => Err(JoshutoError::new(
+ JoshutoErrorKind::InvalidParameters,
+ format!("{}: Expected 1, got 0", command),
+ )),
+ arg => Ok(Self::SearchRegex {
+ pattern: arg.to_string(),
+ }),
+ }
} else if command == CMD_SELECT_FILES {
let mut options = SelectOption::default();
let mut pattern = "";
@@ -356,6 +366,7 @@ impl std::str::FromStr for Command {
match arg.as_str() {
"--type=string" => set_type = SetType::String,
"--type=glob" => set_type = SetType::Glob,
+ "--type=regex" => set_type = SetType::Regex,
"--type=fzf" => set_type = SetType::Fzf,
s => value = s,
}