From ea4d1d37127bfd1838c6e1a9a92ec0a7136a8472 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Sat, 2 Mar 2024 14:35:56 +0900 Subject: [PATCH 01/11] Allow multiple arguments for zoxide query --- src/run.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/run.rs b/src/run.rs index 9a1ce8d..82c9b01 100644 --- a/src/run.rs +++ b/src/run.rs @@ -746,15 +746,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { let commands = command .split_whitespace() .collect::>(); - if commands.len() > 2 { - //Invalid argument. - print_warning( - "Invalid argument for zoxide.", - state.layout.y, - ); - state.move_cursor(state.layout.y); - break 'zoxide; - } else if commands.len() == 1 { + if commands.len() == 1 { //go to the home directory let home_dir = dirs::home_dir().ok_or_else(|| { From 86e77688b2e75fd1abc3c683bbf9e96794fdd367 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Sat, 2 Mar 2024 14:52:48 +0900 Subject: [PATCH 02/11] Add ignore_case option --- src/config.rs | 2 ++ src/run.rs | 17 ++++++++++++----- src/state.rs | 15 +++++++++++++-- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/config.rs b/src/config.rs index ca20ffd..9ad2918 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,6 +20,7 @@ pub struct Config { pub default: Option, pub match_vim_exit_behavior: Option, pub exec: Option>>, + pub ignore_case: Option, pub color: Option, } @@ -70,6 +71,7 @@ impl Default for Config { default: Default::default(), match_vim_exit_behavior: Default::default(), exec: Default::default(), + ignore_case: Some(false), color: Some(Default::default()), } } diff --git a/src/run.rs b/src/run.rs index 9a1ce8d..af0ca18 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1520,11 +1520,18 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { let key = &keyword.iter().collect::(); - let target = state - .list - .iter() - .position(|x| x.file_name.contains(key)); - + let target = match state.ignore_case { + Some(true) => { + state.list.iter().position(|x| { + x.file_name + .to_lowercase() + .contains(&key.to_lowercase()) + }) + } + _ => state.list.iter().position(|x| { + x.file_name.contains(key) + }), + }; match target { Some(i) => { state.layout.nums.skip = i as u16; diff --git a/src/state.rs b/src/state.rs index 6b0ec76..3a4c6c6 100644 --- a/src/state.rs +++ b/src/state.rs @@ -52,6 +52,7 @@ pub struct State { pub has_zoxide: bool, pub default: String, pub commands: Option>, + pub ignore_case: Option, pub registers: Registers, pub operations: Operation, pub jumplist: JumpList, @@ -263,6 +264,7 @@ impl State { .unwrap_or_else(|| env::var("EDITOR").unwrap_or_default()); self.match_vim_exit_behavior = config.match_vim_exit_behavior.unwrap_or_default(); self.commands = to_extension_map(&config.exec); + self.ignore_case = config.ignore_case; let colors = config.color.unwrap_or_default(); self.layout.colors = colors; } @@ -1285,7 +1287,13 @@ impl State { /// Highlight matched items. pub fn highlight_matches(&mut self, keyword: &str) { for item in self.list.iter_mut() { - item.matches = item.file_name.contains(keyword); + item.matches = match self.ignore_case { + Some(true) => item + .file_name + .to_lowercase() + .contains(&keyword.to_lowercase()), + _ => item.file_name.contains(keyword), + } } } @@ -1599,7 +1607,10 @@ impl State { let count = self .list .iter() - .filter(|x| x.file_name.contains(keyword)) + .filter(|x| match self.ignore_case { + Some(true) => x.file_name.to_lowercase().contains(&keyword.to_lowercase()), + _ => x.file_name.contains(keyword), + }) .count(); let count = if count <= 1 { format!("{} match", count) From be1b9271ad864e5ff98376f501b6d5cac536cd8d Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Sat, 2 Mar 2024 15:05:49 +0900 Subject: [PATCH 03/11] Show items linked to directory in the directory section --- src/state.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/state.rs b/src/state.rs index 6b0ec76..91bfd70 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1205,7 +1205,14 @@ impl State { } match entry.file_type { FileType::Directory => dir_v.push(entry), - FileType::File | FileType::Symlink => file_v.push(entry), + FileType::File => file_v.push(entry), + FileType::Symlink => { + if entry.symlink_dir_path.is_some() { + dir_v.push(entry); + } else { + file_v.push(entry); + } + } } } From 0fb83c5115369d38e20ef2c64ac110b3af4a9cde Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Sun, 3 Mar 2024 10:53:03 +0900 Subject: [PATCH 04/11] Fix: Receive multiple arguments --- src/run.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/run.rs b/src/run.rs index 82c9b01..0954992 100644 --- a/src/run.rs +++ b/src/run.rs @@ -762,7 +762,8 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { break 'zoxide; } else if let Ok(output) = std::process::Command::new("zoxide") - .args(["query", commands[1]]) + .arg("query") + .args(&commands[1..]) .output() { let output = output.stdout; From b0ce98e39b4ee369b2ab1d602381734c08c8a4a3 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Sun, 3 Mar 2024 11:32:22 +0900 Subject: [PATCH 05/11] Show pointed path when symlink --- src/state.rs | 94 +++++++++++++++++++++++++++++----------------------- 1 file changed, 53 insertions(+), 41 deletions(-) diff --git a/src/state.rs b/src/state.rs index 23c17dc..6deac74 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1647,56 +1647,68 @@ impl State { /// Return footer string. fn make_footer(&self, item: &ItemInfo) -> String { - match &item.file_ext { - Some(ext) => { - let footer = match item.permissions { - Some(permissions) => { - format!( - " {}/{} {} {} {}", + let mut footer = String::new(); + if item.file_type == FileType::Symlink { + footer = " linked to: ".to_owned(); + match &item.symlink_dir_path { + Some(true_path) => { + footer.push_str(true_path.to_str().unwrap_or("(invalid unicode path)")) + } + None => match fs::read_link(&item.file_path) { + Ok(true_path) => { + footer.push_str(true_path.to_str().unwrap_or("(invalid unicode path)")) + } + Err(_) => footer.push_str("Broken link"), + }, + } + } else { + match &item.file_ext { + Some(ext) => { + footer = match item.permissions { + Some(permissions) => { + format!( + " {}/{} {} {} {}", + self.layout.nums.index + 1, + self.list.len(), + ext.clone(), + to_proper_size(item.file_size), + convert_to_permissions(permissions) + ) + } + None => format!( + " {}/{} {} {}", self.layout.nums.index + 1, self.list.len(), ext.clone(), to_proper_size(item.file_size), - convert_to_permissions(permissions) - ) - } - None => format!( - " {}/{} {} {}", - self.layout.nums.index + 1, - self.list.len(), - ext.clone(), - to_proper_size(item.file_size), - ), - }; - footer - .chars() - .take(self.layout.terminal_column.into()) - .collect() - } - None => { - let footer = match item.permissions { - Some(permissions) => { - format!( - " {}/{} {} {}", + ), + }; + } + None => { + footer = match item.permissions { + Some(permissions) => { + format!( + " {}/{} {} {}", + self.layout.nums.index + 1, + self.list.len(), + to_proper_size(item.file_size), + convert_to_permissions(permissions) + ) + } + None => format!( + " {}/{} {}", self.layout.nums.index + 1, self.list.len(), to_proper_size(item.file_size), - convert_to_permissions(permissions) - ) - } - None => format!( - " {}/{} {}", - self.layout.nums.index + 1, - self.list.len(), - to_proper_size(item.file_size), - ), - }; - footer - .chars() - .take(self.layout.terminal_column.into()) - .collect() + ), + }; + } } } + footer + .chars() + .take(self.layout.terminal_column.into()) + .collect() } /// Scroll down previewed text. From d0408fc89f01bc619f87ba8efdcb5e90fbdb821c Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Sun, 3 Mar 2024 11:34:15 +0900 Subject: [PATCH 06/11] Update CHANGELOG --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e2156d..1cb2cd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ ## Unreleased +### Added +- `ignore_case` option to do case-insensitie search by `/`. + +### Changed +- Symlink items linked to directory now appears in the directory section, not the file section. + +### fixed +- `z` command can now receive multiple arguments: `z dot files` works as in your terminal. + ## v2.12.1 (2024-02-04) ### Fixed From 1b7a4ca832900c664ee553d91cb9dda89b539372 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Sun, 3 Mar 2024 11:55:52 +0900 Subject: [PATCH 07/11] Use normpath to show atcual target path of symlink items --- src/state.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/state.rs b/src/state.rs index 6deac74..632a818 100644 --- a/src/state.rs +++ b/src/state.rs @@ -16,6 +16,7 @@ use crossterm::event::KeyEventKind; use crossterm::event::{Event, KeyCode, KeyEvent}; use crossterm::style::Stylize; use log::info; +use normpath::PathExt; use std::collections::VecDeque; use std::collections::{BTreeMap, BTreeSet}; use std::env; @@ -1655,10 +1656,12 @@ impl State { footer.push_str(true_path.to_str().unwrap_or("(invalid unicode path)")) } None => match fs::read_link(&item.file_path) { - Ok(true_path) => { - footer.push_str(true_path.to_str().unwrap_or("(invalid unicode path)")) - } - Err(_) => footer.push_str("Broken link"), + Ok(true_path) => match true_path.normalize() { + Ok(p) => footer + .push_str(p.as_path().to_str().unwrap_or("(invalid univode path)")), + Err(_) => footer.push_str("(invalid path)"), + }, + Err(_) => footer.push_str("(broken link)"), }, } } else { @@ -1818,12 +1821,7 @@ fn read_item(entry: fs::DirEntry) -> ItemInfo { if filetype == FileType::Symlink { if let Ok(sym_meta) = fs::metadata(&path) { if sym_meta.is_dir() { - if cfg!(not(windows)) { - // Avoid error on Windows - path.canonicalize().ok() - } else { - Some(path.clone()) - } + path.normalize().map(|p| p.into_path_buf()).ok() } else { None } From a2f6e1c467d93bc7207836800eaa8401ed4d834b Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Sun, 3 Mar 2024 11:56:14 +0900 Subject: [PATCH 08/11] Remove canonicalize for OS compartibility --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + src/run.rs | 24 ++++++++++++++---------- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b08d2aa..552a402 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,6 +346,7 @@ dependencies = [ "lzma-rs", "natord", "nix", + "normpath", "rayon", "serde", "serde_yaml", @@ -641,6 +642,15 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "normpath" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5831952a9476f2fed74b77d74182fa5ddc4d21c72ec45a333b250e3ed0272804" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "num-conv" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3ccaf0f..87d9034 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ lzma-rs = "0.3.0" zstd = "0.12.4" unicode-width = "0.1.10" git2 = {version = "0.18.0", default-features = false } +normpath = "1.2.0" [dev-dependencies] bwrap = { version = "1.3.0", features = ["use_std"] } diff --git a/src/run.rs b/src/run.rs index 05c2ff1..67261b9 100644 --- a/src/run.rs +++ b/src/run.rs @@ -13,6 +13,7 @@ use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifier use crossterm::execute; use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen}; use log::{error, info}; +use normpath::PathExt; use std::env; use std::io::{stdout, Write}; use std::panic; @@ -118,12 +119,14 @@ pub fn run(arg: PathBuf, log: bool) -> Result<(), FxError> { let mut state = State::new(&session_path)?; state.trash_dir = trash_dir_path; state.lwd_file = lwd_file_path; - state.current_dir = if cfg!(not(windows)) { - // If executed this on windows, "//?" will be inserted at the beginning of the path. - arg.canonicalize()? - } else { - arg - }; + let normalized_arg = arg.normalize(); + if normalized_arg.is_err() { + return Err(FxError::Arg(format!( + "Invalid path: {}\n`fx -h` shows help.", + &arg.display() + ))); + } + state.current_dir = normalized_arg.unwrap().into_path_buf(); state.jumplist.add(&state.current_dir); state.is_ro = match has_write_permission(&state.current_dir) { Ok(b) => !b, @@ -2240,12 +2243,13 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { } else if commands.len() == 2 && command == "cd" { if let Ok(target) = std::path::Path::new(commands[1]) - .canonicalize() + .normalize() { if target.exists() { - if let Err(e) = - state.chdir(&target, Move::Jump) - { + if let Err(e) = state.chdir( + &target.into_path_buf(), + Move::Jump, + ) { print_warning(e, state.layout.y); } break 'command; From c1d1cf1931f18cf9885ea8002bb0ec3feac4ae90 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Sat, 6 Apr 2024 16:46:26 +0900 Subject: [PATCH 09/11] Add ignore_case to sample config --- config.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config.yaml b/config.yaml index c56aec8..c235d2e 100644 --- a/config.yaml +++ b/config.yaml @@ -16,6 +16,9 @@ # 'feh -.': # [jpg, jpeg, png, gif, svg, hdr] +# Whether to do the case-insensitive search by `/`. +# ignore_case: true + # The foreground color of directory, file and symlink. # Pick one of the following: # Black // 0 From 53035558b1b61aef2224ebb475882332f1107976 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Sat, 6 Apr 2024 16:46:38 +0900 Subject: [PATCH 10/11] Add config test --- src/config.rs | 72 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 9ad2918..75a1d14 100644 --- a/src/config.rs +++ b/src/config.rs @@ -24,7 +24,7 @@ pub struct Config { pub color: Option, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, PartialEq)] pub struct ConfigColor { pub dir_fg: Colorname, pub file_fg: Colorname, @@ -43,7 +43,7 @@ impl Default for ConfigColor { } } -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, PartialEq)] pub enum Colorname { Black, // 0 Red, // 1 @@ -143,3 +143,71 @@ pub fn read_config_or_default() -> Result { }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_read_default_config() { + let default_config: Config = serde_yaml::from_str("").unwrap(); + assert_eq!(default_config.default, None); + assert_eq!(default_config.match_vim_exit_behavior, None); + assert_eq!(default_config.exec, None); + assert_eq!(default_config.ignore_case, None); + assert_eq!(default_config.color, None); + } + + #[test] + fn test_read_full_config() { + let full_config: Config = serde_yaml::from_str( + r#" +default: nvim +match_vim_exit_behavior: true +exec: + zathura: + [pdf] + 'feh -.': + [jpg, jpeg, png, gif, svg, hdr] +ignore_case: true +color: + dir_fg: LightCyan + file_fg: LightWhite + symlink_fg: LightYellow + dirty_fg: Red +"#, + ) + .unwrap(); + assert_eq!(full_config.default, Some("nvim".to_string())); + assert_eq!(full_config.match_vim_exit_behavior, Some(true)); + assert_eq!( + full_config.exec.clone().unwrap().get("zathura"), + Some(&vec!["pdf".to_string()]) + ); + assert_eq!( + full_config.exec.unwrap().get("feh -."), + Some(&vec![ + "jpg".to_string(), + "jpeg".to_string(), + "png".to_string(), + "gif".to_string(), + "svg".to_string(), + "hdr".to_string() + ]) + ); + assert_eq!(full_config.ignore_case, Some(true)); + assert_eq!( + full_config.color.clone().unwrap().dir_fg, + Colorname::LightCyan + ); + assert_eq!( + full_config.color.clone().unwrap().file_fg, + Colorname::LightWhite + ); + assert_eq!( + full_config.color.clone().unwrap().symlink_fg, + Colorname::LightYellow + ); + assert_eq!(full_config.color.unwrap().dirty_fg, Colorname::Red); + } +} From 4628a59ab031dd8b65d1778fd01696e438d5545d Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Sun, 7 Apr 2024 15:00:05 +0900 Subject: [PATCH 11/11] v2.13.0 --- CHANGELOG.md | 6 +++++- Cargo.toml | 2 +- README.md | 22 +++++++++++++++++++--- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cb2cd3..08434c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,15 @@ ## Unreleased +## v2.13.0 (2024-04-07) + ### Added -- `ignore_case` option to do case-insensitie search by `/`. +- `ignore_case` option to the do case-insensitie search by `/`. +- Symbolic link destinations are now displayed when the cursor is hovered over them. ### Changed - Symlink items linked to directory now appears in the directory section, not the file section. +- MSRV is now v1.74.1 ### fixed - `z` command can now receive multiple arguments: `z dot files` works as in your terminal. diff --git a/Cargo.toml b/Cargo.toml index 87d9034..3772f0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "felix" -version = "2.12.1" +version = "2.13.0" authors = ["Kyohei Uto "] edition = "2021" description = "tui file manager with vim-like key mapping" diff --git a/README.md b/README.md index ec08da4..29932a0 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,22 @@ For more detailed document, visit https://kyoheiu.dev/felix. ## New release +## v2.13.0 (2024-04-07) + +### Added + +- `ignore_case` option to the do case-insensitie search by `/`. +- Symbolic link destinations are now displayed when the cursor is hovered over them. + +### Changed + +- Symlink items linked to directory now appears in the directory section, not the file section. +- MSRV is now v1.74.1 + +### fixed + +- `z` command can now receive multiple arguments: `z dot files` works as in your terminal. + ## v2.12.1 (2024-02-04) ### Fixed @@ -70,16 +86,16 @@ report any problems._ | package | installation command | notes | | ---------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| crates.io | `cargo install felix` | Minimum Supported rustc Version: **1.67.1** | +| crates.io | `cargo install felix` | Minimum Supported rustc Version: **1.74.1** | | Arch Linux | `pacman -S felix-rs` | The binary name is `felix` if you install via pacman. Alias `fx='felix'` if you want, as this document (and other installations) uses `fx`. | | NetBSD | `pkgin install felix` | | ### From this repository - Make sure that `gcc` is installed. -- MSRV(Minimum Supported rustc Version): **1.67.1** +- MSRV(Minimum Supported rustc Version): **1.74.1** -Update Rust if rustc < 1.67.1: +Update Rust if rustc < 1.74.1: ``` rustup update