diff --git a/app/src/executor.rs b/app/src/executor.rs index 6c7e41ae..bbc1ffec 100644 --- a/app/src/executor.rs +++ b/app/src/executor.rs @@ -151,6 +151,7 @@ impl Executor { let b = cx.manager.active_mut().set_sorter(FilesSorter { by: SortBy::try_from(exec.args.get(0).cloned().unwrap_or_default()) .unwrap_or_default(), + sensitive: exec.named.contains_key("sensitive"), reverse: exec.named.contains_key("reverse"), dir_first: exec.named.contains_key("dir_first"), }); diff --git a/app/src/header/layout.rs b/app/src/header/layout.rs index 15695166..4371b95f 100644 --- a/app/src/header/layout.rs +++ b/app/src/header/layout.rs @@ -19,11 +19,11 @@ impl<'a> Widget for Layout<'a> { .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) .split(area); - let current = &self.cx.manager.current(); - let location = if current.cwd.is_search() { - format!("{} (search)", readable_path(¤t.cwd)) + let cwd = &self.cx.manager.current().cwd; + let location = if cwd.is_search() { + format!("{} (search: {})", readable_path(cwd), cwd.frag().unwrap()) } else { - readable_path(¤t.cwd) + readable_path(cwd) }; Paragraph::new(location).style(Style::new().fg(Color::Cyan)).render(chunks[0], buf); diff --git a/config/docs/yazi.md b/config/docs/yazi.md index 7e6f4255..0c609692 100644 --- a/config/docs/yazi.md +++ b/config/docs/yazi.md @@ -14,6 +14,11 @@ - `"natural"`: Sort naturally, e.g. `1.md` < `2.md` < `10.md` - `"size"`: Sort by file size +- sort_sensitive: Sort case-sensitively + + - `true`: Case-sensitive + - `false`: Case-insensitive + - sort_reverse: Display files in reverse order - `true`: Reverse order @@ -81,8 +86,8 @@ rules = [ Available rule parameters are as follows: -- name: Glob expression for matching the file name -- mime: Glob expression for matching the MIME type +- name: Glob expression for matching the file name. Case insensitive by default, add `\s` to the beginning to make it sensitive. +- mime: Glob expression for matching the MIME type. Case insensitive by default, add `\s` to the beginning to make it sensitive. - use: Opener name corresponding to the names in the opener section. ## tasks diff --git a/config/preset/yazi.toml b/config/preset/yazi.toml index ed101007..0e23f707 100644 --- a/config/preset/yazi.toml +++ b/config/preset/yazi.toml @@ -1,6 +1,7 @@ [manager] layout = [ 1, 4, 3 ] sort_by = "modified" +sort_sensitive = false sort_reverse = true sort_dir_first = true show_hidden = false diff --git a/config/src/manager/manager.rs b/config/src/manager/manager.rs index ddc1627a..f1acf380 100644 --- a/config/src/manager/manager.rs +++ b/config/src/manager/manager.rs @@ -9,6 +9,7 @@ pub struct Manager { // Sorting pub sort_by: SortBy, + pub sort_sensitive: bool, pub sort_reverse: bool, pub sort_dir_first: bool, diff --git a/config/src/pattern.rs b/config/src/pattern.rs index 1b5bdc60..7a9d7c30 100644 --- a/config/src/pattern.rs +++ b/config/src/pattern.rs @@ -1,18 +1,26 @@ use std::path::Path; +use glob::MatchOptions; use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(try_from = "String")] pub struct Pattern { inner: glob::Pattern, + sensitive: bool, is_folder: bool, full_path: bool, } impl Pattern { #[inline] - pub fn matches(&self, str: impl AsRef) -> bool { self.inner.matches(str.as_ref()) } + pub fn matches(&self, str: impl AsRef) -> bool { + self.inner.matches_with(str.as_ref(), MatchOptions { + case_sensitive: self.sensitive, + require_literal_separator: false, + require_literal_leading_dot: false, + }) + } #[inline] pub fn match_path(&self, path: impl AsRef, is_folder: Option) -> bool { @@ -20,7 +28,7 @@ impl Pattern { let s = if self.full_path { path.to_str() } else { - path.file_name().and_then(|n| n.to_str()).or(path.to_str()) + path.file_name().and_then(|n| n.to_str()).or_else(|| path.to_str()) }; is_folder.map_or(true, |f| f == self.is_folder) && s.map_or(false, |s| self.matches(s)) } @@ -30,11 +38,13 @@ impl TryFrom<&str> for Pattern { type Error = anyhow::Error; fn try_from(s: &str) -> Result { - let new = s.trim_end_matches('/'); + let a = s.trim_start_matches("\\s"); + let b = a.trim_end_matches('/'); Ok(Self { - inner: glob::Pattern::new(new)?, - is_folder: new.len() < s.len(), - full_path: new.contains('/'), + inner: glob::Pattern::new(b)?, + sensitive: a.len() < s.len(), + is_folder: b.len() < a.len(), + full_path: b.contains('/'), }) } } diff --git a/core/src/files/sorter.rs b/core/src/files/sorter.rs index 36ce4794..5149a9ac 100644 --- a/core/src/files/sorter.rs +++ b/core/src/files/sorter.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, collections::BTreeMap, mem}; +use std::{cmp::Ordering, collections::BTreeMap, mem, ops::Deref}; use config::{manager::SortBy, MANAGER}; use shared::Url; @@ -8,6 +8,7 @@ use super::File; #[derive(Clone, Copy, PartialEq)] pub struct FilesSorter { pub by: SortBy, + pub sensitive: bool, pub reverse: bool, pub dir_first: bool, } @@ -16,6 +17,7 @@ impl Default for FilesSorter { fn default() -> Self { Self { by: MANAGER.sort_by, + sensitive: MANAGER.sort_sensitive, reverse: MANAGER.sort_reverse, dir_first: MANAGER.sort_dir_first, } @@ -29,9 +31,17 @@ impl FilesSorter { } match self.by { - SortBy::Alphabetical => { - items.sort_unstable_by(|a, b| self.cmp(&*a.url, &*b.url, self.promote(a, b))) - } + SortBy::Alphabetical => items.sort_unstable_by(|a, b| { + if self.sensitive { + return self.cmp(&*a.url, &*b.url, self.promote(a, b)); + } + + self.cmp( + a.url.as_os_str().to_ascii_lowercase(), + b.url.as_os_str().to_ascii_lowercase(), + self.promote(a, b), + ) + }), SortBy::Created => items.sort_unstable_by(|a, b| { if let (Ok(aa), Ok(bb)) = (a.meta.created(), b.meta.created()) { return self.cmp(aa, bb, self.promote(a, b)); @@ -65,12 +75,16 @@ impl FilesSorter { indices.sort_unstable_by(|&a, &b| { let promote = self.promote(entities[a].1, entities[b].1); if promote != Ordering::Equal { - promote - } else if self.reverse { - natord::compare(&entities[b].0, &entities[a].0) - } else { - natord::compare(&entities[a].0, &entities[b].0) + return promote; } + + let ordering = if self.sensitive { + natord::compare(&entities[a].0, &entities[b].0) + } else { + natord::compare_ignore_case(&entities[a].0, &entities[b].0) + }; + + if self.reverse { ordering.reverse() } else { ordering } }); let dummy = File { diff --git a/core/src/manager/preview/provider.rs b/core/src/manager/preview/provider.rs index 4ac499db..3271b0ae 100644 --- a/core/src/manager/preview/provider.rs +++ b/core/src/manager/preview/provider.rs @@ -3,6 +3,7 @@ use std::{io::BufRead, path::Path, sync::atomic::{AtomicUsize, Ordering}}; use adaptor::ADAPTOR; use anyhow::anyhow; use config::{MANAGER, PREVIEW}; +use futures::TryFutureExt; use shared::{MimeKind, PeekError}; use syntect::{easy::HighlightFile, util::as_24_bit_terminal_escaped}; use tokio::fs; @@ -69,7 +70,9 @@ impl Provider { } pub(super) async fn json(path: &Path, skip: usize) -> Result { - external::jq(path, skip, MANAGER.layout.preview_height()).await + external::jq(path, skip, MANAGER.layout.preview_height()) + .or_else(|_| Provider::highlight(path, skip)) + .await } pub(super) async fn archive(path: &Path, skip: usize) -> Result { diff --git a/core/src/manager/tab.rs b/core/src/manager/tab.rs index 6e8b656a..b24fb4b2 100644 --- a/core/src/manager/tab.rs +++ b/core/src/manager/tab.rs @@ -289,7 +289,7 @@ impl Tab { handle.abort(); } - let cwd = self.current.cwd.to_search(); + let mut cwd = self.current.cwd.clone(); let hidden = self.show_hidden; self.search = Some(tokio::spawn(async move { @@ -297,6 +297,7 @@ impl Tab { bail!("canceled") }; + cwd = cwd.into_search(subject.clone()); let rx = if grep { external::rg(external::RgOpt { cwd: cwd.clone(), hidden, subject }) } else { diff --git a/shared/src/url.rs b/shared/src/url.rs index 9b5b340c..10e45db3 100644 --- a/shared/src/url.rs +++ b/shared/src/url.rs @@ -4,6 +4,7 @@ use std::{ffi::{OsStr, OsString}, fmt::{Debug, Formatter}, ops::{Deref, DerefMut pub struct Url { scheme: UrlScheme, path: PathBuf, + frag: Option, } #[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] @@ -114,11 +115,12 @@ impl Url { pub fn is_search(&self) -> bool { self.scheme == UrlScheme::Search } #[inline] - pub fn to_search(&self) -> Self { self.clone().into_search() } + pub fn to_search(&self, frag: String) -> Self { self.clone().into_search(frag) } #[inline] - pub fn into_search(mut self) -> Self { + pub fn into_search(mut self, frag: String) -> Self { self.scheme = UrlScheme::Search; + self.frag = Some(frag); self } @@ -137,4 +139,8 @@ impl Url { // --- Path #[inline] pub fn set_path(&mut self, path: PathBuf) { self.path = path; } + + // --- Frag + #[inline] + pub fn frag(&self) -> Option<&str> { self.frag.as_deref() } }