From 7ef9d2ad7ec421a082d39364221a322bc5943837 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Mon, 21 Nov 2022 06:07:06 +0900 Subject: [PATCH 01/41] Fix comments --- config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.yaml b/config.yaml index 08b9b0fd..500d244d 100644 --- a/config.yaml +++ b/config.yaml @@ -1,12 +1,12 @@ # v2.1.0 # (Optional) -# Default exec command when open files. +# Default exec command when opening files. # If not set, will default to $EDITOR. # default: nvim # (Optional) -# key (the command you want to use): [values] (extensions) +# key (the command you want to use when opening certain files): [values] (extensions) # exec: # feh: # [jpg, jpeg, png, gif, svg] From e13905b393ca2155eb11c236bb0c57b4e85a3586 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Tue, 22 Nov 2022 05:02:06 +0900 Subject: [PATCH 02/41] Remove temporary variable --- src/run.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/run.rs b/src/run.rs index e81392a6..f69f672e 100644 --- a/src/run.rs +++ b/src/run.rs @@ -802,7 +802,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { state.clear_and_show_headline(); state.update_list()?; state.list_up(); - let cursor_pos = if state.list.is_empty() { + state.layout.y = if state.list.is_empty() { BEGINNING_ROW } else if state.layout.nums.index == len - 1 { state.layout.nums.go_up(); @@ -813,9 +813,9 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { let duration = duration_to_string(start.elapsed()); print_info( format!("1 item deleted [{}]", duration), - cursor_pos, + state.layout.y, ); - state.move_cursor(cursor_pos); + state.move_cursor(state.layout.y); } _ => { reset_info_line(); From 52016b29af6880abfffc3ec8bd4e551bf46829c2 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Tue, 22 Nov 2022 05:31:56 +0900 Subject: [PATCH 03/41] Use term::terminal_size instead of imported function --- src/errors.rs | 4 +++- src/run.rs | 6 +++--- src/state.rs | 6 ++---- src/term.rs | 6 ++++++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index a09a49c3..f2ad63f9 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; #[derive(Debug)] pub enum FxError { + TerminalSizeDetection, Io(String), Dirs(String), GetItem, @@ -15,8 +16,8 @@ pub enum FxError { RemoveItem(PathBuf), TooSmallWindowSize, Log(String), - Panic, Unpack(String), + Panic, } impl std::error::Error for FxError {} @@ -24,6 +25,7 @@ impl std::error::Error for FxError {} impl std::fmt::Display for FxError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let printable = match self { + FxError::TerminalSizeDetection => "Error: Cannot detect terminal size.".to_owned(), FxError::Io(s) => s.to_owned(), FxError::Dirs(s) => s.to_owned(), FxError::GetItem => "Error: Cannot get item info".to_owned(), diff --git a/src/run.rs b/src/run.rs index f69f672e..0c198c3a 100644 --- a/src/run.rs +++ b/src/run.rs @@ -747,7 +747,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { } } } else { - let (new_column, new_row) = crossterm::terminal::size()?; + let (new_column, new_row) = terminal_size()?; state.refresh(new_column, new_row, state.layout.y); } } @@ -757,7 +757,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { Split::Vertical => { state.layout.split = Split::Horizontal; if state.layout.preview { - let (new_column, mut new_row) = crossterm::terminal::size()?; + let (new_column, mut new_row) = terminal_size()?; new_row /= 2; state.refresh(new_column, new_row, state.layout.y); } @@ -765,7 +765,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { Split::Horizontal => { state.layout.split = Split::Vertical; if state.layout.preview { - let (mut new_column, new_row) = crossterm::terminal::size()?; + let (mut new_column, new_row) = terminal_size()?; new_column /= 2; state.refresh(new_column, new_row, state.layout.y); } diff --git a/src/state.rs b/src/state.rs index dd313689..8cda9436 100644 --- a/src/state.rs +++ b/src/state.rs @@ -99,8 +99,7 @@ impl State { } }; let session = read_session()?; - let (original_column, original_row) = - crossterm::terminal::size().unwrap_or_else(|_| panic!("Cannot detect terminal size.")); + let (original_column, original_row) = terminal_size()?; // Return error if terminal size may cause panic if original_column < 4 { @@ -696,8 +695,7 @@ impl State { pub fn refresh(&mut self, column: u16, row: u16, mut cursor_pos: u16) { let (time_start, name_max) = make_layout(column); - let (original_column, original_row) = - crossterm::terminal::size().unwrap_or_else(|_| panic!("Cannot detect terminal size.")); + let (original_column, original_row) = terminal_size().unwrap(); self.layout.terminal_row = row; self.layout.terminal_column = column; diff --git a/src/term.rs b/src/term.rs index 282e8343..ed037c0a 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,3 +1,5 @@ +use crate::errors::FxError; + use super::config::Colorname; use crossterm::{ @@ -30,6 +32,10 @@ pub fn leave_raw_mode() { terminal::disable_raw_mode().ok(); } +pub fn terminal_size() -> Result<(u16, u16), FxError> { + terminal::size().map_err(|_| FxError::TerminalSizeDetection) +} + pub fn move_to(x: u16, y: u16) { print!("{}", MoveTo(x - 1, y - 1)); } From 79c07f61e17f5949c318e0e01212479d08d8c83c Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Tue, 22 Nov 2022 06:02:54 +0900 Subject: [PATCH 04/41] More predictable function name --- src/functions.rs | 2 +- src/run.rs | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/functions.rs b/src/functions.rs index b03a8f20..d78f91b1 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -67,7 +67,7 @@ pub fn rename_dir(dir_name: &str, name_set: &HashSet) -> String { new_name } -pub fn reset_info_line() { +pub fn go_to_and_rest_info() { to_info_bar(); clear_current_line(); } diff --git a/src/run.rs b/src/run.rs index 0c198c3a..afcb27b9 100644 --- a/src/run.rs +++ b/src/run.rs @@ -357,7 +357,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { if let Event::Key(KeyEvent { code, .. }) = event::read()? { match code { KeyCode::Esc => { - reset_info_line(); + go_to_and_rest_info(); hide_cursor(); state.move_cursor(state.layout.y); break 'zoxide; @@ -383,7 +383,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { KeyCode::Backspace => { if current_pos == initial_pos + 1 { - reset_info_line(); + go_to_and_rest_info(); hide_cursor(); state.move_cursor(state.layout.y); break 'zoxide; @@ -462,7 +462,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { } } } else { - reset_info_line(); + go_to_and_rest_info(); hide_cursor(); state.move_cursor(state.layout.y); break 'zoxide; @@ -590,7 +590,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { } _ => { - reset_info_line(); + go_to_and_rest_info(); hide_cursor(); state.move_cursor(state.layout.y); } @@ -818,7 +818,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { state.move_cursor(state.layout.y); } _ => { - reset_info_line(); + go_to_and_rest_info(); hide_cursor(); state.move_cursor(state.layout.y); } @@ -842,13 +842,13 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { match code { KeyCode::Char('y') => { state.yank_item(false); - reset_info_line(); + go_to_and_rest_info(); hide_cursor(); print_info("1 item yanked", state.layout.y); } _ => { - reset_info_line(); + go_to_and_rest_info(); hide_cursor(); state.move_cursor(state.layout.y); } @@ -934,7 +934,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { } KeyCode::Esc => { - reset_info_line(); + go_to_and_rest_info(); hide_cursor(); state.move_cursor(state.layout.y); break; @@ -1011,7 +1011,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { if let Event::Key(KeyEvent { code, .. }) = event::read()? { match code { KeyCode::Enter => { - reset_info_line(); + go_to_and_rest_info(); state.keyword = Some(keyword.iter().collect()); state.move_cursor(state.layout.y); break; @@ -1180,7 +1180,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { if let Event::Key(KeyEvent { code, .. }) = event::read()? { match code { KeyCode::Esc => { - reset_info_line(); + go_to_and_rest_info(); hide_cursor(); state.move_cursor(state.layout.y); break 'command; @@ -1206,7 +1206,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { KeyCode::Backspace => { if current_pos == initial_pos { - reset_info_line(); + go_to_and_rest_info(); hide_cursor(); state.move_cursor(state.layout.y); break 'command; @@ -1224,7 +1224,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { KeyCode::Enter => { hide_cursor(); if command.is_empty() { - reset_info_line(); + go_to_and_rest_info(); state.move_cursor(state.layout.y); break; } @@ -1416,7 +1416,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { print_warning(e, state.layout.y); continue 'main; } - reset_info_line(); + go_to_and_rest_info(); if state.current_dir == state.trash_dir { state.reload(BEGINNING_ROW)?; print_info( @@ -1434,7 +1434,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { break 'command; } _ => { - reset_info_line(); + go_to_and_rest_info(); state.move_cursor(state.layout.y); break 'command; } @@ -1570,7 +1570,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { } _ => { - reset_info_line(); + go_to_and_rest_info(); hide_cursor(); state.move_cursor(state.layout.y); } From dd5c87b2a6aca0e2baa4f7a78c59f7614332d5cd Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Tue, 22 Nov 2022 06:03:21 +0900 Subject: [PATCH 05/41] Trim info/error message to fit into the terminal width --- src/functions.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/functions.rs b/src/functions.rs index d78f91b1..e2edb556 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -80,9 +80,16 @@ pub fn delete_cursor() { /// Print the result of operation, such as put/delete/redo/undo. pub fn print_info(message: T, then: u16) { delete_cursor(); - reset_info_line(); + go_to_and_rest_info(); info!("{}", message); - print!("{}", message); + + let (width, _) = terminal_size().unwrap(); + let trimmed: String = message + .to_string() + .chars() + .take((width - 1).into()) + .collect(); + print!("{}", trimmed); hide_cursor(); move_to(1, then); @@ -93,13 +100,18 @@ pub fn print_info(message: T, then: u16) { /// When something goes wrong or does not work, print information about it. pub fn print_warning(message: T, then: u16) { delete_cursor(); + go_to_and_rest_info(); warn!("{}", message); - to_info_bar(); - clear_current_line(); + let (width, _) = terminal_size().unwrap(); + let trimmed: String = message + .to_string() + .chars() + .take((width - 1).into()) + .collect(); set_color(&TermColor::ForeGround(&Colorname::White)); set_color(&TermColor::BackGround(&Colorname::LightRed)); - print!("{}", message,); + print!("{}", trimmed); reset_color(); hide_cursor(); From 5afc217cc03738a399a25964e4b3b2bcb8be7dd3 Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Tue, 22 Nov 2022 06:03:32 +0900 Subject: [PATCH 06/41] Trim header to fit into the terminal width --- src/state.rs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/state.rs b/src/state.rs index 8cda9436..0125d4bb 100644 --- a/src/state.rs +++ b/src/state.rs @@ -726,12 +726,24 @@ impl State { clear_all(); move_to(1, 1); + let mut header_space = (self.layout.terminal_column - 1) as usize; + //Show current directory path. //crossterm's Stylize cannot be applied to PathBuf, //current directory does not have any text attribute for now. - set_color(&TermColor::ForeGround(&Colorname::Cyan)); - print!(" {}", self.current_dir.display(),); - reset_color(); + let current_dir = self.current_dir.display().to_string(); + if current_dir.len() >= header_space { + let current_dir: String = current_dir.chars().take(header_space).collect(); + set_color(&TermColor::ForeGround(&Colorname::Cyan)); + print!(" {}", current_dir); + reset_color(); + return; + } else { + set_color(&TermColor::ForeGround(&Colorname::Cyan)); + print!(" {}", current_dir); + reset_color(); + header_space -= current_dir.len(); + } //If .git directory exists, get the branch information and print it. let git = self.current_dir.join(".git"); @@ -740,10 +752,13 @@ impl State { if let Ok(head) = std::fs::read(head) { let branch: Vec = head.into_iter().skip(16).collect(); if let Ok(branch) = std::str::from_utf8(&branch) { - print!(" on ",); - set_color(&TermColor::ForeGround(&Colorname::LightMagenta)); - print!("{}", branch.trim().bold()); - reset_color(); + if branch.len() + 4 <= header_space { + print!(" on ",); + set_color(&TermColor::ForeGround(&Colorname::LightMagenta)); + print!("{}", branch.trim().bold()); + reset_color(); + } else { + } } } } From ffaf8e1596ec362d57ce4452be1ad0de6967dcf7 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Wed, 23 Nov 2022 05:08:03 +0900 Subject: [PATCH 07/41] Change: refresh may fail in order to detect terminal size --- src/run.rs | 16 ++++++++-------- src/state.rs | 9 +++++---- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/run.rs b/src/run.rs index afcb27b9..eda4ed9d 100644 --- a/src/run.rs +++ b/src/run.rs @@ -107,7 +107,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { Split::Vertical => state.layout.terminal_row, Split::Horizontal => state.layout.terminal_row / 2, }; - state.refresh(new_column, new_row, BEGINNING_ROW); + state.refresh(new_column, new_row, BEGINNING_ROW)?; } else { state.reload(BEGINNING_ROW)?; } @@ -738,17 +738,17 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { Split::Vertical => { let new_column = state.layout.terminal_column / 2; let new_row = state.layout.terminal_row; - state.refresh(new_column, new_row, state.layout.y); + state.refresh(new_column, new_row, state.layout.y)?; } Split::Horizontal => { let new_row = state.layout.terminal_row / 2; let new_column = state.layout.terminal_column; - state.refresh(new_column, new_row, state.layout.y); + state.refresh(new_column, new_row, state.layout.y)?; } } } else { let (new_column, new_row) = terminal_size()?; - state.refresh(new_column, new_row, state.layout.y); + state.refresh(new_column, new_row, state.layout.y)?; } } @@ -759,7 +759,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { if state.layout.preview { let (new_column, mut new_row) = terminal_size()?; new_row /= 2; - state.refresh(new_column, new_row, state.layout.y); + state.refresh(new_column, new_row, state.layout.y)?; } } Split::Horizontal => { @@ -767,7 +767,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { if state.layout.preview { let (mut new_column, new_row) = terminal_size()?; new_column /= 2; - state.refresh(new_column, new_row, state.layout.y); + state.refresh(new_column, new_row, state.layout.y)?; } } }, @@ -1612,7 +1612,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { new_row - 1 }; - state.refresh(new_column, new_row, cursor_pos); + state.refresh(new_column, new_row, cursor_pos)?; } else { let cursor_pos = if state.layout.y < row { state.layout.y @@ -1621,7 +1621,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { state.layout.nums.index -= diff as usize; row - 1 }; - state.refresh(column, row, cursor_pos); + state.refresh(column, row, cursor_pos)?; } } _ => {} diff --git a/src/state.rs b/src/state.rs index 0125d4bb..5c4e3a61 100644 --- a/src/state.rs +++ b/src/state.rs @@ -411,8 +411,8 @@ impl State { for item in self.list.iter_mut().filter(|item| item.selected) { self.registered.push(item.clone()); } - } else { - let item = self.get_item().unwrap().clone(); + } else if let Ok(item) = self.get_item() { + let item = item.clone(); self.registered.push(item); } } @@ -692,10 +692,10 @@ impl State { } /// Reload the app layout when terminal size changes. - pub fn refresh(&mut self, column: u16, row: u16, mut cursor_pos: u16) { + pub fn refresh(&mut self, column: u16, row: u16, mut cursor_pos: u16) -> Result<(), FxError> { let (time_start, name_max) = make_layout(column); - let (original_column, original_row) = terminal_size().unwrap(); + let (original_column, original_row) = terminal_size()?; self.layout.terminal_row = row; self.layout.terminal_column = column; @@ -719,6 +719,7 @@ impl State { } self.redraw(cursor_pos); + Ok(()) } /// Clear all and show the current directory information. From fceb25493f3ee29e8b58c631e19fa558355e6e78 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Wed, 23 Nov 2022 05:47:23 +0900 Subject: [PATCH 08/41] Change sort order(case-insensitive) --- src/state.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/state.rs b/src/state.rs index 5c4e3a61..ae91527f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -857,8 +857,8 @@ impl State { match self.layout.sort_by { SortKey::Name => { - dir_v.sort_by(|a, b| natord::compare(&a.file_name, &b.file_name)); - file_v.sort_by(|a, b| natord::compare(&a.file_name, &b.file_name)); + dir_v.sort_by(|a, b| natord::compare_ignore_case(&a.file_name, &b.file_name)); + file_v.sort_by(|a, b| natord::compare_ignore_case(&a.file_name, &b.file_name)); } SortKey::Time => { dir_v.sort_by(|a, b| b.modified.partial_cmp(&a.modified).unwrap()); From 2b8a48919f26088385cc11f9587d15676ca6053e Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Wed, 23 Nov 2022 05:56:29 +0900 Subject: [PATCH 09/41] Use BTreeMap/Set instead of HashMap/Set --- src/config.rs | 4 ++-- src/functions.rs | 12 ++++++------ src/run.rs | 4 ++-- src/state.rs | 12 ++++++------ 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/config.rs b/src/config.rs index 9408d5dd..2465197b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,7 +2,7 @@ use super::errors::FxError; use super::state::FX_CONFIG_DIR; use serde::Deserialize; -use std::collections::HashMap; +use std::collections::BTreeMap; use std::fs::read_to_string; use std::path::{Path, PathBuf}; @@ -74,7 +74,7 @@ color: #[derive(Deserialize, Debug, Clone)] pub struct Config { pub default: Option, - pub exec: Option>>, + pub exec: Option>>, pub color: ConfigColor, pub syntax_highlight: Option, pub default_theme: Option, diff --git a/src/functions.rs b/src/functions.rs index e2edb556..85935e7f 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -5,7 +5,7 @@ use super::term::*; use crossterm::style::Stylize; use log::{info, warn}; use simplelog::{ConfigBuilder, LevelFilter, WriteLogger}; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet}; use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::time::Duration; @@ -21,7 +21,7 @@ pub fn format_time(time: &Option) -> String { } /// Rename the put file, in order to avoid the name conflict. -pub fn rename_file(file_name: &str, name_set: &HashSet) -> String { +pub fn rename_file(file_name: &str, name_set: &BTreeSet) -> String { let mut count: usize = 1; let (stem, extension) = { let file_name = PathBuf::from(file_name); @@ -53,7 +53,7 @@ pub fn rename_file(file_name: &str, name_set: &HashSet) -> String { } /// Rename the put directory, in order to avoid the name conflict. -pub fn rename_dir(dir_name: &str, name_set: &HashSet) -> String { +pub fn rename_dir(dir_name: &str, name_set: &BTreeSet) -> String { let mut count: usize = 1; let mut new_name = dir_name.to_owned(); while name_set.contains(&new_name) { @@ -137,9 +137,9 @@ pub fn display_count(i: usize, all: usize) -> String { /// Convert extension setting in the config to HashMap. pub fn to_extension_map( - config: &Option>>, -) -> Option> { - let mut new_map = HashMap::new(); + config: &Option>>, +) -> Option> { + let mut new_map = BTreeMap::new(); match config { Some(config) => { for (command, extensions) in config.iter() { diff --git a/src/run.rs b/src/run.rs index eda4ed9d..c97d5b4e 100644 --- a/src/run.rs +++ b/src/run.rs @@ -15,7 +15,7 @@ use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers}; use crossterm::execute; use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen}; use log::{error, info}; -use std::collections::HashSet; +use std::collections::BTreeSet; use std::env::set_current_dir; use std::fmt::Write as _; use std::io::{stdout, Write}; @@ -316,7 +316,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { if let Ok(item) = state.get_item() { let p = item.file_path.clone(); - let mut name_set: HashSet = HashSet::new(); + let mut name_set: BTreeSet = BTreeSet::new(); for item in state.list.iter() { name_set.insert(item.file_name.clone()); diff --git a/src/state.rs b/src/state.rs index ae91527f..bb5539be 100644 --- a/src/state.rs +++ b/src/state.rs @@ -13,8 +13,8 @@ use crossterm::event; use crossterm::event::{KeyCode, KeyEvent}; use crossterm::style::Stylize; use log::{error, info}; -use std::collections::HashMap; -use std::collections::HashSet; +use std::collections::BTreeMap; +use std::collections::BTreeSet; use std::env; use std::ffi::OsStr; use std::fmt::Write as _; @@ -37,7 +37,7 @@ pub struct State { pub current_dir: PathBuf, pub trash_dir: PathBuf, pub default: String, - pub commands: Option>, + pub commands: Option>, pub registered: Vec, pub operations: Operation, pub c_memo: Vec, @@ -425,7 +425,7 @@ impl State { target_dir: Option, ) -> Result<(), FxError> { //make HashSet of file_name - let mut name_set = HashSet::new(); + let mut name_set = BTreeSet::new(); match &target_dir { None => { for item in self.list.iter() { @@ -486,7 +486,7 @@ impl State { &mut self, item: &ItemInfo, target_dir: &Option, - name_set: &mut HashSet, + name_set: &mut BTreeSet, ) -> Result { match target_dir { None => { @@ -537,7 +537,7 @@ impl State { &mut self, buf: &ItemInfo, target_dir: &Option, - name_set: &mut HashSet, + name_set: &mut BTreeSet, ) -> Result { let mut base: usize = 0; let mut target: PathBuf = PathBuf::new(); From d29823f0b68ca1c861fb09fe944f6609c9784454 Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Sun, 27 Nov 2022 06:31:07 +0900 Subject: [PATCH 10/41] Add nix, libc (cfg unix) --- Cargo.lock | 31 +++++++++++++++++++++++++++++++ Cargo.toml | 3 +++ 2 files changed, 34 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 231e67d7..a4d7ce52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -332,9 +332,11 @@ dependencies = [ "crossterm", "dirs", "flate2", + "libc", "log", "lzma-rs", "natord", + "nix", "serde", "serde_yaml", "simplelog", @@ -532,6 +534,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "miniz_oxide" version = "0.5.4" @@ -559,6 +570,20 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c" +[[package]] +name = "nix" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -667,6 +692,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.26" diff --git a/Cargo.toml b/Cargo.toml index 3097b39a..8cf25d83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,9 @@ tar = "0.4.38" flate2 = "1.0.24" lzma-rs = "0.2.0" zstd = "0.11.2" +[target.'cfg(unix)'.dependencies] +nix = {version = "0.25.0", features = ["process"]} +libc = "0.2.137" [dependencies.serde] version = "1.0.136" From dd9e502e1d99cd89491f50ae8efca34ecb9a7144 Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Sun, 27 Nov 2022 06:31:18 +0900 Subject: [PATCH 11/41] Add nix error --- src/errors.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/errors.rs b/src/errors.rs index f2ad63f9..d8b1a3f6 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -17,6 +17,7 @@ pub enum FxError { TooSmallWindowSize, Log(String), Unpack(String), + Nix(String), Panic, } @@ -39,8 +40,9 @@ impl std::fmt::Display for FxError { FxError::RemoveItem(s) => format!("Error: Cannot remove -> {:?}", s), FxError::TooSmallWindowSize => "Error: Too small window size".to_owned(), FxError::Log(s) => s.to_owned(), - FxError::Panic => "Error: felix panicked".to_owned(), FxError::Unpack(s) => s.to_owned(), + FxError::Nix(s) => s.to_owned(), + FxError::Panic => "Error: felix panicked".to_owned(), }; write!(f, "{}", printable) } @@ -86,3 +88,9 @@ impl From for FxError { FxError::Unpack(err.to_string()) } } + +impl From for FxError { + fn from(err: nix::errno::Errno) -> Self { + FxError::Nix(err.to_string()) + } +} From 0376a0854d99804f643783673c78b73e383aec3d Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Sun, 27 Nov 2022 06:35:22 +0900 Subject: [PATCH 12/41] Fix: Use double-fork on linux when opening a new window --- src/state.rs | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/state.rs b/src/state.rs index bb5539be..41b916e5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -13,6 +13,10 @@ use crossterm::event; use crossterm::event::{KeyCode, KeyEvent}; use crossterm::style::Stylize; use log::{error, info}; +use nix::sys::wait::waitpid; +use nix::unistd::fork; +use nix::unistd::setsid; +use nix::unistd::ForkResult; use std::collections::BTreeMap; use std::collections::BTreeSet; use std::env; @@ -22,7 +26,7 @@ use std::fs; #[cfg(target_family = "unix")] use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; -use std::process::{Child, Command, ExitStatus, Stdio}; +use std::process::{Command, ExitStatus, Stdio}; use std::time::UNIX_EPOCH; use syntect::highlighting::{Theme, ThemeSet}; @@ -209,7 +213,7 @@ impl State { } /// Open the selected file in a new window, according to the config. - pub fn open_file_in_new_window(&self) -> Result { + pub fn open_file_in_new_window(&self) -> Result<(), FxError> { let item = self.get_item()?; let path = &item.file_path; let map = &self.commands; @@ -222,12 +226,36 @@ impl State { Some(map) => match extension { Some(extension) => match map.get(extension) { Some(command) => { - let mut ex = Command::new(command); - ex.arg(path) - .stdout(Stdio::null()) - .stdin(Stdio::null()) - .spawn() - .or(Err(FxError::OpenItem)) + if cfg!(target_os = "linux") { + match unsafe { fork() } { + Ok(result) => match result { + ForkResult::Parent { child } => { + waitpid(Some(child), None)?; + Ok(()) + } + ForkResult::Child => { + setsid()?; + let mut ex = Command::new(command); + ex.arg(path) + .stdout(Stdio::null()) + .stdin(Stdio::null()) + .spawn() + .and(Ok(())) + .map_err(|_| FxError::OpenItem)?; + unsafe { libc::_exit(0) }; + } + }, + Err(e) => Err(FxError::Nix(e.to_string())), + } + } else { + let mut ex = Command::new(command); + ex.arg(path) + .stdout(Stdio::null()) + .stdin(Stdio::null()) + .spawn() + .and(Ok(())) + .or(Err(FxError::OpenItem)) + } } None => Err(FxError::OpenNewWindow( "Cannot open this type of item in new window".to_owned(), From e11e1c1fa859000c61ac7fa3e43f20b7c09dc229 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Sun, 27 Nov 2022 14:43:03 +0900 Subject: [PATCH 13/41] Specify target os around open_file_in_new_window --- src/errors.rs | 4 ++- src/state.rs | 99 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 66 insertions(+), 37 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index d8b1a3f6..4a201d2f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -17,8 +17,9 @@ pub enum FxError { TooSmallWindowSize, Log(String), Unpack(String), - Nix(String), Panic, + #[cfg(target_os = "linux")] + Nix(String), } impl std::error::Error for FxError {} @@ -89,6 +90,7 @@ impl From for FxError { } } +#[cfg(target_os = "linux")] impl From for FxError { fn from(err: nix::errno::Errno) -> Self { FxError::Nix(err.to_string()) diff --git a/src/state.rs b/src/state.rs index 41b916e5..3bdb4b5f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -13,23 +13,26 @@ use crossterm::event; use crossterm::event::{KeyCode, KeyEvent}; use crossterm::style::Stylize; use log::{error, info}; -use nix::sys::wait::waitpid; -use nix::unistd::fork; -use nix::unistd::setsid; -use nix::unistd::ForkResult; use std::collections::BTreeMap; use std::collections::BTreeSet; use std::env; use std::ffi::OsStr; use std::fmt::Write as _; use std::fs; -#[cfg(target_family = "unix")] -use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; use std::process::{Command, ExitStatus, Stdio}; use std::time::UNIX_EPOCH; use syntect::highlighting::{Theme, ThemeSet}; +#[cfg(target_os = "linux")] +use nix::sys::wait::waitpid; +use nix::unistd::fork; +use nix::unistd::setsid; +use nix::unistd::ForkResult; + +#[cfg(target_family = "unix")] +use std::os::unix::fs::PermissionsExt; + pub const BEGINNING_ROW: u16 = 3; pub const FX_CONFIG_DIR: &str = "felix"; pub const TRASH: &str = "trash"; @@ -212,6 +215,7 @@ impl State { } } + #[cfg(target_os = "linux")] /// Open the selected file in a new window, according to the config. pub fn open_file_in_new_window(&self) -> Result<(), FxError> { let item = self.get_item()?; @@ -225,37 +229,60 @@ impl State { None => Err(FxError::OpenNewWindow("No exec configuration".to_owned())), Some(map) => match extension { Some(extension) => match map.get(extension) { - Some(command) => { - if cfg!(target_os = "linux") { - match unsafe { fork() } { - Ok(result) => match result { - ForkResult::Parent { child } => { - waitpid(Some(child), None)?; - Ok(()) - } - ForkResult::Child => { - setsid()?; - let mut ex = Command::new(command); - ex.arg(path) - .stdout(Stdio::null()) - .stdin(Stdio::null()) - .spawn() - .and(Ok(())) - .map_err(|_| FxError::OpenItem)?; - unsafe { libc::_exit(0) }; - } - }, - Err(e) => Err(FxError::Nix(e.to_string())), + Some(command) => match unsafe { fork() } { + Ok(result) => match result { + ForkResult::Parent { child } => { + waitpid(Some(child), None)?; + Ok(()) } - } else { - let mut ex = Command::new(command); - ex.arg(path) - .stdout(Stdio::null()) - .stdin(Stdio::null()) - .spawn() - .and(Ok(())) - .or(Err(FxError::OpenItem)) - } + ForkResult::Child => { + setsid()?; + let mut ex = Command::new(command); + ex.arg(path) + .stdout(Stdio::null()) + .stdin(Stdio::null()) + .spawn() + .and(Ok(())) + .map_err(|_| FxError::OpenItem)?; + unsafe { libc::_exit(0) }; + } + }, + Err(e) => Err(FxError::Nix(e.to_string())), + }, + None => Err(FxError::OpenNewWindow( + "Cannot open this type of item in new window".to_owned(), + )), + }, + + None => Err(FxError::OpenNewWindow( + "Cannot open this type of item in new window".to_owned(), + )), + }, + } + } + + #[cfg(not(target_os = "linux"))] + /// Open the selected file in a new window, according to the config. + pub fn open_file_in_new_window(&self) -> Result<(), FxError> { + let item = self.get_item()?; + let path = &item.file_path; + let map = &self.commands; + let extension = &item.file_ext; + + info!("OPEN(new window): {:?}", path); + + match map { + None => Err(FxError::OpenNewWindow("No exec configuration".to_owned())), + Some(map) => match extension { + Some(extension) => match map.get(extension) { + Some(command) => { + let mut ex = Command::new(command); + ex.arg(path) + .stdout(Stdio::null()) + .stdin(Stdio::null()) + .spawn() + .and(Ok(())) + .or(Err(FxError::OpenItem)) } None => Err(FxError::OpenNewWindow( "Cannot open this type of item in new window".to_owned(), From 65d3a3768724452af3db5d3e0d6bac6abf557093 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Sun, 27 Nov 2022 16:35:51 +0900 Subject: [PATCH 14/41] Fix build on macOS and Windows --- src/errors.rs | 3 ++- src/state.rs | 16 +++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 4a201d2f..033d2d29 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -42,8 +42,9 @@ impl std::fmt::Display for FxError { FxError::TooSmallWindowSize => "Error: Too small window size".to_owned(), FxError::Log(s) => s.to_owned(), FxError::Unpack(s) => s.to_owned(), - FxError::Nix(s) => s.to_owned(), FxError::Panic => "Error: felix panicked".to_owned(), + #[cfg(target_os = "linux")] + FxError::Nix(s) => s.to_owned(), }; write!(f, "{}", printable) } diff --git a/src/state.rs b/src/state.rs index 3bdb4b5f..02c8367b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -24,12 +24,6 @@ use std::process::{Command, ExitStatus, Stdio}; use std::time::UNIX_EPOCH; use syntect::highlighting::{Theme, ThemeSet}; -#[cfg(target_os = "linux")] -use nix::sys::wait::waitpid; -use nix::unistd::fork; -use nix::unistd::setsid; -use nix::unistd::ForkResult; - #[cfg(target_family = "unix")] use std::os::unix::fs::PermissionsExt; @@ -229,14 +223,14 @@ impl State { None => Err(FxError::OpenNewWindow("No exec configuration".to_owned())), Some(map) => match extension { Some(extension) => match map.get(extension) { - Some(command) => match unsafe { fork() } { + Some(command) => match unsafe { nix::unistd::fork() } { Ok(result) => match result { - ForkResult::Parent { child } => { - waitpid(Some(child), None)?; + nix::unistd::ForkResult::Parent { child } => { + nix::sys::wait::waitpid(Some(child), None)?; Ok(()) } - ForkResult::Child => { - setsid()?; + nix::unistd::ForkResult::Child => { + nix::unistd::setsid()?; let mut ex = Command::new(command); ex.arg(path) .stdout(Stdio::null()) From 8b2e3278f036030d688001f957ab15b1b9fcd536 Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Mon, 28 Nov 2022 05:31:58 +0900 Subject: [PATCH 15/41] Remove libc, using process::exit() --- Cargo.lock | 1 - Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4d7ce52..44554f73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -332,7 +332,6 @@ dependencies = [ "crossterm", "dirs", "flate2", - "libc", "log", "lzma-rs", "natord", diff --git a/Cargo.toml b/Cargo.toml index 8cf25d83..cf8f9b4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,9 +31,9 @@ tar = "0.4.38" flate2 = "1.0.24" lzma-rs = "0.2.0" zstd = "0.11.2" + [target.'cfg(unix)'.dependencies] nix = {version = "0.25.0", features = ["process"]} -libc = "0.2.137" [dependencies.serde] version = "1.0.136" From e50cdbdc0e435bbac396ec4a11853ea1bd48abe5 Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Mon, 28 Nov 2022 05:32:09 +0900 Subject: [PATCH 16/41] Use drop before exit() --- src/state.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/state.rs b/src/state.rs index 02c8367b..92e6d668 100644 --- a/src/state.rs +++ b/src/state.rs @@ -238,7 +238,8 @@ impl State { .spawn() .and(Ok(())) .map_err(|_| FxError::OpenItem)?; - unsafe { libc::_exit(0) }; + drop(ex); + std::process::exit(0); } }, Err(e) => Err(FxError::Nix(e.to_string())), From 82c6a5499daf515c3b1134e788f0ca4c0964a5bc Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Mon, 28 Nov 2022 17:15:03 +0900 Subject: [PATCH 17/41] Fix: cfg(unix) -> cfg(linux) to avoid build on macOS --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cf8f9b4f..07d8f734 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ flate2 = "1.0.24" lzma-rs = "0.2.0" zstd = "0.11.2" -[target.'cfg(unix)'.dependencies] +[target.'cfg(linux)'.dependencies] nix = {version = "0.25.0", features = ["process"]} [dependencies.serde] From d4f0d8af7f721a27cfcf136c2a8534b58e74a1ea Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Tue, 29 Nov 2022 05:18:09 +0900 Subject: [PATCH 18/41] Fix target in Cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 07d8f734..b70fe720 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ flate2 = "1.0.24" lzma-rs = "0.2.0" zstd = "0.11.2" -[target.'cfg(linux)'.dependencies] +[target.'cfg(target_os = "linux")'.dependencies] nix = {version = "0.25.0", features = ["process"]} [dependencies.serde] From e55519d76b2f6448a88ecc6b8b98819f11573ce8 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Tue, 29 Nov 2022 05:54:34 +0900 Subject: [PATCH 19/41] Update --- Cargo.lock | 60 +++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44554f73..0040f730 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,9 +112,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.76" +version = "1.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" dependencies = [ "jobserver", ] @@ -206,9 +206,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if", ] @@ -250,9 +250,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" +checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" dependencies = [ "cc", "cxxbridge-flags", @@ -262,9 +262,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" +checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" dependencies = [ "cc", "codespan-reporting", @@ -277,15 +277,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" +checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" [[package]] name = "cxxbridge-macro" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" +checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" dependencies = [ "proc-macro2", "quote", @@ -294,9 +294,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", @@ -360,9 +360,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", "miniz_oxide", @@ -436,9 +436,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -544,9 +544,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] @@ -802,18 +802,18 @@ checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" [[package]] name = "serde" -version = "1.0.147" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" dependencies = [ "proc-macro2", "quote", @@ -822,9 +822,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ "itoa", "ryu", @@ -921,9 +921,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" dependencies = [ "proc-macro2", "quote", @@ -1273,9 +1273,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.1+zstd.1.5.2" +version = "2.0.4+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" +checksum = "4fa202f2ef00074143e219d15b62ffc317d17cc33909feac471c044087cad7b0" dependencies = [ "cc", "libc", From 89e7dfdf3904c0fe842264f306ac5150a23ef009 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Tue, 29 Nov 2022 05:55:01 +0900 Subject: [PATCH 20/41] Fix: Show previewed file name with multibyte chars properly --- src/layout.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/layout.rs b/src/layout.rs index 7a114112..6feb0ed9 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -127,11 +127,12 @@ impl Layout { clear_until_newline(); move_right(1); let mut file_name = format!("[{}]", item.file_name); - if file_name.len() > self.preview_space.0.into() { - file_name = file_name - .chars() - .take(self.preview_space.0.into()) - .collect(); + let mut i = self.preview_space.0 as usize; + if file_name.bytes().len() > i { + while !file_name.is_char_boundary(i) { + i -= 1; + } + file_name = file_name.split_at(i).0.to_owned(); } print!("{}", file_name); } From 8c1d75b36b68ca44c5f14def2150242812b4cbe6 Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Tue, 29 Nov 2022 06:05:57 +0900 Subject: [PATCH 21/41] Fix: Show current dir path with multibyte chars properly --- src/state.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/state.rs b/src/state.rs index 92e6d668..790d4421 100644 --- a/src/state.rs +++ b/src/state.rs @@ -782,9 +782,13 @@ impl State { //Show current directory path. //crossterm's Stylize cannot be applied to PathBuf, //current directory does not have any text attribute for now. + let mut i = header_space; let current_dir = self.current_dir.display().to_string(); - if current_dir.len() >= header_space { - let current_dir: String = current_dir.chars().take(header_space).collect(); + if current_dir.bytes().len() >= header_space { + while !current_dir.is_char_boundary(i) { + i -= 1; + } + let current_dir = current_dir.split_at(i).0.to_owned(); set_color(&TermColor::ForeGround(&Colorname::Cyan)); print!(" {}", current_dir); reset_color(); From bbc8cdbac87ff98c4dc110535aec603d20200d2d Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Tue, 29 Nov 2022 06:06:10 +0900 Subject: [PATCH 22/41] Fix: Show item name with multibyte chars properly --- src/state.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/state.rs b/src/state.rs index 790d4421..a9321900 100644 --- a/src/state.rs +++ b/src/state.rs @@ -821,16 +821,16 @@ impl State { /// Print an item in the directory. fn print_item(&self, item: &ItemInfo) { - let chars: Vec = item.file_name.chars().collect(); - let name = if chars.len() > self.layout.name_max_len { - let mut result = chars - .iter() - .take(self.layout.name_max_len - 2) - .collect::(); - result.push_str(".."); - result - } else { + let name = if item.file_name.bytes().len() <= self.layout.name_max_len { item.file_name.clone() + } else { + let mut i = (self.layout.name_max_len - 2) as usize; + while !item.file_name.is_char_boundary(i) { + i -= 1; + } + let mut file_name = item.file_name.split_at(i).0.to_owned(); + file_name.push_str(".."); + file_name }; let time = format_time(&item.modified); let color = match item.file_type { From 15dc659c745f68849818b29aadff8acfdc4087c0 Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Tue, 29 Nov 2022 23:23:30 +0900 Subject: [PATCH 23/41] Add split_str() to avoid wrong new line with wide characters --- src/functions.rs | 15 +++++++++++++++ src/layout.rs | 8 ++------ src/state.rs | 13 +++---------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/functions.rs b/src/functions.rs index 85935e7f..39832f4f 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -318,6 +318,21 @@ pub fn convert_to_permissions(permissions: u32) -> String { permissions.chars().rev().collect() } +///The length of the file name is counted by bytes(), not chars(), +/// because it is possible that if the name contains multibyte characters +/// (sometimes they are wide chars such as CJK), +/// it may exceed the file-name space i.e. header, item list space or preview header. +/// To avoid this, the file name is split by the nearest character boundary (round-off), +/// even if extra space between the name and the modified time may appear. +pub fn split_str(s: &str, i: usize) -> String { + let mut i = i; + while !s.is_char_boundary(i) { + i -= 1; + } + let (result, _) = s.split_at(i); + result.to_owned() +} + //cargo test -- --nocapture #[cfg(test)] mod tests { diff --git a/src/layout.rs b/src/layout.rs index 6feb0ed9..187bfb59 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -127,12 +127,8 @@ impl Layout { clear_until_newline(); move_right(1); let mut file_name = format!("[{}]", item.file_name); - let mut i = self.preview_space.0 as usize; - if file_name.bytes().len() > i { - while !file_name.is_char_boundary(i) { - i -= 1; - } - file_name = file_name.split_at(i).0.to_owned(); + if file_name.bytes().len() > self.preview_space.0 as usize { + file_name = split_str(&file_name, self.preview_space.0 as usize); } print!("{}", file_name); } diff --git a/src/state.rs b/src/state.rs index a9321900..f71bbdd4 100644 --- a/src/state.rs +++ b/src/state.rs @@ -782,13 +782,9 @@ impl State { //Show current directory path. //crossterm's Stylize cannot be applied to PathBuf, //current directory does not have any text attribute for now. - let mut i = header_space; let current_dir = self.current_dir.display().to_string(); if current_dir.bytes().len() >= header_space { - while !current_dir.is_char_boundary(i) { - i -= 1; - } - let current_dir = current_dir.split_at(i).0.to_owned(); + let current_dir = split_str(¤t_dir, header_space); set_color(&TermColor::ForeGround(&Colorname::Cyan)); print!(" {}", current_dir); reset_color(); @@ -824,11 +820,8 @@ impl State { let name = if item.file_name.bytes().len() <= self.layout.name_max_len { item.file_name.clone() } else { - let mut i = (self.layout.name_max_len - 2) as usize; - while !item.file_name.is_char_boundary(i) { - i -= 1; - } - let mut file_name = item.file_name.split_at(i).0.to_owned(); + let i = (self.layout.name_max_len - 2) as usize; + let mut file_name = split_str(&item.file_name, i); file_name.push_str(".."); file_name }; From f436279d2b0920866acab6c81bff076211eb7dec Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Tue, 29 Nov 2022 23:24:14 +0900 Subject: [PATCH 24/41] Use split_str() to avoid breaking preview layout --- src/functions.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/functions.rs b/src/functions.rs index 39832f4f..fe56a5f9 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -201,13 +201,14 @@ pub fn list_up_contents(path: &Path) -> Result, FxError> { } /// Generate the contents tree. -pub fn make_tree(v: Vec) -> Result { +pub fn make_tree(v: Vec, width: usize) -> Result { let len = v.len(); let mut result = String::new(); for (i, path) in v.iter().enumerate() { if i == len - 1 { let mut line = "└ ".to_string(); line.push_str(path); + line = split_str(&line, width); result.push_str(&line); } else { let mut line = "├ ".to_string(); From 7b1d5bf0565dedadc48a1d42cc2d22c28825dad5 Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Tue, 29 Nov 2022 23:27:15 +0900 Subject: [PATCH 25/41] Use bytes() instead of char() to avoid breaking preview layout --- src/functions.rs | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/functions.rs b/src/functions.rs index fe56a5f9..b06220d4 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -221,31 +221,23 @@ pub fn make_tree(v: Vec, width: usize) -> Result { } /// Format texts to print. Used when printing help or text preview. -pub fn format_txt(txt: &str, column: u16, is_help: bool) -> Vec { +pub fn format_txt(txt: &str, width: u16, is_help: bool) -> Vec { let mut v = Vec::new(); - let mut column_count = 0; - let mut line = String::new(); - for c in txt.chars() { - if c == '\n' { - v.push(line); - line = String::new(); - column_count = 0; - continue; - } - line.push(c); - column_count += 1; - if column_count == column { - v.push(line); - line = String::new(); - column_count = 0; - continue; + for line in txt.lines() { + let mut line = line; + while line.bytes().len() > width as usize { + let mut i = width as usize; + while !line.is_char_boundary(i) { + i -= 1; + } + let (first, second) = line.split_at(i); + v.push(first.to_owned()); + line = second; } - } - if !line.is_empty() { - v.push(line); + v.push(line.to_owned()); } if is_help { - v.push("Press Enter to go back.".to_string()); + v.push("Press Enter to go back.".to_owned()); } v } From 75e2131686a045a6fede400c2e64e075b2dba517 Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Tue, 29 Nov 2022 23:28:40 +0900 Subject: [PATCH 26/41] Refactor: Use ref as parameter if possible --- src/functions.rs | 10 ++-------- src/layout.rs | 10 +++++----- src/run.rs | 29 +++++++++-------------------- 3 files changed, 16 insertions(+), 33 deletions(-) diff --git a/src/functions.rs b/src/functions.rs index b06220d4..9f9be4b6 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -362,14 +362,8 @@ mod tests { #[test] fn test_make_tree() { - let v = vec![ - "data".to_string(), - "01.txt".to_string(), - "2.txt".to_string(), - "a.txt".to_string(), - "b.txt".to_string(), - ]; - let tree = make_tree(v).unwrap(); + let v = ["data", "01.txt", "2.txt", "a.txt", "b.txt"]; + let tree = make_tree(v.iter().copied().map(|x| x.to_owned()).collect(), 50).unwrap(); let formatted = format_txt(&tree, 50, false); assert_eq!( tree, diff --git a/src/layout.rs b/src/layout.rs index 187bfb59..42a5a4a2 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -141,7 +141,7 @@ impl Layout { vec![] } }; - self.print_txt_in_preview_area(item, content, false); + self.print_txt_in_preview_area(item, &content, false); } /// Preview text with syntax highlighting. @@ -169,7 +169,7 @@ impl Layout { .iter() .map(|x| as_24_bit_terminal_escaped(x, false)) .collect(); - self.print_txt_in_preview_area(item, result, true); + self.print_txt_in_preview_area(item, &result, true); } else { print!(""); } @@ -186,7 +186,7 @@ impl Layout { Some(p) => list_up_contents(p), }; if let Ok(contents) = contents { - if let Ok(contents) = make_tree(contents) { + if let Ok(contents) = make_tree(contents, self.preview_space.0 as usize) { format_txt(&contents, self.preview_space.0, false) } else { vec![] @@ -196,13 +196,13 @@ impl Layout { } }; - self.print_txt_in_preview_area(item, content, false); + self.print_txt_in_preview_area(item, &content, false); } fn print_txt_in_preview_area( &self, item: &ItemInfo, - content: Vec, + content: &[String], syntex_highlight: bool, ) { match self.split { diff --git a/src/run.rs b/src/run.rs index c97d5b4e..dec2dd31 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1258,7 +1258,6 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { state.layout.terminal_column, true, ); - let help_len = help.clone().len(); print_help(&help, 0, state.layout.terminal_row); screen.flush()?; @@ -1269,25 +1268,15 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { { match code { KeyCode::Char('j') | KeyCode::Down => { - if help_len - < state.layout.terminal_row.into() - || skip - == help_len + 1 - - state.layout.terminal_row - as usize - { - continue; - } else { - clear_all(); - skip += 1; - print_help( - &help, - skip, - state.layout.terminal_row, - ); - screen.flush()?; - continue; - } + clear_all(); + skip += 1; + print_help( + &help, + skip, + state.layout.terminal_row, + ); + screen.flush()?; + continue; } KeyCode::Char('k') | KeyCode::Up => { if skip == 0 { From 1ba9f37ad10e48e98a5d8b5889317fd6bd48e09d Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Wed, 30 Nov 2022 10:30:57 +0900 Subject: [PATCH 27/41] Remove magic number, use const --- src/state.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/state.rs b/src/state.rs index f71bbdd4..7fb6811b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -31,6 +31,7 @@ pub const BEGINNING_ROW: u16 = 3; pub const FX_CONFIG_DIR: &str = "felix"; pub const TRASH: &str = "trash"; pub const EMPTY_WARNING: &str = "Are you sure to empty the trash directory? (if yes: y)"; +const TIME_PREFIX: usize = 11; #[derive(Debug)] pub struct State { @@ -541,7 +542,7 @@ impl State { match target_dir { None => { if item.file_path.parent() == Some(&self.trash_dir) { - let rename: String = item.file_name.chars().skip(11).collect(); + let rename: String = item.file_name.chars().skip(TIME_PREFIX).collect(); let rename = rename_file(&rename, name_set); let to = &self.current_dir.join(&rename); if std::fs::copy(&item.file_path, to).is_err() { @@ -561,7 +562,7 @@ impl State { } Some(path) => { if item.file_path.parent() == Some(&self.trash_dir) { - let rename: String = item.file_name.chars().skip(11).collect(); + let rename: String = item.file_name.chars().skip(TIME_PREFIX).collect(); let rename = rename_file(&rename, name_set); let to = path.join(&rename); if std::fs::copy(&item.file_path, to.clone()).is_err() { @@ -616,7 +617,7 @@ impl State { .parent() .ok_or_else(|| FxError::Io("Cannot read parent dir.".to_string()))?; if parent == &self.trash_dir { - let rename: String = buf.file_name.chars().skip(11).collect(); + let rename: String = buf.file_name.chars().skip(TIME_PREFIX).collect(); target = match &target_dir { None => self.current_dir.join(&rename), Some(path) => path.join(&rename), From be39c5ed6e785abdfa272db2a8218753c94e2a91 Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Wed, 30 Nov 2022 10:31:39 +0900 Subject: [PATCH 28/41] Fix: Remove unnecessary Result --- src/functions.rs | 14 +++++++------- src/layout.rs | 27 +++++++++++---------------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/functions.rs b/src/functions.rs index 9f9be4b6..7781b293 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -201,23 +201,23 @@ pub fn list_up_contents(path: &Path) -> Result, FxError> { } /// Generate the contents tree. -pub fn make_tree(v: Vec, width: usize) -> Result { - let len = v.len(); +pub fn make_tree(v: Vec, width: usize) -> String { let mut result = String::new(); - for (i, path) in v.iter().enumerate() { + let len = v.len(); + for (i, item) in v.iter().enumerate() { if i == len - 1 { let mut line = "└ ".to_string(); - line.push_str(path); + line.push_str(item); line = split_str(&line, width); result.push_str(&line); } else { let mut line = "├ ".to_string(); - line.push_str(path); + line.push_str(item); line.push('\n'); result.push_str(&line); } } - Ok(result) + result } /// Format texts to print. Used when printing help or text preview. @@ -363,7 +363,7 @@ mod tests { #[test] fn test_make_tree() { let v = ["data", "01.txt", "2.txt", "a.txt", "b.txt"]; - let tree = make_tree(v.iter().copied().map(|x| x.to_owned()).collect(), 50).unwrap(); + let tree = make_tree(v.iter().copied().map(|x| x.to_owned()).collect(), 50); let formatted = format_txt(&tree, 50, false); assert_eq!( tree, diff --git a/src/layout.rs b/src/layout.rs index 42a5a4a2..fd191894 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -180,23 +180,18 @@ impl Layout { } fn preview_directory(&self, item: &ItemInfo) { - let content = { - let contents = match &item.symlink_dir_path { - None => list_up_contents(&item.file_path), - Some(p) => list_up_contents(p), - }; - if let Ok(contents) = contents { - if let Ok(contents) = make_tree(contents, self.preview_space.0 as usize) { - format_txt(&contents, self.preview_space.0, false) - } else { - vec![] - } - } else { - vec![] - } + let contents = match &item.symlink_dir_path { + None => list_up_contents(&item.file_path), + Some(p) => list_up_contents(p), }; - - self.print_txt_in_preview_area(item, &content, false); + if let Ok(contents) = contents { + let contents = make_tree(contents, self.preview_space.0 as usize); + self.print_txt_in_preview_area( + item, + &format_txt(&contents, self.preview_space.0, false), + false, + ); + } } fn print_txt_in_preview_area( From 0ec13553cfb0eca343ad7dd41bdeb44b3c6b7dff Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Wed, 30 Nov 2022 10:31:48 +0900 Subject: [PATCH 29/41] Refactor --- src/layout.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/layout.rs b/src/layout.rs index fd191894..3e118dd1 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -13,7 +13,6 @@ use syntect::highlighting::Theme; use syntect::parsing::SyntaxSet; use syntect::util::{as_24_bit_terminal_escaped, split_at, LinesWithEndings}; -/// cf: https://docs.rs/image/latest/src/image/image.rs.html#84-112 pub const MAX_SIZE_TO_PREVIEW: u64 = 1_000_000_000; pub const CHAFA_WARNING: &str = "From v1.1.0, the image preview needs chafa. For more details, please see help by `:h` "; @@ -102,7 +101,7 @@ impl Layout { } Some(PreviewType::Text) => { if self.syntax_highlight { - match self.preview_text_with_sh(item) { + match self.preview_text_with_highlight(item) { Ok(_) => {} Err(e) => { print!("{}", e); @@ -134,18 +133,17 @@ impl Layout { } fn preview_text(&self, item: &ItemInfo) { - let content = { - if let Some(content) = &item.content { - format_txt(content, self.preview_space.0, false) - } else { - vec![] - } - }; - self.print_txt_in_preview_area(item, &content, false); + if let Some(content) = &item.content { + self.print_txt_in_preview_area( + item, + &format_txt(content, self.preview_space.0, false), + false, + ); + } } /// Preview text with syntax highlighting. - fn preview_text_with_sh(&self, item: &ItemInfo) -> Result<(), FxError> { + fn preview_text_with_highlight(&self, item: &ItemInfo) -> Result<(), FxError> { if let Ok(Some(syntax)) = self.syntax_set.find_syntax_for_file(item.file_path.clone()) { let mut h = HighlightLines::new(syntax, &self.theme); if let Some(content) = &item.content { @@ -171,7 +169,6 @@ impl Layout { .collect(); self.print_txt_in_preview_area(item, &result, true); } else { - print!(""); } } else { self.preview_text(item); From 130b1df60db234537ae84ae8e3345504468d6d24 Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Wed, 30 Nov 2022 11:19:46 +0900 Subject: [PATCH 30/41] v2.1.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- config.yaml | 2 +- src/help.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0040f730..224a0a4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -325,7 +325,7 @@ dependencies = [ [[package]] name = "felix" -version = "2.1.0" +version = "2.1.1" dependencies = [ "chrono", "content_inspector", diff --git a/Cargo.toml b/Cargo.toml index b70fe720..874bed81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "felix" -version = "2.1.0" +version = "2.1.1" authors = ["Kyohei Uto "] edition = "2021" description = "tui file manager with vim-like key mapping" diff --git a/config.yaml b/config.yaml index 500d244d..60c4da45 100644 --- a/config.yaml +++ b/config.yaml @@ -1,4 +1,4 @@ -# v2.1.0 +# v2.1.1 # (Optional) # Default exec command when opening files. diff --git a/src/help.rs b/src/help.rs index c946b022..d4911b4b 100644 --- a/src/help.rs +++ b/src/help.rs @@ -1,5 +1,5 @@ /// Help text. -pub const HELP: &str = "# felix v2.1.0 +pub const HELP: &str = "# felix v2.1.1 A simple TUI file manager with vim-like keymapping. ## Usage From 8cad3a811339996191c57c4501dff13cf5771f49 Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Wed, 30 Nov 2022 11:19:58 +0900 Subject: [PATCH 31/41] Update CHANGELOG --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index afc6f9ee..c989cc48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ ## Unreleased +### Fixed + +- By using `fork()` and creating a new process, now you can open a file in a new window on Wayland environment. +- Proper handling of wide characters: Even if an item or a info message includes some wide charatcters such as CJK, the layout won't break anymore. + +### Changed + +- Some refactoring around text-printing in the preview space. + ## v2.1.0 (2022-11-19) ### Added From 48e5d9813c9435deebc69490d8d3fa1fa5b9127b Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Thu, 1 Dec 2022 05:28:44 +0900 Subject: [PATCH 32/41] Refactor --- src/functions.rs | 55 +++++++++++++++++++----------------------------- src/layout.rs | 8 +++---- 2 files changed, 26 insertions(+), 37 deletions(-) diff --git a/src/functions.rs b/src/functions.rs index 7781b293..eccb4360 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -10,7 +10,7 @@ use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::time::Duration; -pub const SPACES: u16 = 3; +pub const PROCESS_INDICATOR_LENGTH: u16 = 7; /// Generate modified time as `String`. pub fn format_time(time: &Option) -> String { @@ -67,11 +67,13 @@ pub fn rename_dir(dir_name: &str, name_set: &BTreeSet) -> String { new_name } +/// Move to information bar. pub fn go_to_and_rest_info() { to_info_bar(); clear_current_line(); } +/// Delele cursor. pub fn delete_cursor() { print!(" "); move_left(1); @@ -84,11 +86,7 @@ pub fn print_info(message: T, then: u16) { info!("{}", message); let (width, _) = terminal_size().unwrap(); - let trimmed: String = message - .to_string() - .chars() - .take((width - 1).into()) - .collect(); + let trimmed = split_str(&message.to_string(), (width - 1).into()); print!("{}", trimmed); hide_cursor(); @@ -104,11 +102,7 @@ pub fn print_warning(message: T, then: u16) { warn!("{}", message); let (width, _) = terminal_size().unwrap(); - let trimmed: String = message - .to_string() - .chars() - .take((width - 1).into()) - .collect(); + let trimmed = split_str(&message.to_string(), (width - 1).into()); set_color(&TermColor::ForeGround(&Colorname::White)); set_color(&TermColor::BackGround(&Colorname::LightRed)); print!("{}", trimmed); @@ -123,7 +117,7 @@ pub fn print_warning(message: T, then: u16) { /// Print process of put/delete. pub fn print_process(message: T) { print!("{}", message); - move_left(7); + move_left(PROCESS_INDICATOR_LENGTH); } /// Print the number of process (put/delete). @@ -135,7 +129,7 @@ pub fn display_count(i: usize, all: usize) -> String { result } -/// Convert extension setting in the config to HashMap. +/// Convert extension setting in the config to BTreeMap. pub fn to_extension_map( config: &Option>>, ) -> Option> { @@ -180,11 +174,11 @@ pub fn to_proper_size(byte: u64) -> String { result } -/// Generate the contents of item to show as a preview. -pub fn list_up_contents(path: &Path) -> Result, FxError> { +/// Generate the contents of the directory to preview. +pub fn list_up_contents(path: &Path, width: u16) -> Result { let mut file_v = Vec::new(); let mut dir_v = Vec::new(); - let mut result = Vec::new(); + let mut v = Vec::new(); for item in std::fs::read_dir(path)? { let item = item?; if item.file_type()?.is_dir() { @@ -195,20 +189,16 @@ pub fn list_up_contents(path: &Path) -> Result, FxError> { } dir_v.sort_by(|a, b| natord::compare(a, b)); file_v.sort_by(|a, b| natord::compare(a, b)); - result.append(&mut dir_v); - result.append(&mut file_v); - Ok(result) -} + v.append(&mut dir_v); + v.append(&mut file_v); -/// Generate the contents tree. -pub fn make_tree(v: Vec, width: usize) -> String { let mut result = String::new(); let len = v.len(); for (i, item) in v.iter().enumerate() { if i == len - 1 { let mut line = "└ ".to_string(); line.push_str(item); - line = split_str(&line, width); + line = split_str(&line, width.into()); result.push_str(&line); } else { let mut line = "├ ".to_string(); @@ -217,7 +207,7 @@ pub fn make_tree(v: Vec, width: usize) -> String { result.push_str(&line); } } - result + Ok(result) } /// Format texts to print. Used when printing help or text preview. @@ -260,10 +250,12 @@ pub fn print_help(v: &[String], skip_number: usize, row: u16) { } } +/// Check if we can edit the file name safely. pub fn is_editable(s: &str) -> bool { s.is_ascii() } +/// Initialize the log if `-l` option is added. pub fn init_log(config_dir_path: &Path) -> Result<(), FxError> { let mut log_name = chrono::Local::now().format("%F-%H-%M-%S").to_string(); log_name.push_str(".log"); @@ -282,6 +274,7 @@ pub fn init_log(config_dir_path: &Path) -> Result<(), FxError> { Ok(()) } +/// Check the latest version of felix. pub fn check_version() -> Result<(), FxError> { let output = std::process::Command::new("cargo") .args(["search", "felix", "--limit", "1"]) @@ -305,6 +298,7 @@ pub fn check_version() -> Result<(), FxError> { Ok(()) } +/// linux-specific: Convert u32 to permission-ish string. pub fn convert_to_permissions(permissions: u32) -> String { let permissions = format!("{permissions:o}"); let permissions: String = permissions.chars().rev().take(3).collect(); @@ -361,15 +355,10 @@ mod tests { } #[test] - fn test_make_tree() { - let v = ["data", "01.txt", "2.txt", "a.txt", "b.txt"]; - let tree = make_tree(v.iter().copied().map(|x| x.to_owned()).collect(), 50); - let formatted = format_txt(&tree, 50, false); - assert_eq!( - tree, - ("├ data\n├ 01.txt\n├ 2.txt\n├ a.txt\n└ b.txt").to_string() - ); - assert_eq!(tree.lines().count(), formatted.len()); + fn test_list_up_contents() { + let p = PathBuf::from("./testfiles"); + let tree = list_up_contents(&p, 20).unwrap(); + assert_eq!(tree, "├ archives\n└ images".to_string()); } #[test] diff --git a/src/layout.rs b/src/layout.rs index 3e118dd1..e3200cc6 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -19,6 +19,7 @@ pub const CHAFA_WARNING: &str = pub const PROPER_WIDTH: u16 = 28; pub const TIME_WIDTH: u16 = 16; +const EXTRA_SPACES: u16 = 3; #[derive(Debug)] pub struct Layout { @@ -178,11 +179,10 @@ impl Layout { fn preview_directory(&self, item: &ItemInfo) { let contents = match &item.symlink_dir_path { - None => list_up_contents(&item.file_path), - Some(p) => list_up_contents(p), + None => list_up_contents(&item.file_path, self.preview_space.0), + Some(p) => list_up_contents(p, self.preview_space.0), }; if let Ok(contents) = contents { - let contents = make_tree(contents, self.preview_space.0 as usize); self.print_txt_in_preview_area( item, &format_txt(&contents, self.preview_space.0, false), @@ -317,7 +317,7 @@ pub fn make_layout(column: u16) -> (u16, usize) { (time_start, name_max) } else { time_start = column - TIME_WIDTH; - name_max = (time_start - SPACES).into(); + name_max = (time_start - EXTRA_SPACES).into(); let required = time_start + TIME_WIDTH - 1; if required > column { let diff = required - column; From a3c40e7cf566a7a2a317ee4ee9d9de9606df002f Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Thu, 1 Dec 2022 05:28:56 +0900 Subject: [PATCH 33/41] Fix expression --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c989cc48..e798dbc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Fixed -- By using `fork()` and creating a new process, now you can open a file in a new window on Wayland environment. +- You can now open a file in a new window on Wayland environment too. - Proper handling of wide characters: Even if an item or a info message includes some wide charatcters such as CJK, the layout won't break anymore. ### Changed From 5cb9b23aeb942912877d2615f7787b0e81407e5b Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Thu, 1 Dec 2022 17:11:17 +0900 Subject: [PATCH 34/41] Refactor: Around unpack() --- src/magic_packed.rs | 236 ++++++++++++++++++++++++-------------------- src/run.rs | 34 ++----- src/state.rs | 19 ++++ 3 files changed, 156 insertions(+), 133 deletions(-) diff --git a/src/magic_packed.rs b/src/magic_packed.rs index f93c6abf..d3684cb5 100644 --- a/src/magic_packed.rs +++ b/src/magic_packed.rs @@ -43,7 +43,7 @@ const HEADER_ZLIB_DEFAULT_COMPRESSION_WITH_PRESET: [u8; 2] = [0x78, 0xBB]; const HEADER_ZLIB_BEST_COMPRESSION_WITH_PRESET: [u8; 2] = [0x78, 0xF9]; #[derive(PartialEq, Eq, Debug)] -enum PackedSignature { +enum CompressionSignature { Gzip, Xz, Zstd, @@ -88,134 +88,134 @@ enum ZlibCompression { BestCompressionWithPreset, } -impl std::fmt::Display for PackedSignature { +impl std::fmt::Display for CompressionSignature { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let printable = match self { - PackedSignature::Gzip => "Gzip", - PackedSignature::Xz => "xz", - PackedSignature::Zstd => "zstd", - PackedSignature::Tar => "tar", - PackedSignature::Pkzip => "zip", - PackedSignature::TarzLZW => "tar(LZW)", - PackedSignature::TarzLZH => "tar(LZH)", - PackedSignature::SevenZ => "7z", - PackedSignature::Lzh0 => "lzh method 0", - PackedSignature::Lzh5 => "lzh method 5", - PackedSignature::Bzip2 => "Bzip2", - PackedSignature::Rnc1 => "rnc ver.1", - PackedSignature::Rnc2 => "rnc ver.2", - PackedSignature::Lzip => "lzip", - PackedSignature::Rar1 => "rar v1.50", - PackedSignature::Rar5 => "rar v5.00", - PackedSignature::SzddQuantum => "Quantum", - PackedSignature::Rsvkdata => "QuickZip rs", - PackedSignature::Ace => "ACE", - PackedSignature::Kwaj => "KWAJ", - PackedSignature::Szdd9x => "SZDD", - PackedSignature::Isz => "ISO", - PackedSignature::Draco => "Google Draco", - PackedSignature::Slob => "Slob", - PackedSignature::DCMPa30 => "Binary Delta", - PackedSignature::Pa30 => "Binary Delta", - PackedSignature::Lzfse => "LZFSE", - PackedSignature::Zlib(_) => "zlib", - PackedSignature::NonArchived => "Non archived", + CompressionSignature::Gzip => "Gzip", + CompressionSignature::Xz => "xz", + CompressionSignature::Zstd => "zstd", + CompressionSignature::Tar => "tar", + CompressionSignature::Pkzip => "zip", + CompressionSignature::TarzLZW => "tar(LZW)", + CompressionSignature::TarzLZH => "tar(LZH)", + CompressionSignature::SevenZ => "7z", + CompressionSignature::Lzh0 => "lzh method 0", + CompressionSignature::Lzh5 => "lzh method 5", + CompressionSignature::Bzip2 => "Bzip2", + CompressionSignature::Rnc1 => "rnc ver.1", + CompressionSignature::Rnc2 => "rnc ver.2", + CompressionSignature::Lzip => "lzip", + CompressionSignature::Rar1 => "rar v1.50", + CompressionSignature::Rar5 => "rar v5.00", + CompressionSignature::SzddQuantum => "Quantum", + CompressionSignature::Rsvkdata => "QuickZip rs", + CompressionSignature::Ace => "ACE", + CompressionSignature::Kwaj => "KWAJ", + CompressionSignature::Szdd9x => "SZDD", + CompressionSignature::Isz => "ISO", + CompressionSignature::Draco => "Google Draco", + CompressionSignature::Slob => "Slob", + CompressionSignature::DCMPa30 => "Binary Delta", + CompressionSignature::Pa30 => "Binary Delta", + CompressionSignature::Lzfse => "LZFSE", + CompressionSignature::Zlib(_) => "zlib", + CompressionSignature::NonArchived => "Non archived", }; write!(f, "{}", printable) } } -fn inspect_packed(p: &Path) -> Result { +fn inspect_compression(p: &Path) -> Result { let mut file = std::fs::File::open(p)?; let mut buffer = [0; 265]; file.read_exact(&mut buffer)?; let sign = if buffer[..2] == HEADER_GZIP { - PackedSignature::Gzip + CompressionSignature::Gzip } else if buffer[..6] == HEADER_XZ { - PackedSignature::Xz + CompressionSignature::Xz } else if buffer[..4] == HEADER_ZSTD { - PackedSignature::Zstd + CompressionSignature::Zstd } else if buffer[257..] == HEADER_TAR1 || buffer[257..] == HEADER_TAR2 { - PackedSignature::Tar + CompressionSignature::Tar } else if buffer[..4] == HEADER_PKZIP { - PackedSignature::Pkzip + CompressionSignature::Pkzip } else if buffer[..2] == HEADER_TARZ_LZW { - PackedSignature::TarzLZW + CompressionSignature::TarzLZW } else if buffer[..2] == HEADER_TARZ_LZH { - PackedSignature::TarzLZH + CompressionSignature::TarzLZH } else if buffer[..6] == HEADER_SEVENZ { - PackedSignature::SevenZ + CompressionSignature::SevenZ } else if buffer[..6] == HEADER_LZH0 { - PackedSignature::Lzh0 + CompressionSignature::Lzh0 } else if buffer[..6] == HEADER_LZH5 { - PackedSignature::Lzh5 + CompressionSignature::Lzh5 } else if buffer[..3] == HEADER_BZ2 { - PackedSignature::Bzip2 + CompressionSignature::Bzip2 } else if buffer[..4] == HEADER_RNC1 { - PackedSignature::Rnc1 + CompressionSignature::Rnc1 } else if buffer[..4] == HEADER_RNC2 { - PackedSignature::Rnc2 + CompressionSignature::Rnc2 } else if buffer[..7] == HEADER_RAR1 { - PackedSignature::Rar1 + CompressionSignature::Rar1 } else if buffer[..4] == HEADER_LZIP { - PackedSignature::Lzip + CompressionSignature::Lzip } else if buffer[..8] == HEADER_RAR5 { - PackedSignature::Rar5 + CompressionSignature::Rar5 } else if buffer[..8] == HEADER_SZDDQUANTUM { - PackedSignature::SzddQuantum + CompressionSignature::SzddQuantum } else if buffer[..8] == HEADER_RSVKDATA { - PackedSignature::Rsvkdata + CompressionSignature::Rsvkdata } else if buffer[..7] == HEADER_ACE { - PackedSignature::Ace + CompressionSignature::Ace } else if buffer[..4] == HEADER_KWAJ { - PackedSignature::Kwaj + CompressionSignature::Kwaj } else if buffer[..4] == HEADER_SZDD9X { - PackedSignature::Szdd9x + CompressionSignature::Szdd9x } else if buffer[..4] == HEADER_ISZ { - PackedSignature::Isz + CompressionSignature::Isz } else if buffer[..5] == HEADER_DRACO { - PackedSignature::Draco + CompressionSignature::Draco } else if buffer[..8] == HEADER_SLOB { - PackedSignature::Slob + CompressionSignature::Slob } else if buffer[..8] == HEADER_DCMPA30 { - PackedSignature::DCMPa30 + CompressionSignature::DCMPa30 } else if buffer[..4] == HEADER_PA30 { - PackedSignature::Pa30 + CompressionSignature::Pa30 } else if buffer[..4] == HEADER_LZFSE { - PackedSignature::Lzfse + CompressionSignature::Lzfse } else if buffer[..2] == HEADER_ZLIB_NO_COMPRESSION_WITHOUT_PRESET { - PackedSignature::Zlib(ZlibCompression::NoCompressionWithoutPreset) + CompressionSignature::Zlib(ZlibCompression::NoCompressionWithoutPreset) } else if buffer[..2] == HEADER_ZLIB_DEFAULT_COMPRESSION_WITHOUT_PRESET { - PackedSignature::Zlib(ZlibCompression::DefaultCompressionWithoutPreset) + CompressionSignature::Zlib(ZlibCompression::DefaultCompressionWithoutPreset) } else if buffer[..2] == HEADER_ZLIB_BEST_SPEED_WITHOUT_PRESET { - PackedSignature::Zlib(ZlibCompression::BestSpeedWithoutPreset) + CompressionSignature::Zlib(ZlibCompression::BestSpeedWithoutPreset) } else if buffer[..2] == HEADER_ZLIB_BEST_COMPRESSION_WITHOUT_PRESET { - PackedSignature::Zlib(ZlibCompression::BestCompressionWithoutPreset) + CompressionSignature::Zlib(ZlibCompression::BestCompressionWithoutPreset) } else if buffer[..2] == HEADER_ZLIB_NO_COMPRESSION_WITH_PRESET { - PackedSignature::Zlib(ZlibCompression::NoCompressionWithPreset) + CompressionSignature::Zlib(ZlibCompression::NoCompressionWithPreset) } else if buffer[..2] == HEADER_ZLIB_DEFAULT_COMPRESSION_WITH_PRESET { - PackedSignature::Zlib(ZlibCompression::DefaultCompressionWithPreset) + CompressionSignature::Zlib(ZlibCompression::DefaultCompressionWithPreset) } else if buffer[..2] == HEADER_ZLIB_BEST_SPEED_WITH_PRESET { - PackedSignature::Zlib(ZlibCompression::BestSpeedWithPreset) + CompressionSignature::Zlib(ZlibCompression::BestSpeedWithPreset) } else if buffer[..2] == HEADER_ZLIB_BEST_COMPRESSION_WITH_PRESET { - PackedSignature::Zlib(ZlibCompression::BestCompressionWithPreset) + CompressionSignature::Zlib(ZlibCompression::BestCompressionWithPreset) } else { - PackedSignature::NonArchived + CompressionSignature::NonArchived }; Ok(sign) } pub fn unpack(p: &Path, dest: &Path) -> Result<(), FxError> { - let sign = inspect_packed(p)?; + let sign = inspect_compression(p)?; match sign { - PackedSignature::Gzip => { + CompressionSignature::Gzip => { let file = std::fs::File::open(p)?; let file = flate2::read::GzDecoder::new(file); let mut archive = tar::Archive::new(file); archive.unpack(dest)?; } - PackedSignature::Xz => { + CompressionSignature::Xz => { let file = std::fs::File::open(p)?; let mut file = std::io::BufReader::new(file); let mut decomp: Vec = Vec::new(); @@ -223,7 +223,7 @@ pub fn unpack(p: &Path, dest: &Path) -> Result<(), FxError> { let mut archive = tar::Archive::new(decomp.as_slice()); archive.unpack(dest)?; } - PackedSignature::Zstd => { + CompressionSignature::Zstd => { let file = std::fs::File::open(p)?; let file = std::io::BufReader::new(file); let decoder = zstd::stream::decode_all(file).unwrap(); @@ -231,38 +231,16 @@ pub fn unpack(p: &Path, dest: &Path) -> Result<(), FxError> { if dest.exists() { std::fs::remove_dir_all(dest)?; } - //Create a new file from the zst file, stripping the extension - let mut new_name = p.with_extension(""); - while new_name.exists() { - let (parent, mut stem, extension) = { - ( - new_name.parent(), - new_name.file_stem().unwrap().to_owned(), - new_name.extension(), - ) - }; - stem.push("+"); - if let Some(ext) = extension { - stem.push("."); - stem.push(ext); - } - if let Some(parent) = parent { - let mut with_p = parent.to_path_buf(); - with_p.push(stem); - new_name = with_p; - } else { - new_name = PathBuf::from(stem); - } - } - std::fs::write(new_name, decoder)?; + let new_path = add_suffix_to_zstd_path(p); + std::fs::write(new_path, decoder)?; } } - PackedSignature::Tar => { + CompressionSignature::Tar => { let file = std::fs::File::open(p)?; let mut archive = tar::Archive::new(file); archive.unpack(dest)?; } - PackedSignature::Pkzip => { + CompressionSignature::Pkzip => { let file = std::fs::File::open(p)?; let mut archive = zip::ZipArchive::new(file)?; archive.extract(dest)?; @@ -273,7 +251,7 @@ pub fn unpack(p: &Path, dest: &Path) -> Result<(), FxError> { // let mut archive = tar::Archive::new(file); // archive.unpack(dest)?; // } - PackedSignature::NonArchived => { + CompressionSignature::NonArchived => { return Err(FxError::Unpack("Seems not an archive file.".to_owned())) } _ => { @@ -287,6 +265,36 @@ pub fn unpack(p: &Path, dest: &Path) -> Result<(), FxError> { Ok(()) } +/// Create a new path from the zst file, stripping the extension +fn add_suffix_to_zstd_path(p: &Path) -> PathBuf { + let mut new_path = p.with_extension(""); + let original_name = new_path.clone(); + let mut count: usize = 1; + while new_path.exists() { + let (parent, mut stem, extension) = { + ( + original_name.parent(), + original_name.file_stem().unwrap_or_default().to_owned(), + original_name.extension(), + ) + }; + + stem.push("_"); + stem.push(count.to_string()); + if let Some(ext) = extension { + stem.push("."); + stem.push(ext); + } + if let Some(parent) = parent { + let mut with_p = parent.to_path_buf(); + with_p.push(stem); + new_path = with_p; + } + count += 1; + } + new_path +} + #[cfg(test)] mod tests { use super::*; @@ -302,48 +310,60 @@ mod tests { /// zip file format and formats based on it(zip, docx, ...) fn test_inspect_signature() { let p = PathBuf::from("testfiles/archives/archive.tar.gz"); - assert_eq!(PackedSignature::Gzip, inspect_packed(&p).unwrap()); + assert_eq!(CompressionSignature::Gzip, inspect_compression(&p).unwrap()); let dest = PathBuf::from("testfiles/archives/gz"); assert!(unpack(&p, &dest).is_ok()); let p = PathBuf::from("testfiles/archives/archive.tar.xz"); - assert_eq!(PackedSignature::Xz, inspect_packed(&p).unwrap()); + assert_eq!(CompressionSignature::Xz, inspect_compression(&p).unwrap()); let dest = PathBuf::from("testfiles/archives/xz"); assert!(unpack(&p, &dest).is_ok()); let p = PathBuf::from("testfiles/archives/archive.tar.zst"); - assert_eq!(PackedSignature::Zstd, inspect_packed(&p).unwrap()); + assert_eq!(CompressionSignature::Zstd, inspect_compression(&p).unwrap()); let dest = PathBuf::from("testfiles/archives/zst"); assert!(unpack(&p, &dest).is_ok()); let p = PathBuf::from("testfiles/archives/archive.txt.zst"); - assert_eq!(PackedSignature::Zstd, inspect_packed(&p).unwrap()); + assert_eq!(CompressionSignature::Zstd, inspect_compression(&p).unwrap()); let dest = PathBuf::from("testfiles/archives/zst_no_tar"); assert!(unpack(&p, &dest).is_ok()); let p = PathBuf::from("testfiles/archives/archive.tar"); - assert_eq!(PackedSignature::Tar, inspect_packed(&p).unwrap()); + assert_eq!(CompressionSignature::Tar, inspect_compression(&p).unwrap()); let dest = PathBuf::from("testfiles/archives/tar"); assert!(unpack(&p, &dest).is_ok()); let p = PathBuf::from("testfiles/archives/archive_bzip2.zip"); - assert_eq!(PackedSignature::Pkzip, inspect_packed(&p).unwrap()); + assert_eq!( + CompressionSignature::Pkzip, + inspect_compression(&p).unwrap() + ); let dest = PathBuf::from("testfiles/archives/bzip2"); assert!(unpack(&p, &dest).is_ok()); let p = PathBuf::from("testfiles/archives/archive_store.zip"); - assert_eq!(PackedSignature::Pkzip, inspect_packed(&p).unwrap()); + assert_eq!( + CompressionSignature::Pkzip, + inspect_compression(&p).unwrap() + ); let dest = PathBuf::from("testfiles/archives/store"); assert!(unpack(&p, &dest).is_ok()); let p = PathBuf::from("testfiles/archives/archive_deflate.zip"); - assert_eq!(PackedSignature::Pkzip, inspect_packed(&p).unwrap()); + assert_eq!( + CompressionSignature::Pkzip, + inspect_compression(&p).unwrap() + ); let dest = PathBuf::from("testfiles/archives/deflate"); assert!(unpack(&p, &dest).is_ok()); //bz2 not available now let p = PathBuf::from("testfiles/archives/archive.tar.bz2"); - assert_eq!(PackedSignature::Bzip2, inspect_packed(&p).unwrap()); + assert_eq!( + CompressionSignature::Bzip2, + inspect_compression(&p).unwrap() + ); let dest = PathBuf::from("testfiles/archives/bz2"); assert!(unpack(&p, &dest).is_err()); } diff --git a/src/run.rs b/src/run.rs index dec2dd31..0754123a 100644 --- a/src/run.rs +++ b/src/run.rs @@ -3,7 +3,6 @@ use super::errors::FxError; use super::functions::*; use super::help::HELP; use super::layout::Split; -use super::magic_packed::unpack; use super::nums::*; use super::op::*; use super::session::*; @@ -15,7 +14,6 @@ use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers}; use crossterm::execute; use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen}; use log::{error, info}; -use std::collections::BTreeSet; use std::env::set_current_dir; use std::fmt::Write as _; use std::io::{stdout, Write}; @@ -313,31 +311,17 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { //Unpack archive file. Fails if it is not an archive file or any of supported types. KeyCode::Char('e') => { - if let Ok(item) = state.get_item() { - let p = item.file_path.clone(); - - let mut name_set: BTreeSet = BTreeSet::new(); - - for item in state.list.iter() { - name_set.insert(item.file_name.clone()); - } - - let dest_name = rename_dir(&item.file_name, &name_set); - let mut dest = state.current_dir.clone(); - dest.push(dest_name); - - print_info("Unpacking...", state.layout.y); - screen.flush()?; - let start = Instant::now(); - if let Err(e) = unpack(&p, &dest) { - state.reload(state.layout.y)?; - print_warning(e, state.layout.y); - continue; - } + print_info("Unpacking...", state.layout.y); + screen.flush()?; + let start = Instant::now(); + if let Err(e) = state.unpack() { state.reload(state.layout.y)?; - let duration = duration_to_string(start.elapsed()); - print_info(format!("Unpacked. [{}]", duration), state.layout.y); + print_warning(e, state.layout.y); + continue; } + let duration = duration_to_string(start.elapsed()); + state.reload(state.layout.y)?; + print_info(format!("Unpacked. [{}]", duration), state.layout.y); } //Jumps to the directory that matches the keyword (zoxide required). diff --git a/src/state.rs b/src/state.rs index 7fb6811b..3535b9cc 100644 --- a/src/state.rs +++ b/src/state.rs @@ -3,6 +3,7 @@ use super::errors::FxError; use super::functions::*; use super::layout::*; use super::magic_image::is_supported_image_type; +use super::magic_packed; use super::nums::*; use super::op::*; use super::session::*; @@ -1253,6 +1254,24 @@ impl State { fs::write(&session_path, serialized)?; Ok(()) } + + /// Unpack/unarchive a file. + pub fn unpack(&self) -> Result<(), FxError> { + let item = self.get_item()?; + let p = item.file_path.clone(); + + let mut name_set: BTreeSet = BTreeSet::new(); + for item in self.list.iter() { + name_set.insert(item.file_name.clone()); + } + + let dest_name = rename_dir(&item.file_name, &name_set); + let mut dest = self.current_dir.clone(); + dest.push(dest_name); + + magic_packed::unpack(&p, &dest)?; + Ok(()) + } } /// Read item information from `std::fs::DirEntry`. From 852a633d779866098172d1330bbac51870172f6c Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Fri, 2 Dec 2022 04:52:38 +0900 Subject: [PATCH 35/41] Refactor --- src/magic_packed.rs | 6 ++---- src/op.rs | 1 + src/state.rs | 10 ++++------ src/term.rs | 18 +++++++----------- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/magic_packed.rs b/src/magic_packed.rs index d3684cb5..480c2225 100644 --- a/src/magic_packed.rs +++ b/src/magic_packed.rs @@ -1,9 +1,7 @@ /// Based on the page of Wikipedia ([List of file signatures - Wikipedia](https://en.wikipedia.org/wiki/List_of_file_signatures)) use super::errors::FxError; -use std::{ - io::Read, - path::{Path, PathBuf}, -}; +use std::io::Read; +use std::path::{Path, PathBuf}; const HEADER_GZIP: [u8; 2] = [0x1F, 0x8B]; const HEADER_XZ: [u8; 6] = [0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00]; diff --git a/src/op.rs b/src/op.rs index 198c98b9..60e7cfa3 100644 --- a/src/op.rs +++ b/src/op.rs @@ -1,4 +1,5 @@ use super::state::ItemInfo; + use log::info; use std::path::PathBuf; diff --git a/src/state.rs b/src/state.rs index 3535b9cc..83fc07c3 100644 --- a/src/state.rs +++ b/src/state.rs @@ -10,12 +10,10 @@ use super::session::*; use super::term::*; use chrono::prelude::*; -use crossterm::event; -use crossterm::event::{KeyCode, KeyEvent}; +use crossterm::event::{Event, KeyCode, KeyEvent}; use crossterm::style::Stylize; use log::{error, info}; -use std::collections::BTreeMap; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::env; use std::ffi::OsStr; use std::fmt::Write as _; @@ -84,8 +82,8 @@ impl State { eprintln!("Cannot read the config file properly.\nError: {}\nDo you want to use the default config? [press Enter to continue]", e); enter_raw_mode(); loop { - match event::read()? { - event::Event::Key(KeyEvent { code, .. }) => match code { + match crossterm::event::read()? { + Event::Key(KeyEvent { code, .. }) => match code { KeyCode::Enter => break, _ => { leave_raw_mode(); diff --git a/src/term.rs b/src/term.rs index ed037c0a..9e536958 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,13 +1,9 @@ -use crate::errors::FxError; - use super::config::Colorname; +use super::errors::FxError; -use crossterm::{ - cursor::{Hide, MoveLeft, MoveRight, MoveTo, Show}, - style::{Color, ResetColor, SetBackgroundColor, SetForegroundColor}, - terminal, - terminal::Clear, -}; +use crossterm::cursor::{Hide, MoveLeft, MoveRight, MoveTo, Show}; +use crossterm::style::{Color, ResetColor, SetBackgroundColor, SetForegroundColor}; +use crossterm::terminal::Clear; pub enum TermColor<'a> { ForeGround(&'a Colorname), @@ -23,17 +19,17 @@ pub enum TermColor<'a> { /// leaving the user with a broken terminal. /// pub fn enter_raw_mode() { - terminal::enable_raw_mode().ok(); + crossterm::terminal::enable_raw_mode().ok(); hide_cursor(); } pub fn leave_raw_mode() { show_cursor(); - terminal::disable_raw_mode().ok(); + crossterm::terminal::disable_raw_mode().ok(); } pub fn terminal_size() -> Result<(u16, u16), FxError> { - terminal::size().map_err(|_| FxError::TerminalSizeDetection) + crossterm::terminal::size().map_err(|_| FxError::TerminalSizeDetection) } pub fn move_to(x: u16, y: u16) { From be5ff95ded0c2c23958a3542ad68d84e8a238f70 Mon Sep 17 00:00:00 2001 From: Kyohei Uto Date: Fri, 2 Dec 2022 05:24:06 +0900 Subject: [PATCH 36/41] Change: More efficient way to reorder the list by mem::take --- src/run.rs | 4 ++-- src/state.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/run.rs b/src/run.rs index 0754123a..cacb4a53 100644 --- a/src/run.rs +++ b/src/run.rs @@ -40,7 +40,7 @@ pub fn run(arg: PathBuf, log: bool) -> Result<(), FxError> { init_log(&config_dir_path)?; } - //Make config file and trash directory if not exist. + //Make config file and trash directory if not exists. make_config_if_not_exists(&config_file_path, &trash_dir_path)?; //If session file, which stores sortkey and whether to show hidden items, does not exist (i.e. first launch), make it. @@ -695,7 +695,7 @@ fn _run(mut state: State, session_path: PathBuf) -> Result<(), FxError> { } } state.layout.nums.reset(); - state.reload(BEGINNING_ROW)?; + state.reorder(BEGINNING_ROW); } // Show/hide hidden files or directories diff --git a/src/state.rs b/src/state.rs index 83fc07c3..28b614d2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -48,7 +48,7 @@ pub struct State { pub rust_log: Option, } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] +#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct ItemInfo { pub file_type: FileType, pub file_name: String, @@ -73,6 +73,12 @@ pub enum FileType { Symlink, } +impl Default for FileType { + fn default() -> Self { + FileType::File + } +} + impl State { /// Initialize the state of the app. pub fn new(p: &std::path::Path) -> Result { @@ -924,6 +930,47 @@ impl State { Ok(()) } + pub fn reorder(&mut self, y: u16) { + self.change_order(); + self.clear_and_show_headline(); + self.list_up(); + self.move_cursor(y); + } + + pub fn change_order(&mut self) { + let mut dir_v = Vec::new(); + let mut file_v = Vec::new(); + let mut result = Vec::with_capacity(self.list.len()); + + for item in self.list.iter_mut() { + if item.file_type == FileType::Directory { + dir_v.push(std::mem::take(item)); + } else { + file_v.push(std::mem::take(item)); + } + } + + match self.layout.sort_by { + SortKey::Name => { + dir_v.sort_by(|a, b| natord::compare_ignore_case(&a.file_name, &b.file_name)); + file_v.sort_by(|a, b| natord::compare_ignore_case(&a.file_name, &b.file_name)); + } + SortKey::Time => { + dir_v.sort_by(|a, b| b.modified.partial_cmp(&a.modified).unwrap()); + file_v.sort_by(|a, b| b.modified.partial_cmp(&a.modified).unwrap()); + } + } + + result.append(&mut dir_v); + result.append(&mut file_v); + + if !self.layout.show_hidden { + result.retain(|x| !x.is_hidden); + } + + self.list = result; + } + /// Reset all item's selected state and exit the select mode. pub fn reset_selection(&mut self) { for mut item in self.list.iter_mut() { From f39e92388275bbe6f06bf8fdbdcd54d76e59af79 Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Fri, 2 Dec 2022 05:45:44 +0900 Subject: [PATCH 37/41] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e798dbc0..b6377360 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,18 @@ ## Unreleased +## v2.1.1 (2022-12-02) + ### Fixed - You can now open a file in a new window on Wayland environment too. - Proper handling of wide characters: Even if an item or a info message includes some wide charatcters such as CJK, the layout won't break anymore. +- Fix cursor color after printing the text preview. ### Changed - Some refactoring around text-printing in the preview space. +- When you change the sort key, felix refresh the list more efficiently than ever by avoiding to read all the items. ## v2.1.0 (2022-11-19) From 155553e55fd8169966b38e7cdf2528642983a54a Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Fri, 2 Dec 2022 05:45:54 +0900 Subject: [PATCH 38/41] v2.1.1 --- README.md | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 0d75a9aa..7cba6af4 100644 --- a/README.md +++ b/README.md @@ -11,24 +11,13 @@ For the detailed document, please see https://kyoheiu.dev/felix. ## New Release -## v2.1.0 (2022-11-19) - -### Added - -![sample_extraction](screenshots/sample_extraction.gif) - -- Feature to unpack archive/compressed file to the current directory. Supported types: `tar.gz`(Gzip), `tar.xz`(lzma), `tar.zst`(Zstandard & tar), `zst`(Zstandard), `tar`, zip file format and formats based on it(`zip`, `docx`, ...). To unpack, press `e` on the item. - - The number of dependencies bumps up to around 150 due to this. +## v2.1.1 (2022-12-02) ### Fixed -- Bug: In the select mode, the selected item was unintentionally canceled when going up/down. -- Delete pointer properly when removing items. -- Instead of panic, return error when `config_dir()` fails. - -### Changed - -- Image file detection: Use magic bytes instead of checking the extension. This will enable to detect image more precisely. +- You can now open a file in a new window on Wayland environment too. +- Proper handling of wide characters: Even if e.g. file name includes some wide charatcters such as CJK, the layout won't break anymore. +- Fix cursor color after printing the text preview. For more details, see `CHANGELOG.md`. @@ -43,13 +32,13 @@ For more details, see `CHANGELOG.md`. _For Windows users: From v1.3.0, it can be at least compiled on Windows (see `.github/workflows/install_test.yml`.) If you're interested, please try and report any problems._ -MSRV(Minimum Supported rustc Version): **1.60.0** - ## Installation _Make sure that `gcc` is installed._ -Update Rust: +MSRV(Minimum Supported rustc Version): **1.60.0** + +Update Rust if rustc < 1.60: ``` rustup update From 55f07ef8909995e150c7dc878cb9310bc665ba49 Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Fri, 2 Dec 2022 05:46:03 +0900 Subject: [PATCH 39/41] Add comments --- src/state.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/state.rs b/src/state.rs index 28b614d2..86c0a6e1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -930,6 +930,7 @@ impl State { Ok(()) } + /// Change (only) the order of the list and print it. pub fn reorder(&mut self, y: u16) { self.change_order(); self.clear_and_show_headline(); @@ -937,6 +938,7 @@ impl State { self.move_cursor(y); } + /// Change the order of the list not reading all the items. pub fn change_order(&mut self) { let mut dir_v = Vec::new(); let mut file_v = Vec::new(); From afdac33f6d04e2e787db2517c7b1a1df1ce3d40f Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Fri, 2 Dec 2022 05:46:21 +0900 Subject: [PATCH 40/41] Clear color setting after printing text preview --- src/layout.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/layout.rs b/src/layout.rs index e3200cc6..c40a82b0 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -211,10 +211,8 @@ impl Layout { } else { set_color(&TermColor::ForeGround(&Colorname::LightBlack)); print!("{}", line); - reset_color(); } if sum == self.preview_space.1 - 1 { - reset_color(); break; } } @@ -232,15 +230,14 @@ impl Layout { } else { set_color(&TermColor::ForeGround(&Colorname::LightBlack)); print!("{}", line); - reset_color(); } if row == self.terminal_row + self.preview_space.1 { - reset_color(); break; } } } } + reset_color(); } /// Print text preview on the right half of the terminal (Experimental). From 6183b614b749f3f6a7883808a87bb02996765c09 Mon Sep 17 00:00:00 2001 From: kyoheiu Date: Fri, 2 Dec 2022 05:51:03 +0900 Subject: [PATCH 41/41] Better installation guide --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7cba6af4..a6bff565 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,10 @@ _For Windows users: From v1.3.0, it can be at least compiled on Windows (see `.g ## Installation -_Make sure that `gcc` is installed._ +### Prerequisites -MSRV(Minimum Supported rustc Version): **1.60.0** +- Make sure that `gcc` is installed. +- MSRV(Minimum Supported rustc Version): **1.60.0** Update Rust if rustc < 1.60: @@ -44,25 +45,27 @@ Update Rust if rustc < 1.60: rustup update ``` -From crates.io: +### From crates.io ``` cargo install felix ``` -From AUR: +### From AUR ``` yay -S felix-rs ``` -On NetBSD, package is available from the official repositories: +### NetBSD + +available from the official repositories: ``` pkgin install felix ``` -From this repository: +### From this repository ``` git clone https://github.com/kyoheiu/felix.git