From d5217f667751ba00997bf1d2620ea531ec61d744 Mon Sep 17 00:00:00 2001 From: har7an <99636919+har7an@users.noreply.github.com> Date: Tue, 26 Mar 2024 06:14:32 +0000 Subject: [PATCH 01/28] cargo: Revert version update on `ansi-to-tui` (#702) which causes custom styling to be lost on the currently selected line. --- Cargo.lock | 12 ++---------- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f86cac..433d022 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,14 +52,12 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "ansi-to-tui" -version = "4.0.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8438af3d7e7dccdb98eff55e5351587d9bec2294daff505fc9a061bd14d22db0" +checksum = "0b0e348dcd256ba06d44d5deabc88a7c0e80ee7303158253ca069bcd9e9b7f57" dependencies = [ "nom", "ratatui", - "simdutf8", - "smallvec", "thiserror", ] @@ -1337,12 +1335,6 @@ dependencies = [ "libc", ] -[[package]] -name = "simdutf8" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" - [[package]] name = "skim" version = "0.10.4" diff --git a/Cargo.toml b/Cargo.toml index 6c50de0..62f1d6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ natord = "1.0.9" anyhow = "1.0.81" serde_yaml = "0.9.33" crossterm = { version = "0.27.0", features = [], default-features = false } -ansi-to-tui = "4.0.1" +ansi-to-tui = "3.1.0" regex = "1.10.3" gethostname = "0.4.3" serde_json = "1.0.114" From 7c6dffc2c6249494166cb624d45f0b598c209a89 Mon Sep 17 00:00:00 2001 From: alice Date: Sat, 6 Apr 2024 08:55:12 +0200 Subject: [PATCH 02/28] cargo: allow building with system lua (#703) useful for distros --- Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 62f1d6f..c412af3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,7 @@ features = ['serde'] [dependencies.mlua] version = "0.9.6" -features = ['luajit', 'vendored', 'serialize', 'send'] +features = ['luajit', 'serialize', 'send'] [dependencies.tui-input] version = "0.8.0" @@ -90,5 +90,7 @@ panic = 'abort' strip = true [features] +default = ["vendored-lua"] +vendored-lua = ["mlua/vendored"] From e834242f5d98bd0049b580088dfed1652abf8347 Mon Sep 17 00:00:00 2001 From: Ahmed ElSamhaa Date: Sun, 7 Apr 2024 23:56:14 +0300 Subject: [PATCH 03/28] Adds vim-like scrolling --- benches/criterion.rs | 2 +- src/app.rs | 45 +++++++++++++++----------- src/directory_buffer.rs | 70 ++++++++++++++++++++++++++++++++++++++--- src/runner.rs | 13 +++++--- src/ui.rs | 27 +++++++++------- 5 files changed, 118 insertions(+), 39 deletions(-) diff --git a/benches/criterion.rs b/benches/criterion.rs index 04d31a4..f9bddee 100644 --- a/benches/criterion.rs +++ b/benches/criterion.rs @@ -121,7 +121,7 @@ fn draw_benchmark(c: &mut Criterion) { c.bench_function("draw on terminal", |b| { b.iter(|| { - terminal.draw(|f| ui::draw(f, &app, &lua)).unwrap(); + terminal.draw(|f| ui::draw(f, &mut app, &lua)).unwrap(); }) }); diff --git a/src/app.rs b/src/app.rs index 49f3b33..3a32cee 100644 --- a/src/app.rs +++ b/src/app.rs @@ -752,7 +752,10 @@ impl App { self.explorer_config.clone(), self.pwd.clone().into(), focus.as_ref().map(PathBuf::from), - self.directory_buffer.as_ref().map(|d| d.focus).unwrap_or(0), + self.directory_buffer + .as_ref() + .map(|d| d.scroll_state.current_focus) + .unwrap_or(0), ) { Ok(dir) => self.set_directory(dir), Err(e) => { @@ -791,7 +794,7 @@ impl App { } } - dir.focus = 0; + dir.scroll_state.set_focus(0); if save_history { if let Some(n) = self.focused_node() { @@ -809,7 +812,7 @@ impl App { history = history.push(n.absolute_path.clone()); } - dir.focus = dir.total.saturating_sub(1); + dir.scroll_state.set_focus(dir.total.saturating_sub(1)); if let Some(n) = dir.focused_node() { self.history = history.push(n.absolute_path.clone()); @@ -822,14 +825,15 @@ impl App { let bounded = self.config.general.enforce_bounded_index_navigation; if let Some(dir) = self.directory_buffer_mut() { - dir.focus = if dir.focus == 0 { + if dir.scroll_state.current_focus == 0 { if bounded { - dir.focus + dir.scroll_state.set_focus(dir.scroll_state.current_focus); } else { - dir.total.saturating_sub(1) + dir.scroll_state.set_focus(dir.total.saturating_sub(1)); } } else { - dir.focus.saturating_sub(1) + dir.scroll_state + .set_focus(dir.scroll_state.current_focus.saturating_sub(1)); }; }; Ok(self) @@ -882,7 +886,8 @@ impl App { history = history.push(n.absolute_path.clone()); } - dir.focus = dir.focus.saturating_sub(index); + dir.scroll_state + .set_focus(dir.scroll_state.current_focus.saturating_sub(index)); if let Some(n) = self.focused_node() { self.history = history.push(n.absolute_path.clone()); } @@ -907,14 +912,15 @@ impl App { let bounded = self.config.general.enforce_bounded_index_navigation; if let Some(dir) = self.directory_buffer_mut() { - dir.focus = if (dir.focus + 1) == dir.total { + if (dir.scroll_state.current_focus + 1) == dir.total { if bounded { - dir.focus + dir.scroll_state.set_focus(dir.scroll_state.current_focus); } else { - 0 + dir.scroll_state.set_focus(0); } } else { - dir.focus + 1 + dir.scroll_state + .set_focus(dir.scroll_state.current_focus + 1); } }; Ok(self) @@ -967,10 +973,12 @@ impl App { history = history.push(n.absolute_path.clone()); } - dir.focus = dir - .focus - .saturating_add(index) - .min(dir.total.saturating_sub(1)); + dir.scroll_state.set_focus( + dir.scroll_state + .current_focus + .saturating_add(index) + .min(dir.total.saturating_sub(1)), + ); if let Some(n) = self.focused_node() { self.history = history.push(n.absolute_path.clone()); @@ -1238,7 +1246,8 @@ impl App { fn focus_by_index(mut self, index: usize) -> Result { let history = self.history.clone(); if let Some(dir) = self.directory_buffer_mut() { - dir.focus = index.min(dir.total.saturating_sub(1)); + dir.scroll_state + .set_focus(index.min(dir.total.saturating_sub(1))); if let Some(n) = self.focused_node() { self.history = history.push(n.absolute_path.clone()); } @@ -1275,7 +1284,7 @@ impl App { history = history.push(n.absolute_path.clone()); } } - dir_buf.focus = focus; + dir_buf.scroll_state.set_focus(focus); if save_history { if let Some(n) = dir_buf.focused_node() { self.history = history.push(n.absolute_path.clone()); diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs index c6e755d..cf6a5c1 100644 --- a/src/directory_buffer.rs +++ b/src/directory_buffer.rs @@ -2,31 +2,93 @@ use crate::node::Node; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct ScrollState { + pub current_focus: usize, + pub last_focus: Option, + pub skipped_rows: usize, +} + +impl ScrollState { + /* The number of visible next lines when scrolling towards either ends of the view port */ + pub const PREVIEW_CUSHION: usize = 3; + + pub fn set_focus(&mut self, current_focus: usize) { + self.last_focus = Some(self.current_focus); + self.current_focus = current_focus; + } + + pub fn calc_skipped_rows(&mut self, height: usize, total: usize) -> usize { + let current_focus = self.current_focus; + let last_focus = self.last_focus; + let first_visible_row = self.skipped_rows; + let start_cushion_row = first_visible_row + ScrollState::PREVIEW_CUSHION; + let end_cushion_row = (first_visible_row + height) + .saturating_sub(ScrollState::PREVIEW_CUSHION + 1); + + let vim_scrolling_enabled = true; + + if !vim_scrolling_enabled { + height * (self.current_focus / height.max(1)) + } else if last_focus == None { + // Just entered the directory + 0 + } else if current_focus == 0 { + 0 + } else if current_focus == total.saturating_sub(1) { + total.saturating_sub(height) + } else if current_focus > last_focus.unwrap() { + // Scrolling down + if current_focus <= end_cushion_row { + first_visible_row + } else if total <= (current_focus + ScrollState::PREVIEW_CUSHION) { + first_visible_row + } else { + (self.current_focus + ScrollState::PREVIEW_CUSHION + 1) + .saturating_sub(height) + } + } else { + // Scrolling up + if current_focus >= start_cushion_row { + first_visible_row + } else if current_focus <= ScrollState::PREVIEW_CUSHION { + 0 + } else { + current_focus.saturating_sub(ScrollState::PREVIEW_CUSHION) + } + } + } +} + #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct DirectoryBuffer { pub parent: String, pub nodes: Vec, pub total: usize, - pub focus: usize, + pub scroll_state: ScrollState, #[serde(skip, default = "now")] pub explored_at: OffsetDateTime, } impl DirectoryBuffer { - pub fn new(parent: String, nodes: Vec, focus: usize) -> Self { + pub fn new(parent: String, nodes: Vec, current_focus: usize) -> Self { let total = nodes.len(); Self { parent, nodes, total, - focus, + scroll_state: ScrollState { + current_focus, + last_focus: None, + skipped_rows: 0, + }, explored_at: now(), } } pub fn focused_node(&self) -> Option<&Node> { - self.nodes.get(self.focus) + self.nodes.get(self.scroll_state.current_focus) } } diff --git a/src/runner.rs b/src/runner.rs index 4b80274..7c15bfc 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -89,7 +89,7 @@ fn call( let focus_index = app .directory_buffer .as_ref() - .map(|d| d.focus) + .map(|d| d.scroll_state.current_focus) .unwrap_or_default() .to_string(); @@ -279,7 +279,10 @@ impl Runner { app.explorer_config.clone(), app.pwd.clone().into(), self.focused_path, - app.directory_buffer.as_ref().map(|d| d.focus).unwrap_or(0), + app.directory_buffer + .as_ref() + .map(|d| d.scroll_state.current_focus) + .unwrap_or(0), tx_msg_in.clone(), ); tx_pwd_watcher.send(app.pwd.clone())?; @@ -430,7 +433,7 @@ impl Runner { .map(|n| n.relative_path.clone().into()), app.directory_buffer .as_ref() - .map(|d| d.focus) + .map(|d| d.scroll_state.current_focus) .unwrap_or(0), tx_msg_in.clone(), ); @@ -445,7 +448,7 @@ impl Runner { .map(|n| n.relative_path.clone().into()), app.directory_buffer .as_ref() - .map(|d| d.focus) + .map(|d| d.scroll_state.current_focus) .unwrap_or(0), tx_msg_in.clone(), ); @@ -493,7 +496,7 @@ impl Runner { } // UI - terminal.draw(|f| ui::draw(f, &app, &lua))?; + terminal.draw(|f| ui::draw(f, &mut app, &lua))?; } EnableMouse => { diff --git a/src/ui.rs b/src/ui.rs index 0d3dda0..838b1ef 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -722,7 +722,7 @@ fn draw_table( f: &mut Frame, screen_size: TuiRect, layout_size: TuiRect, - app: &app::App, + app: &mut app::App, lua: &Lua, ) { let panel_config = &app.config.general.panel_ui; @@ -735,15 +735,16 @@ fn draw_table( let rows = app .directory_buffer - .as_ref() + .as_mut() .map(|dir| { + dir.scroll_state.skipped_rows = dir.scroll_state.calc_skipped_rows(height, dir.total); dir.nodes .iter() .enumerate() - .skip(height * (dir.focus / height.max(1))) + .skip(dir.scroll_state.skipped_rows) .take(height) .map(|(index, node)| { - let is_focused = dir.focus == index; + let is_focused = dir.scroll_state.current_focus == index; let is_selected = app .selection @@ -772,9 +773,13 @@ fn draw_table( let node_type = app_config.node_types.get(node); let (relative_index, is_before_focus, is_after_focus) = - match dir.focus.cmp(&index) { - Ordering::Greater => (dir.focus - index, true, false), - Ordering::Less => (index - dir.focus, false, true), + match dir.scroll_state.current_focus.cmp(&index) { + Ordering::Greater => { + (dir.scroll_state.current_focus - index, true, false) + } + Ordering::Less => { + (index - dir.scroll_state.current_focus, false, true) + } Ordering::Equal => (0, false, false), }; @@ -1284,7 +1289,7 @@ pub fn draw_dynamic( f: &mut Frame, screen_size: TuiRect, layout_size: TuiRect, - app: &app::App, + app: &mut app::App, func: &str, lua: &Lua, ) { @@ -1308,7 +1313,7 @@ pub fn draw_static( f: &mut Frame, screen_size: TuiRect, layout_size: TuiRect, - app: &app::App, + app: &mut app::App, panel: CustomPanel, _lua: &Lua, ) { @@ -1402,7 +1407,7 @@ pub fn draw_layout( f: &mut Frame, screen_size: TuiRect, layout_size: TuiRect, - app: &app::App, + app: &mut app::App, lua: &Lua, ) { match layout { @@ -1493,7 +1498,7 @@ pub fn draw_layout( } } -pub fn draw(f: &mut Frame, app: &app::App, lua: &Lua) { +pub fn draw(f: &mut Frame, app: &mut app::App, lua: &Lua) { let screen_size = f.size(); let layout = app.mode.layout.as_ref().unwrap_or(&app.layout).to_owned(); From 01606e0e6006a9d6ef3492975d573565121d306c Mon Sep 17 00:00:00 2001 From: Ahmed ElSamhaa Date: Mon, 8 Apr 2024 00:14:29 +0300 Subject: [PATCH 04/28] Adds corresponding config setting for vimlike_scrolling --- src/config.rs | 3 +++ src/directory_buffer.rs | 11 +++++++---- src/init.lua | 5 +++++ src/ui.rs | 6 +++++- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/config.rs b/src/config.rs index db08f86..05f9998 100644 --- a/src/config.rs +++ b/src/config.rs @@ -353,6 +353,9 @@ pub struct GeneralConfig { #[serde(default)] pub global_key_bindings: KeyBindings, + + #[serde(default)] + pub vimlike_scrolling: bool, } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs index cf6a5c1..b25798a 100644 --- a/src/directory_buffer.rs +++ b/src/directory_buffer.rs @@ -18,7 +18,12 @@ impl ScrollState { self.current_focus = current_focus; } - pub fn calc_skipped_rows(&mut self, height: usize, total: usize) -> usize { + pub fn calc_skipped_rows( + &mut self, + height: usize, + total: usize, + vimlike_scrolling: bool, + ) -> usize { let current_focus = self.current_focus; let last_focus = self.last_focus; let first_visible_row = self.skipped_rows; @@ -26,9 +31,7 @@ impl ScrollState { let end_cushion_row = (first_visible_row + height) .saturating_sub(ScrollState::PREVIEW_CUSHION + 1); - let vim_scrolling_enabled = true; - - if !vim_scrolling_enabled { + if !vimlike_scrolling { height * (self.current_focus / height.max(1)) } else if last_focus == None { // Just entered the directory diff --git a/src/init.lua b/src/init.lua index a36ddb7..48a5f54 100644 --- a/src/init.lua +++ b/src/init.lua @@ -91,6 +91,11 @@ xplr.config.general.enable_recover_mode = false -- Type: boolean xplr.config.general.hide_remaps_in_help_menu = false +-- Set it to `true` if you want vim-like scrolling. +-- +-- Type: boolean +xplr.config.general.vimlike_scrolling = false + -- Set it to `true` if you want the cursor to stay in the same position when -- the focus is on the first path and you navigate to the previous path -- (by pressing `up`/`k`), or when the focus is on the last path and you diff --git a/src/ui.rs b/src/ui.rs index 838b1ef..2f92c6e 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -737,7 +737,11 @@ fn draw_table( .directory_buffer .as_mut() .map(|dir| { - dir.scroll_state.skipped_rows = dir.scroll_state.calc_skipped_rows(height, dir.total); + dir.scroll_state.skipped_rows = dir.scroll_state.calc_skipped_rows( + height, + dir.total, + app.config.general.vimlike_scrolling, + ); dir.nodes .iter() .enumerate() From 4aa367ca7c33229c314bd2b62a91280384e01a2f Mon Sep 17 00:00:00 2001 From: Ahmed ElSamhaa Date: Mon, 8 Apr 2024 00:26:33 +0300 Subject: [PATCH 05/28] Makes the current_focus field private to limit usage to its setters and getters --- src/app.rs | 18 +++++++++--------- src/directory_buffer.rs | 6 +++++- src/runner.rs | 8 ++++---- src/ui.rs | 8 ++++---- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/app.rs b/src/app.rs index 3a32cee..7c95c1c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -754,7 +754,7 @@ impl App { focus.as_ref().map(PathBuf::from), self.directory_buffer .as_ref() - .map(|d| d.scroll_state.current_focus) + .map(|d| d.scroll_state.get_focus()) .unwrap_or(0), ) { Ok(dir) => self.set_directory(dir), @@ -825,15 +825,15 @@ impl App { let bounded = self.config.general.enforce_bounded_index_navigation; if let Some(dir) = self.directory_buffer_mut() { - if dir.scroll_state.current_focus == 0 { + if dir.scroll_state.get_focus() == 0 { if bounded { - dir.scroll_state.set_focus(dir.scroll_state.current_focus); + dir.scroll_state.set_focus(dir.scroll_state.get_focus()); } else { dir.scroll_state.set_focus(dir.total.saturating_sub(1)); } } else { dir.scroll_state - .set_focus(dir.scroll_state.current_focus.saturating_sub(1)); + .set_focus(dir.scroll_state.get_focus().saturating_sub(1)); }; }; Ok(self) @@ -887,7 +887,7 @@ impl App { } dir.scroll_state - .set_focus(dir.scroll_state.current_focus.saturating_sub(index)); + .set_focus(dir.scroll_state.get_focus().saturating_sub(index)); if let Some(n) = self.focused_node() { self.history = history.push(n.absolute_path.clone()); } @@ -912,15 +912,15 @@ impl App { let bounded = self.config.general.enforce_bounded_index_navigation; if let Some(dir) = self.directory_buffer_mut() { - if (dir.scroll_state.current_focus + 1) == dir.total { + if (dir.scroll_state.get_focus() + 1) == dir.total { if bounded { - dir.scroll_state.set_focus(dir.scroll_state.current_focus); + dir.scroll_state.set_focus(dir.scroll_state.get_focus()); } else { dir.scroll_state.set_focus(0); } } else { dir.scroll_state - .set_focus(dir.scroll_state.current_focus + 1); + .set_focus(dir.scroll_state.get_focus() + 1); } }; Ok(self) @@ -975,7 +975,7 @@ impl App { dir.scroll_state.set_focus( dir.scroll_state - .current_focus + .get_focus() .saturating_add(index) .min(dir.total.saturating_sub(1)), ); diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs index b25798a..5d73ec9 100644 --- a/src/directory_buffer.rs +++ b/src/directory_buffer.rs @@ -4,7 +4,7 @@ use time::OffsetDateTime; #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct ScrollState { - pub current_focus: usize, + current_focus: usize, pub last_focus: Option, pub skipped_rows: usize, } @@ -18,6 +18,10 @@ impl ScrollState { self.current_focus = current_focus; } + pub fn get_focus(&self) -> usize { + self.current_focus + } + pub fn calc_skipped_rows( &mut self, height: usize, diff --git a/src/runner.rs b/src/runner.rs index 7c15bfc..8a96599 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -89,7 +89,7 @@ fn call( let focus_index = app .directory_buffer .as_ref() - .map(|d| d.scroll_state.current_focus) + .map(|d| d.scroll_state.get_focus()) .unwrap_or_default() .to_string(); @@ -281,7 +281,7 @@ impl Runner { self.focused_path, app.directory_buffer .as_ref() - .map(|d| d.scroll_state.current_focus) + .map(|d| d.scroll_state.get_focus()) .unwrap_or(0), tx_msg_in.clone(), ); @@ -433,7 +433,7 @@ impl Runner { .map(|n| n.relative_path.clone().into()), app.directory_buffer .as_ref() - .map(|d| d.scroll_state.current_focus) + .map(|d| d.scroll_state.get_focus()) .unwrap_or(0), tx_msg_in.clone(), ); @@ -448,7 +448,7 @@ impl Runner { .map(|n| n.relative_path.clone().into()), app.directory_buffer .as_ref() - .map(|d| d.scroll_state.current_focus) + .map(|d| d.scroll_state.get_focus()) .unwrap_or(0), tx_msg_in.clone(), ); diff --git a/src/ui.rs b/src/ui.rs index 2f92c6e..2f62610 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -748,7 +748,7 @@ fn draw_table( .skip(dir.scroll_state.skipped_rows) .take(height) .map(|(index, node)| { - let is_focused = dir.scroll_state.current_focus == index; + let is_focused = dir.scroll_state.get_focus() == index; let is_selected = app .selection @@ -777,12 +777,12 @@ fn draw_table( let node_type = app_config.node_types.get(node); let (relative_index, is_before_focus, is_after_focus) = - match dir.scroll_state.current_focus.cmp(&index) { + match dir.scroll_state.get_focus().cmp(&index) { Ordering::Greater => { - (dir.scroll_state.current_focus - index, true, false) + (dir.scroll_state.get_focus() - index, true, false) } Ordering::Less => { - (index - dir.scroll_state.current_focus, false, true) + (index - dir.scroll_state.get_focus(), false, true) } Ordering::Equal => (0, false, false), }; From 87805509c56a9552efefdb9ee94ee3118f5f172a Mon Sep 17 00:00:00 2001 From: Ahmed ElSamhaa Date: Mon, 8 Apr 2024 00:34:57 +0300 Subject: [PATCH 06/28] Refactors the calc_skipped_rows function to make it more readable --- src/directory_buffer.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs index 5d73ec9..e1e3c56 100644 --- a/src/directory_buffer.rs +++ b/src/directory_buffer.rs @@ -31,18 +31,22 @@ impl ScrollState { let current_focus = self.current_focus; let last_focus = self.last_focus; let first_visible_row = self.skipped_rows; + + // Calculate the cushion rows at the start and end of the view port let start_cushion_row = first_visible_row + ScrollState::PREVIEW_CUSHION; let end_cushion_row = (first_visible_row + height) .saturating_sub(ScrollState::PREVIEW_CUSHION + 1); - if !vimlike_scrolling { + let new_skipped_rows = if !vimlike_scrolling { height * (self.current_focus / height.max(1)) } else if last_focus == None { // Just entered the directory 0 } else if current_focus == 0 { + // Focus on first node 0 } else if current_focus == total.saturating_sub(1) { + // Focus on last node total.saturating_sub(height) } else if current_focus > last_focus.unwrap() { // Scrolling down @@ -63,7 +67,9 @@ impl ScrollState { } else { current_focus.saturating_sub(ScrollState::PREVIEW_CUSHION) } - } + }; + + new_skipped_rows } } From fd40de26e7b295e4a1270e8475e21f25a4f625df Mon Sep 17 00:00:00 2001 From: Ahmed ElSamhaa Date: Mon, 8 Apr 2024 00:55:52 +0300 Subject: [PATCH 07/28] Adds tests for the ScrollState calc_skipped_rows fn --- src/directory_buffer.rs | 103 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs index e1e3c56..5bc006a 100644 --- a/src/directory_buffer.rs +++ b/src/directory_buffer.rs @@ -110,3 +110,106 @@ fn now() -> OffsetDateTime { .ok() .unwrap_or_else(OffsetDateTime::now_utc) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_calc_skipped_rows_non_vimlike_scrolling() { + let mut state = ScrollState { + current_focus: 10, + last_focus: Some(8), + skipped_rows: 0, + }; + + let height = 5; + let total = 20; + let vimlike_scrolling = false; + + let result = state.calc_skipped_rows(height, total, vimlike_scrolling); + assert_eq!(result, height * (state.current_focus / height.max(1))); + } + + #[test] + fn test_calc_skipped_rows_entered_directory() { + let mut state = ScrollState { + current_focus: 10, + last_focus: None, + skipped_rows: 0, + }; + + let height = 5; + let total = 20; + let vimlike_scrolling = true; + + let result = state.calc_skipped_rows(height, total, vimlike_scrolling); + assert_eq!(result, 0); + } + + #[test] + fn test_calc_skipped_rows_top_of_directory() { + let mut state = ScrollState { + current_focus: 0, + last_focus: Some(8), + skipped_rows: 5, + }; + + let height = 5; + let total = 20; + let vimlike_scrolling = true; + + let result = state.calc_skipped_rows(height, total, vimlike_scrolling); + assert_eq!(result, 0); + } + + #[test] + fn test_calc_skipped_rows_bottom_of_directory() { + let mut state = ScrollState { + current_focus: 19, + last_focus: Some(18), + skipped_rows: 15, + }; + + let height = 5; + let total = 20; + let vimlike_scrolling = true; + + let result = state.calc_skipped_rows(height, total, vimlike_scrolling); + assert_eq!(result, 15); + } + + #[test] + fn test_calc_skipped_rows_scrolling_down() { + let mut state = ScrollState { + current_focus: 12, + last_focus: Some(10), + skipped_rows: 10, + }; + + let height = 5; + let total = 20; + let vimlike_scrolling = true; + + let result = state.calc_skipped_rows(height, total, vimlike_scrolling); + assert_eq!(result, 11); + } + + #[test] + fn test_calc_skipped_rows_scrolling_up() { + let mut state = ScrollState { + current_focus: 8, + last_focus: Some(10), + skipped_rows: 10, + }; + + let height = 5; + let total = 20; + let vimlike_scrolling = true; + + let result = state.calc_skipped_rows(height, total, vimlike_scrolling); + assert_eq!(result, 5); + } + + // Add more tests for other scenarios... +} From a6fb695ff95f2434556f8902e22fc16bc69a7abd Mon Sep 17 00:00:00 2001 From: Ahmed ElSamhaa Date: Mon, 8 Apr 2024 01:59:54 +0300 Subject: [PATCH 08/28] Refactors the calc_skipped_rows function to make it even more readable --- src/directory_buffer.rs | 41 ++++++++++++++++++++++++++--------------- src/runner.rs | 4 ++-- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs index 5bc006a..3f5fc92 100644 --- a/src/directory_buffer.rs +++ b/src/directory_buffer.rs @@ -1,3 +1,5 @@ +use std::thread::current; + use crate::node::Node; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; @@ -35,7 +37,8 @@ impl ScrollState { // Calculate the cushion rows at the start and end of the view port let start_cushion_row = first_visible_row + ScrollState::PREVIEW_CUSHION; let end_cushion_row = (first_visible_row + height) - .saturating_sub(ScrollState::PREVIEW_CUSHION + 1); + .saturating_sub(ScrollState::PREVIEW_CUSHION + 1) + .min(total.saturating_sub(ScrollState::PREVIEW_CUSHION + 1)); let new_skipped_rows = if !vimlike_scrolling { height * (self.current_focus / height.max(1)) @@ -43,30 +46,38 @@ impl ScrollState { // Just entered the directory 0 } else if current_focus == 0 { - // Focus on first node + // When focus goes to first node 0 } else if current_focus == total.saturating_sub(1) { - // Focus on last node + // When focus goes to last node total.saturating_sub(height) + } else if (start_cushion_row..=end_cushion_row).contains(¤t_focus) { + // IF within cushioned area; do nothing + first_visible_row } else if current_focus > last_focus.unwrap() { - // Scrolling down - if current_focus <= end_cushion_row { - first_visible_row - } else if total <= (current_focus + ScrollState::PREVIEW_CUSHION) { - first_visible_row + // When scrolling down outside the view port + if current_focus > total.saturating_sub(ScrollState::PREVIEW_CUSHION + 1) { + // When focusing the last nodes; always view the full last page + total.saturating_sub(height) } else { - (self.current_focus + ScrollState::PREVIEW_CUSHION + 1) - .saturating_sub(height) + // When scrolling down the view port without reaching the last nodes + current_focus.saturating_sub(height - 1 - ScrollState::PREVIEW_CUSHION) } - } else { - // Scrolling up - if current_focus >= start_cushion_row { - first_visible_row - } else if current_focus <= ScrollState::PREVIEW_CUSHION { + } else if current_focus < last_focus.unwrap() { + // When scrolling up outside the view port + if current_focus < ScrollState::PREVIEW_CUSHION { + // When focusing the first nodes; always view the full first page 0 + } else if current_focus > end_cushion_row { + // When scrolling up around from the last rows; do nothing + first_visible_row } else { + // When scrolling up the view port without reaching the first nodes current_focus.saturating_sub(ScrollState::PREVIEW_CUSHION) } + } else { + // If nothing matches; do nothing + first_visible_row }; new_skipped_rows diff --git a/src/runner.rs b/src/runner.rs index 8a96599..8130bf2 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -27,8 +27,8 @@ use tui::Terminal; use tui_input::Input; pub fn get_tty() -> Result { - let tty = "/dev/tty"; - match fs::OpenOptions::new().read(true).write(true).open(tty) { + let tty = "/dev/stdout"; + match fs::File::create(tty) { Ok(f) => Ok(f), Err(e) => { bail!(format!("could not open {tty}. {e}")) From 5240b3904b87baada756cec3fa5c5a4e58cc89bc Mon Sep 17 00:00:00 2001 From: Ahmed ElSamhaa Date: Mon, 8 Apr 2024 02:05:06 +0300 Subject: [PATCH 09/28] Rolls back changes to the open terminal file --- src/directory_buffer.rs | 4 +--- src/runner.rs | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs index 3f5fc92..fd06a81 100644 --- a/src/directory_buffer.rs +++ b/src/directory_buffer.rs @@ -1,5 +1,3 @@ -use std::thread::current; - use crate::node::Node; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; @@ -52,7 +50,7 @@ impl ScrollState { // When focus goes to last node total.saturating_sub(height) } else if (start_cushion_row..=end_cushion_row).contains(¤t_focus) { - // IF within cushioned area; do nothing + // If within cushioned area; do nothing first_visible_row } else if current_focus > last_focus.unwrap() { // When scrolling down outside the view port diff --git a/src/runner.rs b/src/runner.rs index 8130bf2..8a96599 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -27,8 +27,8 @@ use tui::Terminal; use tui_input::Input; pub fn get_tty() -> Result { - let tty = "/dev/stdout"; - match fs::File::create(tty) { + let tty = "/dev/tty"; + match fs::OpenOptions::new().read(true).write(true).open(tty) { Ok(f) => Ok(f), Err(e) => { bail!(format!("could not open {tty}. {e}")) From 95621af9eb28154f08a9a2218d700fa4b0353b5d Mon Sep 17 00:00:00 2001 From: Ahmed ElSamhaa Date: Mon, 8 Apr 2024 03:16:43 +0300 Subject: [PATCH 10/28] Increases the preview_cushion to 5 like in vim --- src/directory_buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs index fd06a81..39d89f5 100644 --- a/src/directory_buffer.rs +++ b/src/directory_buffer.rs @@ -11,7 +11,7 @@ pub struct ScrollState { impl ScrollState { /* The number of visible next lines when scrolling towards either ends of the view port */ - pub const PREVIEW_CUSHION: usize = 3; + pub const PREVIEW_CUSHION: usize = 5; pub fn set_focus(&mut self, current_focus: usize) { self.last_focus = Some(self.current_focus); From 00bd54abe93049e97170097b5109fb423662a4d5 Mon Sep 17 00:00:00 2001 From: Ahmed ElSamhaa Date: Mon, 8 Apr 2024 19:36:17 +0300 Subject: [PATCH 11/28] Removes unnecessary mut from the calc_skipped_rows fn --- src/directory_buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs index 39d89f5..29aa149 100644 --- a/src/directory_buffer.rs +++ b/src/directory_buffer.rs @@ -23,7 +23,7 @@ impl ScrollState { } pub fn calc_skipped_rows( - &mut self, + &self, height: usize, total: usize, vimlike_scrolling: bool, From 91276f6871eef94fe64a29515dc2e937c2ff62b3 Mon Sep 17 00:00:00 2001 From: Ahmed ElSamhaa Date: Mon, 8 Apr 2024 19:44:18 +0300 Subject: [PATCH 12/28] Removes an unnecessary condition --- src/app.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/app.rs b/src/app.rs index 7c95c1c..ec3cd0f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -826,9 +826,7 @@ impl App { if let Some(dir) = self.directory_buffer_mut() { if dir.scroll_state.get_focus() == 0 { - if bounded { - dir.scroll_state.set_focus(dir.scroll_state.get_focus()); - } else { + if !bounded { dir.scroll_state.set_focus(dir.total.saturating_sub(1)); } } else { @@ -913,9 +911,7 @@ impl App { if let Some(dir) = self.directory_buffer_mut() { if (dir.scroll_state.get_focus() + 1) == dir.total { - if bounded { - dir.scroll_state.set_focus(dir.scroll_state.get_focus()); - } else { + if !bounded { dir.scroll_state.set_focus(0); } } else { From 2a3d056bf1a268c1c5214a36b1b998f8e946b439 Mon Sep 17 00:00:00 2001 From: Ahmed ElSamhaa Date: Mon, 8 Apr 2024 20:04:51 +0300 Subject: [PATCH 13/28] Clarifies some comments --- src/directory_buffer.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs index 29aa149..31cbbe8 100644 --- a/src/directory_buffer.rs +++ b/src/directory_buffer.rs @@ -53,24 +53,24 @@ impl ScrollState { // If within cushioned area; do nothing first_visible_row } else if current_focus > last_focus.unwrap() { - // When scrolling down outside the view port + // When scrolling down the cushioned area if current_focus > total.saturating_sub(ScrollState::PREVIEW_CUSHION + 1) { // When focusing the last nodes; always view the full last page total.saturating_sub(height) } else { - // When scrolling down the view port without reaching the last nodes + // When scrolling down the cushioned area without reaching the last nodes current_focus.saturating_sub(height - 1 - ScrollState::PREVIEW_CUSHION) } } else if current_focus < last_focus.unwrap() { - // When scrolling up outside the view port + // When scrolling up the cushioned area if current_focus < ScrollState::PREVIEW_CUSHION { // When focusing the first nodes; always view the full first page 0 } else if current_focus > end_cushion_row { - // When scrolling up around from the last rows; do nothing + // When scrolling up from the last rows; do nothing first_visible_row } else { - // When scrolling up the view port without reaching the first nodes + // When scrolling up the cushioned area without reaching the first nodes current_focus.saturating_sub(ScrollState::PREVIEW_CUSHION) } } else { From 1600ad9a9c2119490c286c4bfeef6ec8e99112a7 Mon Sep 17 00:00:00 2001 From: Ahmed ElSamhaa Date: Mon, 8 Apr 2024 22:19:34 +0300 Subject: [PATCH 14/28] Makes the preview cushion dynamic now, and sets an initial value 5 for it --- src/directory_buffer.rs | 51 +++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs index 31cbbe8..b9764bf 100644 --- a/src/directory_buffer.rs +++ b/src/directory_buffer.rs @@ -7,11 +7,11 @@ pub struct ScrollState { current_focus: usize, pub last_focus: Option, pub skipped_rows: usize, + /* The number of visible next lines when scrolling towards either ends of the view port */ + pub initial_preview_cushion: usize, } impl ScrollState { - /* The number of visible next lines when scrolling towards either ends of the view port */ - pub const PREVIEW_CUSHION: usize = 5; pub fn set_focus(&mut self, current_focus: usize) { self.last_focus = Some(self.current_focus); @@ -28,15 +28,25 @@ impl ScrollState { total: usize, vimlike_scrolling: bool, ) -> usize { + let preview_cushion = if height >= self.initial_preview_cushion * 3 { + self.initial_preview_cushion + } else if height >= 9 { + 3 + } else if height >= 3 { + 1 + } else { + 0 + }; + let current_focus = self.current_focus; let last_focus = self.last_focus; let first_visible_row = self.skipped_rows; // Calculate the cushion rows at the start and end of the view port - let start_cushion_row = first_visible_row + ScrollState::PREVIEW_CUSHION; + let start_cushion_row = first_visible_row + preview_cushion; let end_cushion_row = (first_visible_row + height) - .saturating_sub(ScrollState::PREVIEW_CUSHION + 1) - .min(total.saturating_sub(ScrollState::PREVIEW_CUSHION + 1)); + .saturating_sub(preview_cushion + 1) + .min(total.saturating_sub(preview_cushion + 1)); let new_skipped_rows = if !vimlike_scrolling { height * (self.current_focus / height.max(1)) @@ -54,16 +64,16 @@ impl ScrollState { first_visible_row } else if current_focus > last_focus.unwrap() { // When scrolling down the cushioned area - if current_focus > total.saturating_sub(ScrollState::PREVIEW_CUSHION + 1) { + if current_focus > total.saturating_sub(preview_cushion + 1) { // When focusing the last nodes; always view the full last page total.saturating_sub(height) } else { // When scrolling down the cushioned area without reaching the last nodes - current_focus.saturating_sub(height - 1 - ScrollState::PREVIEW_CUSHION) + current_focus.saturating_sub(height.saturating_sub(preview_cushion + 1)) } } else if current_focus < last_focus.unwrap() { // When scrolling up the cushioned area - if current_focus < ScrollState::PREVIEW_CUSHION { + if current_focus < preview_cushion { // When focusing the first nodes; always view the full first page 0 } else if current_focus > end_cushion_row { @@ -71,7 +81,7 @@ impl ScrollState { first_visible_row } else { // When scrolling up the cushioned area without reaching the first nodes - current_focus.saturating_sub(ScrollState::PREVIEW_CUSHION) + current_focus.saturating_sub(preview_cushion) } } else { // If nothing matches; do nothing @@ -104,6 +114,7 @@ impl DirectoryBuffer { current_focus, last_focus: None, skipped_rows: 0, + initial_preview_cushion: 5, }, explored_at: now(), } @@ -126,10 +137,11 @@ mod tests { #[test] fn test_calc_skipped_rows_non_vimlike_scrolling() { - let mut state = ScrollState { + let state = ScrollState { current_focus: 10, last_focus: Some(8), skipped_rows: 0, + initial_preview_cushion: 5, }; let height = 5; @@ -142,10 +154,11 @@ mod tests { #[test] fn test_calc_skipped_rows_entered_directory() { - let mut state = ScrollState { + let state = ScrollState { current_focus: 10, last_focus: None, skipped_rows: 0, + initial_preview_cushion: 5, }; let height = 5; @@ -158,10 +171,11 @@ mod tests { #[test] fn test_calc_skipped_rows_top_of_directory() { - let mut state = ScrollState { + let state = ScrollState { current_focus: 0, last_focus: Some(8), skipped_rows: 5, + initial_preview_cushion: 5, }; let height = 5; @@ -174,10 +188,11 @@ mod tests { #[test] fn test_calc_skipped_rows_bottom_of_directory() { - let mut state = ScrollState { + let state = ScrollState { current_focus: 19, last_focus: Some(18), skipped_rows: 15, + initial_preview_cushion: 5, }; let height = 5; @@ -190,10 +205,11 @@ mod tests { #[test] fn test_calc_skipped_rows_scrolling_down() { - let mut state = ScrollState { + let state = ScrollState { current_focus: 12, last_focus: Some(10), skipped_rows: 10, + initial_preview_cushion: 5, }; let height = 5; @@ -201,15 +217,16 @@ mod tests { let vimlike_scrolling = true; let result = state.calc_skipped_rows(height, total, vimlike_scrolling); - assert_eq!(result, 11); + assert_eq!(result, 10); } #[test] fn test_calc_skipped_rows_scrolling_up() { - let mut state = ScrollState { + let state = ScrollState { current_focus: 8, last_focus: Some(10), skipped_rows: 10, + initial_preview_cushion: 5, }; let height = 5; @@ -217,7 +234,7 @@ mod tests { let vimlike_scrolling = true; let result = state.calc_skipped_rows(height, total, vimlike_scrolling); - assert_eq!(result, 5); + assert_eq!(result, 7); } // Add more tests for other scenarios... From 96ffe8680b7e4b55005933b3ba9be389d8a8cd20 Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Wed, 10 Apr 2024 12:38:54 +0530 Subject: [PATCH 15/28] Fix ScrollUpHalf --- src/runner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runner.rs b/src/runner.rs index 4b80274..d4583a8 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -411,7 +411,7 @@ impl Runner { } ScrollUpHalf => { - app = app.focus_next_by_relative_index( + app = app.focus_previous_by_relative_index( terminal.size()?.height as usize / 2, )?; } From 96da7e1da81211a07ad4c44be6a1c07c1a893700 Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Wed, 10 Apr 2024 13:02:05 +0530 Subject: [PATCH 16/28] Fix linting --- src/app.rs | 3 +-- src/directory_buffer.rs | 9 +++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/app.rs b/src/app.rs index ec3cd0f..19e3268 100644 --- a/src/app.rs +++ b/src/app.rs @@ -915,8 +915,7 @@ impl App { dir.scroll_state.set_focus(0); } } else { - dir.scroll_state - .set_focus(dir.scroll_state.get_focus() + 1); + dir.scroll_state.set_focus(dir.scroll_state.get_focus() + 1); } }; Ok(self) diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs index b9764bf..aa60eeb 100644 --- a/src/directory_buffer.rs +++ b/src/directory_buffer.rs @@ -12,7 +12,6 @@ pub struct ScrollState { } impl ScrollState { - pub fn set_focus(&mut self, current_focus: usize) { self.last_focus = Some(self.current_focus); self.current_focus = current_focus; @@ -48,9 +47,9 @@ impl ScrollState { .saturating_sub(preview_cushion + 1) .min(total.saturating_sub(preview_cushion + 1)); - let new_skipped_rows = if !vimlike_scrolling { + if !vimlike_scrolling { height * (self.current_focus / height.max(1)) - } else if last_focus == None { + } else if last_focus.is_none() { // Just entered the directory 0 } else if current_focus == 0 { @@ -86,9 +85,7 @@ impl ScrollState { } else { // If nothing matches; do nothing first_visible_row - }; - - new_skipped_rows + } } } From 976530ba708abd7038de4fd113b3ec1c8bec9d6f Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Wed, 10 Apr 2024 13:02:49 +0530 Subject: [PATCH 17/28] Gen docs --- docs/en/src/general-config.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/en/src/general-config.md b/docs/en/src/general-config.md index 75fa861..a986348 100644 --- a/docs/en/src/general-config.md +++ b/docs/en/src/general-config.md @@ -42,6 +42,12 @@ Set it to `true` if you want to hide all remaps in the help menu. Type: boolean +#### xplr.config.general.vimlike_scrolling + +Set it to `true` if you want vim-like scrolling. + +Type: boolean + #### xplr.config.general.enforce_bounded_index_navigation Set it to `true` if you want the cursor to stay in the same position when From 6fb0781fe40533fa1b3ec1a23ca8d8d396c30d9f Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Wed, 1 May 2024 11:15:37 +0530 Subject: [PATCH 18/28] xplr.util.lscolor shouldn't return nil Closes: https://github.com/sayanarijit/xplr/issues/705 Also update xplr version. --- Cargo.lock | 2 +- Cargo.toml | 2 +- docs/en/src/upgrade-guide.md | 7 +++++-- docs/en/src/xplr.util.md | 2 +- src/init.lua | 6 +++--- src/lua/mod.rs | 12 ++++++------ src/lua/util.rs | 7 +++++-- 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 433d022..4ef9bdd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1943,7 +1943,7 @@ checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" [[package]] name = "xplr" -version = "0.21.7" +version = "0.21.8" dependencies = [ "ansi-to-tui", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index c412af3..5f552be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ path = './benches/criterion.rs' [package] name = 'xplr' -version = '0.21.7' +version = '0.21.8' authors = ['Arijit Basu '] edition = '2021' description = 'A hackable, minimal, fast TUI file explorer' diff --git a/docs/en/src/upgrade-guide.md b/docs/en/src/upgrade-guide.md index 54bb05c..38d5347 100644 --- a/docs/en/src/upgrade-guide.md +++ b/docs/en/src/upgrade-guide.md @@ -45,7 +45,7 @@ compatibility. ### Instructions -#### [v0.20.2][48] -> [v0.21.7][49] +#### [v0.20.2][48] -> [v0.21.8][49] - Some plugins might stop rendering colors. Wait for them to update. - Rename `xplr.config.general.sort_and_filter_ui.search_identifier` to @@ -127,6 +127,9 @@ compatibility. - You can use `c` and `m` keys in default mode to quickly copy and move focused or selected files, without having to change directory. - Use `xplr.util.debug()` to debug lua values. +- Since v0.21.8: + - You can set `xplr.config.general.vimlike_scrolling = true` to enable + vim-like scrolling. Thanks to @noahmayr for contributing to a major part of this release. @@ -525,5 +528,5 @@ Else do the following: [46]: https://github.com/sayanarijit/xplr/releases/tag/v0.18.0 [47]: https://github.com/sayanarijit/xplr/releases/tag/v0.19.4 [48]: https://github.com/sayanarijit/xplr/releases/tag/v0.20.2 -[49]: https://github.com/sayanarijit/xplr/releases/tag/v0.21.7 +[49]: https://github.com/sayanarijit/xplr/releases/tag/v0.21.8 [50]: https://github.com/lotabout/skim#search-syntax diff --git a/docs/en/src/xplr.util.md b/docs/en/src/xplr.util.md index fed14d0..7ef601c 100644 --- a/docs/en/src/xplr.util.md +++ b/docs/en/src/xplr.util.md @@ -397,7 +397,7 @@ xplr.util.to_yaml({ foo = "bar" }) Get a [Style][3] object for the given path based on the LS_COLORS environment variable. -Type: function( path:string ) -> [Style][3]|nil +Type: function( path:string ) -> [Style][3] Example: diff --git a/src/init.lua b/src/init.lua index 48a5f54..903a651 100644 --- a/src/init.lua +++ b/src/init.lua @@ -3118,8 +3118,8 @@ xplr.fn.builtin.fmt_general_selection_item = function(n) if n.is_dir then shortened = shortened .. "/" end - local ls_style = xplr.util.lscolor(n.absolute_path) local meta_style = xplr.util.node_type(n).style + local ls_style = xplr.util.lscolor(n.absolute_path) local style = xplr.util.style_mix({ ls_style, meta_style }) return xplr.util.paint(shortened:gsub("\n", nl), style) end @@ -3142,8 +3142,8 @@ end xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m) local nl = xplr.util.paint("\\n", { add_modifiers = { "Italic", "Dim" } }) local r = m.tree .. m.prefix - local style = xplr.util.lscolor(m.absolute_path) - style = xplr.util.style_mix({ style, m.style }) + local ls_style = xplr.util.lscolor(m.absolute_path) + local style = xplr.util.style_mix({ ls_style, m.style }) if m.meta.icon == nil then r = r .. "" diff --git a/src/lua/mod.rs b/src/lua/mod.rs index 49c2640..467a3bf 100644 --- a/src/lua/mod.rs +++ b/src/lua/mod.rs @@ -160,24 +160,24 @@ mod tests { assert!(check_version(VERSION, "foo path").is_ok()); // Current release if OK - assert!(check_version("0.21.7", "foo path").is_ok()); + assert!(check_version("0.21.8", "foo path").is_ok()); // Prev major release is ERR // - Not yet // Prev minor release is ERR (Change when we get to v1) - assert!(check_version("0.20.7", "foo path").is_err()); + assert!(check_version("0.20.8", "foo path").is_err()); // Prev bugfix release is OK - assert!(check_version("0.21.6", "foo path").is_ok()); + assert!(check_version("0.21.7", "foo path").is_ok()); // Next major release is ERR - assert!(check_version("1.20.7", "foo path").is_err()); + assert!(check_version("1.20.8", "foo path").is_err()); // Next minor release is ERR - assert!(check_version("0.22.7", "foo path").is_err()); + assert!(check_version("0.22.8", "foo path").is_err()); // Next bugfix release is ERR (Change when we get to v1) - assert!(check_version("0.21.8", "foo path").is_err()); + assert!(check_version("0.21.9", "foo path").is_err()); } } diff --git a/src/lua/util.rs b/src/lua/util.rs index da01f3c..63abbe9 100644 --- a/src/lua/util.rs +++ b/src/lua/util.rs @@ -654,7 +654,7 @@ pub fn to_yaml<'a>(util: Table<'a>, lua: &Lua) -> Result> { /// Get a [Style][3] object for the given path based on the LS_COLORS /// environment variable. /// -/// Type: function( path:string ) -> [Style][3]|nil +/// Type: function( path:string ) -> [Style][3] /// /// Example: /// @@ -664,7 +664,10 @@ pub fn to_yaml<'a>(util: Table<'a>, lua: &Lua) -> Result> { /// ``` pub fn lscolor<'a>(util: Table<'a>, lua: &Lua) -> Result> { let func = lua.create_function(move |lua, path: String| { - let style = LS_COLORS.style_for_path(path).map(Style::from); + let style = LS_COLORS + .style_for_path(path) + .map(Style::from) + .unwrap_or_default(); lua::serialize(lua, &style).map_err(LuaError::custom) })?; util.set("lscolor", func)?; From ce52bcdf943ccc1d41afc8f4f3508954e695a215 Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Wed, 1 May 2024 13:55:51 +0530 Subject: [PATCH 19/28] Revert vimlike scrolling Use stateful ui widget. --- src/app.rs | 74 +- src/compat.rs | 27 +- src/config.rs | 14 +- src/directory_buffer.rs | 159 +++-- src/dirs.rs | 2 +- src/explorer.rs | 51 +- src/input.rs | 2 +- src/runner.rs | 26 +- src/ui.rs | 1449 +++++++++++++++++++-------------------- 9 files changed, 895 insertions(+), 909 deletions(-) diff --git a/src/app.rs b/src/app.rs index 19e3268..dd521d2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -335,12 +335,12 @@ impl App { &config .general .initial_mode - .to_owned() + .clone() .unwrap_or_else(|| "default".into()), ) { Some(m) => m.clone().sanitized( config.general.read_only, - config.general.global_key_bindings.to_owned(), + config.general.global_key_bindings.clone(), ), None => { bail!("'default' mode is missing") @@ -351,7 +351,7 @@ impl App { &config .general .initial_layout - .to_owned() + .clone() .unwrap_or_else(|| "default".into()), ) { Some(l) => l.clone(), @@ -752,10 +752,8 @@ impl App { self.explorer_config.clone(), self.pwd.clone().into(), focus.as_ref().map(PathBuf::from), - self.directory_buffer - .as_ref() - .map(|d| d.scroll_state.get_focus()) - .unwrap_or(0), + self.directory_buffer.as_ref().map(|d| d.focus).unwrap_or(0), + self.config.general.vimlike_scrolling, ) { Ok(dir) => self.set_directory(dir), Err(e) => { @@ -794,7 +792,7 @@ impl App { } } - dir.scroll_state.set_focus(0); + dir.focus = 0; if save_history { if let Some(n) = self.focused_node() { @@ -812,7 +810,7 @@ impl App { history = history.push(n.absolute_path.clone()); } - dir.scroll_state.set_focus(dir.total.saturating_sub(1)); + dir.focus = dir.total.saturating_sub(1); if let Some(n) = dir.focused_node() { self.history = history.push(n.absolute_path.clone()); @@ -823,15 +821,15 @@ impl App { fn focus_previous(mut self) -> Result { let bounded = self.config.general.enforce_bounded_index_navigation; - if let Some(dir) = self.directory_buffer_mut() { - if dir.scroll_state.get_focus() == 0 { - if !bounded { - dir.scroll_state.set_focus(dir.total.saturating_sub(1)); + dir.focus = if dir.focus == 0 { + if bounded { + dir.focus + } else { + dir.total.saturating_sub(1) } } else { - dir.scroll_state - .set_focus(dir.scroll_state.get_focus().saturating_sub(1)); + dir.focus.saturating_sub(1) }; }; Ok(self) @@ -884,8 +882,7 @@ impl App { history = history.push(n.absolute_path.clone()); } - dir.scroll_state - .set_focus(dir.scroll_state.get_focus().saturating_sub(index)); + dir.focus = dir.focus.saturating_sub(index); if let Some(n) = self.focused_node() { self.history = history.push(n.absolute_path.clone()); } @@ -908,16 +905,18 @@ impl App { fn focus_next(mut self) -> Result { let bounded = self.config.general.enforce_bounded_index_navigation; - if let Some(dir) = self.directory_buffer_mut() { - if (dir.scroll_state.get_focus() + 1) == dir.total { - if !bounded { - dir.scroll_state.set_focus(0); + dir.focus = if (dir.focus + 1) == dir.total { + if bounded { + dir.focus + } else { + 0 } } else { - dir.scroll_state.set_focus(dir.scroll_state.get_focus() + 1); + dir.focus + 1 } }; + Ok(self) } @@ -968,12 +967,10 @@ impl App { history = history.push(n.absolute_path.clone()); } - dir.scroll_state.set_focus( - dir.scroll_state - .get_focus() - .saturating_add(index) - .min(dir.total.saturating_sub(1)), - ); + dir.focus = dir + .focus + .saturating_add(index) + .min(dir.total.saturating_sub(1)); if let Some(n) = self.focused_node() { self.history = history.push(n.absolute_path.clone()); @@ -998,7 +995,7 @@ impl App { fn follow_symlink(self) -> Result { if let Some(pth) = self .focused_node() - .and_then(|n| n.symlink.to_owned().map(|s| s.absolute_path)) + .and_then(|n| n.symlink.clone().map(|s| s.absolute_path)) { self.focus_path(&pth, true) } else { @@ -1241,8 +1238,7 @@ impl App { fn focus_by_index(mut self, index: usize) -> Result { let history = self.history.clone(); if let Some(dir) = self.directory_buffer_mut() { - dir.scroll_state - .set_focus(index.min(dir.total.saturating_sub(1))); + dir.focus = index.min(dir.total.saturating_sub(1)); if let Some(n) = self.focused_node() { self.history = history.push(n.absolute_path.clone()); } @@ -1279,7 +1275,7 @@ impl App { history = history.push(n.absolute_path.clone()); } } - dir_buf.scroll_state.set_focus(focus); + dir_buf.focus = focus; if save_history { if let Some(n) = dir_buf.focused_node() { self.history = history.push(n.absolute_path.clone()); @@ -1386,7 +1382,7 @@ impl App { self = self.push_mode(); self.mode = mode.sanitized( self.config.general.read_only, - self.config.general.global_key_bindings.to_owned(), + self.config.general.global_key_bindings.clone(), ); // Hooks @@ -1411,7 +1407,7 @@ impl App { self = self.push_mode(); self.mode = mode.sanitized( self.config.general.read_only, - self.config.general.global_key_bindings.to_owned(), + self.config.general.global_key_bindings.clone(), ); // Hooks @@ -1438,7 +1434,7 @@ impl App { fn switch_layout_builtin(mut self, layout: &str) -> Result { if let Some(l) = self.config.layouts.builtin.get(layout) { - self.layout = l.to_owned(); + self.layout = l.clone(); // Hooks if !self.hooks.on_layout_switch.is_empty() { @@ -1454,7 +1450,7 @@ impl App { fn switch_layout_custom(mut self, layout: &str) -> Result { if let Some(l) = self.config.layouts.get_custom(layout) { - self.layout = l.to_owned(); + self.layout = l.clone(); // Hooks if !self.hooks.on_layout_switch.is_empty() { @@ -1579,7 +1575,7 @@ impl App { pub fn select(mut self) -> Result { let count = self.selection.len(); - if let Some(n) = self.focused_node().map(|n| n.to_owned()) { + if let Some(n) = self.focused_node().cloned() { self.selection.insert(n); } @@ -1634,7 +1630,7 @@ impl App { pub fn un_select(mut self) -> Result { let count = self.selection.len(); - if let Some(n) = self.focused_node().map(|n| n.to_owned()) { + if let Some(n) = self.focused_node().cloned() { self.selection .retain(|s| s.absolute_path != n.absolute_path); } @@ -1808,7 +1804,7 @@ impl App { .config .general .initial_sorting - .to_owned() + .clone() .unwrap_or_default(); Ok(self) } diff --git a/src/compat.rs b/src/compat.rs index bafd50c..640503b 100644 --- a/src/compat.rs +++ b/src/compat.rs @@ -7,7 +7,7 @@ use crate::ui::block; use crate::ui::string_to_text; use crate::ui::Constraint; use crate::ui::ContentRendererArg; -use mlua::Lua; +use crate::ui::UI; use serde::{Deserialize, Serialize}; use tui::layout::Constraint as TuiConstraint; use tui::layout::Rect as TuiRect; @@ -60,12 +60,11 @@ pub struct CustomContent { /// A cursed function from crate::ui. pub fn draw_custom_content( + ui: &mut UI, f: &mut Frame, - screen_size: TuiRect, layout_size: TuiRect, app: &app::App, content: CustomContent, - lua: &Lua, ) { let config = app.config.general.panel_ui.default.clone(); let title = content.title; @@ -85,12 +84,12 @@ pub fn draw_custom_content( let ctx = ContentRendererArg { app: app.to_lua_ctx_light(), layout_size: layout_size.into(), - screen_size: screen_size.into(), + screen_size: ui.screen_size.into(), }; - let render = lua::serialize(lua, &ctx) + let render = lua::serialize(ui.lua, &ctx) .map(|arg| { - lua::call(lua, &render, arg).unwrap_or_else(|e| format!("{e:?}")) + lua::call(ui.lua, &render, arg).unwrap_or_else(|e| format!("{e:?}")) }) .unwrap_or_else(|e| e.to_string()); @@ -121,12 +120,12 @@ pub fn draw_custom_content( let ctx = ContentRendererArg { app: app.to_lua_ctx_light(), layout_size: layout_size.into(), - screen_size: screen_size.into(), + screen_size: ui.screen_size.into(), }; - let items = lua::serialize(lua, &ctx) + let items = lua::serialize(ui.lua, &ctx) .map(|arg| { - lua::call(lua, &render, arg) + lua::call(ui.lua, &render, arg) .unwrap_or_else(|e| vec![format!("{e:?}")]) }) .unwrap_or_else(|e| vec![e.to_string()]) @@ -161,7 +160,7 @@ pub fn draw_custom_content( let widths = widths .into_iter() - .map(|w| w.to_tui(screen_size, layout_size)) + .map(|w| w.to_tui(ui.screen_size, layout_size)) .collect::>(); let content = Table::new(rows, widths) @@ -182,12 +181,12 @@ pub fn draw_custom_content( let ctx = ContentRendererArg { app: app.to_lua_ctx_light(), layout_size: layout_size.into(), - screen_size: screen_size.into(), + screen_size: ui.screen_size.into(), }; - let rows = lua::serialize(lua, &ctx) + let rows = lua::serialize(ui.lua, &ctx) .map(|arg| { - lua::call(lua, &render, arg) + lua::call(ui.lua, &render, arg) .unwrap_or_else(|e| vec![vec![format!("{e:?}")]]) }) .unwrap_or_else(|e| vec![vec![e.to_string()]]) @@ -204,7 +203,7 @@ pub fn draw_custom_content( let widths = widths .into_iter() - .map(|w| w.to_tui(screen_size, layout_size)) + .map(|w| w.to_tui(ui.screen_size, layout_size)) .collect::>(); let mut content = Table::new(rows, &widths).block(block( diff --git a/src/config.rs b/src/config.rs index 05f9998..64663c5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -55,7 +55,7 @@ pub struct NodeTypeConfig { impl NodeTypeConfig { pub fn extend(mut self, other: &Self) -> Self { self.style = self.style.extend(&other.style); - self.meta.extend(other.meta.to_owned()); + self.meta.extend(other.meta.clone()); self } } @@ -85,11 +85,11 @@ pub struct NodeTypesConfig { impl NodeTypesConfig { pub fn get(&self, node: &Node) -> NodeTypeConfig { let mut node_type = if node.is_symlink { - self.symlink.to_owned() + self.symlink.clone() } else if node.is_dir { - self.directory.to_owned() + self.directory.clone() } else { - self.file.to_owned() + self.file.clone() }; let mut me = node.mime_essence.splitn(2, '/'); @@ -141,7 +141,7 @@ pub struct UiElement { impl UiElement { pub fn extend(mut self, other: &Self) -> Self { - self.format = other.format.to_owned().or(self.format); + self.format = other.format.clone().or(self.format); self.style = self.style.extend(&other.style); self } @@ -641,8 +641,8 @@ impl PanelUiConfig { pub fn extend(mut self, other: &Self) -> Self { self.title = self.title.extend(&other.title); self.style = self.style.extend(&other.style); - self.borders = other.borders.to_owned().or(self.borders); - self.border_type = other.border_type.to_owned().or(self.border_type); + self.borders = other.borders.clone().or(self.borders); + self.border_type = other.border_type.or(self.border_type); self.border_style = self.border_style.extend(&other.border_style); self } diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs index aa60eeb..a625b40 100644 --- a/src/directory_buffer.rs +++ b/src/directory_buffer.rs @@ -1,32 +1,52 @@ use crate::node::Node; use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; use time::OffsetDateTime; -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] pub struct ScrollState { - current_focus: usize, + pub current_focus: usize, pub last_focus: Option, pub skipped_rows: usize, /* The number of visible next lines when scrolling towards either ends of the view port */ pub initial_preview_cushion: usize, + + pub vimlike_scrolling: bool, } impl ScrollState { - pub fn set_focus(&mut self, current_focus: usize) { + pub fn new(current_focus: usize, total: usize, vimlike_scrolling: bool) -> Self { + let initial_preview_cushion = 5; + Self { + current_focus, + last_focus: None, + skipped_rows: 0, + initial_preview_cushion, + vimlike_scrolling, + } + .update_skipped_rows(initial_preview_cushion + 1, total) + } + + pub fn set_focus(mut self, current_focus: usize) -> Self { self.last_focus = Some(self.current_focus); self.current_focus = current_focus; + self } - pub fn get_focus(&self) -> usize { - self.current_focus + pub fn update_skipped_rows(self, height: usize, total: usize) -> Self { + if self.vimlike_scrolling { + self.update_skipped_rows_vimlike(height, total) + } else { + self.update_skipped_rows_paginated(height) + } } - pub fn calc_skipped_rows( - &self, - height: usize, - total: usize, - vimlike_scrolling: bool, - ) -> usize { + pub fn update_skipped_rows_paginated(mut self, height: usize) -> Self { + self.skipped_rows = height * (self.current_focus / height.max(1)); + self + } + + pub fn update_skipped_rows_vimlike(mut self, height: usize, total: usize) -> Self { let preview_cushion = if height >= self.initial_preview_cushion * 3 { self.initial_preview_cushion } else if height >= 9 { @@ -47,45 +67,52 @@ impl ScrollState { .saturating_sub(preview_cushion + 1) .min(total.saturating_sub(preview_cushion + 1)); - if !vimlike_scrolling { - height * (self.current_focus / height.max(1)) - } else if last_focus.is_none() { - // Just entered the directory - 0 - } else if current_focus == 0 { + self.skipped_rows = if current_focus == 0 { // When focus goes to first node 0 } else if current_focus == total.saturating_sub(1) { // When focus goes to last node total.saturating_sub(height) - } else if (start_cushion_row..=end_cushion_row).contains(¤t_focus) { + } else if current_focus > start_cushion_row && current_focus <= end_cushion_row { // If within cushioned area; do nothing first_visible_row - } else if current_focus > last_focus.unwrap() { - // When scrolling down the cushioned area - if current_focus > total.saturating_sub(preview_cushion + 1) { - // When focusing the last nodes; always view the full last page - total.saturating_sub(height) - } else { - // When scrolling down the cushioned area without reaching the last nodes - current_focus.saturating_sub(height.saturating_sub(preview_cushion + 1)) - } - } else if current_focus < last_focus.unwrap() { - // When scrolling up the cushioned area - if current_focus < preview_cushion { - // When focusing the first nodes; always view the full first page - 0 - } else if current_focus > end_cushion_row { - // When scrolling up from the last rows; do nothing - first_visible_row - } else { - // When scrolling up the cushioned area without reaching the first nodes - current_focus.saturating_sub(preview_cushion) + } else if let Some(last_focus) = last_focus { + match current_focus.cmp(&last_focus) { + Ordering::Greater => { + // When scrolling down the cushioned area + if current_focus > total.saturating_sub(preview_cushion + 1) { + // When focusing the last nodes; always view the full last page + total.saturating_sub(height) + } else { + // When scrolling down the cushioned area without reaching the last nodes + current_focus + .saturating_sub(height.saturating_sub(preview_cushion + 1)) + } + } + + Ordering::Less => { + // When scrolling up the cushioned area + if current_focus < preview_cushion { + // When focusing the first nodes; always view the full first page + 0 + } else if current_focus > end_cushion_row { + // When scrolling up from the last rows; do nothing + first_visible_row + } else { + // When scrolling up the cushioned area without reaching the first nodes + current_focus.saturating_sub(preview_cushion) + } + } + Ordering::Equal => { + // Do nothing + first_visible_row + } } } else { - // If nothing matches; do nothing + // Just entered dir first_visible_row - } + }; + self } } @@ -94,31 +121,26 @@ pub struct DirectoryBuffer { pub parent: String, pub nodes: Vec, pub total: usize, - pub scroll_state: ScrollState, + pub focus: usize, #[serde(skip, default = "now")] pub explored_at: OffsetDateTime, } impl DirectoryBuffer { - pub fn new(parent: String, nodes: Vec, current_focus: usize) -> Self { + pub fn new(parent: String, nodes: Vec, focus: usize) -> Self { let total = nodes.len(); Self { parent, nodes, total, - scroll_state: ScrollState { - current_focus, - last_focus: None, - skipped_rows: 0, - initial_preview_cushion: 5, - }, + focus, explored_at: now(), } } pub fn focused_node(&self) -> Option<&Node> { - self.nodes.get(self.scroll_state.current_focus) + self.nodes.get(self.focus) } } @@ -133,36 +155,39 @@ mod tests { use super::*; #[test] - fn test_calc_skipped_rows_non_vimlike_scrolling() { + fn test_update_skipped_rows_paginated() { let state = ScrollState { current_focus: 10, last_focus: Some(8), skipped_rows: 0, initial_preview_cushion: 5, + vimlike_scrolling: false, }; let height = 5; - let total = 20; - let vimlike_scrolling = false; + let total = 100; - let result = state.calc_skipped_rows(height, total, vimlike_scrolling); - assert_eq!(result, height * (state.current_focus / height.max(1))); + let state = state.update_skipped_rows(height, total); + assert_eq!( + state.skipped_rows, + height * (state.current_focus / height.max(1)) + ); } #[test] - fn test_calc_skipped_rows_entered_directory() { + fn test_update_skipped_rows_entered_directory() { let state = ScrollState { - current_focus: 10, + current_focus: 100, last_focus: None, skipped_rows: 0, initial_preview_cushion: 5, + vimlike_scrolling: true, }; let height = 5; - let total = 20; - let vimlike_scrolling = true; + let total = 200; - let result = state.calc_skipped_rows(height, total, vimlike_scrolling); + let result = state.update_skipped_rows(height, total).skipped_rows; assert_eq!(result, 0); } @@ -173,13 +198,13 @@ mod tests { last_focus: Some(8), skipped_rows: 5, initial_preview_cushion: 5, + vimlike_scrolling: true, }; let height = 5; let total = 20; - let vimlike_scrolling = true; - let result = state.calc_skipped_rows(height, total, vimlike_scrolling); + let result = state.update_skipped_rows(height, total).skipped_rows; assert_eq!(result, 0); } @@ -190,13 +215,13 @@ mod tests { last_focus: Some(18), skipped_rows: 15, initial_preview_cushion: 5, + vimlike_scrolling: true, }; let height = 5; let total = 20; - let vimlike_scrolling = true; - let result = state.calc_skipped_rows(height, total, vimlike_scrolling); + let result = state.update_skipped_rows(height, total).skipped_rows; assert_eq!(result, 15); } @@ -207,13 +232,13 @@ mod tests { last_focus: Some(10), skipped_rows: 10, initial_preview_cushion: 5, + vimlike_scrolling: true, }; let height = 5; let total = 20; - let vimlike_scrolling = true; - let result = state.calc_skipped_rows(height, total, vimlike_scrolling); + let result = state.update_skipped_rows(height, total).skipped_rows; assert_eq!(result, 10); } @@ -224,13 +249,13 @@ mod tests { last_focus: Some(10), skipped_rows: 10, initial_preview_cushion: 5, + vimlike_scrolling: true, }; let height = 5; let total = 20; - let vimlike_scrolling = true; - let result = state.calc_skipped_rows(height, total, vimlike_scrolling); + let result = state.update_skipped_rows(height, total).skipped_rows; assert_eq!(result, 7); } diff --git a/src/dirs.rs b/src/dirs.rs index 4bbad82..60a7eca 100644 --- a/src/dirs.rs +++ b/src/dirs.rs @@ -22,5 +22,5 @@ pub fn runtime_dir() -> PathBuf { else { return env::temp_dir(); }; - dir.to_owned() + dir.clone() } diff --git a/src/explorer.rs b/src/explorer.rs index 1f66d56..2ab45dc 100644 --- a/src/explorer.rs +++ b/src/explorer.rs @@ -45,6 +45,7 @@ pub(crate) fn explore_sync( parent: PathBuf, focused_path: Option, fallback_focus: usize, + vimlike_scrolling: bool, ) -> Result { let nodes = explore(&parent, &config)?; let focus_index = if config.searcher.is_some() { @@ -73,26 +74,33 @@ pub(crate) fn explore_async( parent: PathBuf, focused_path: Option, fallback_focus: usize, + vimlike_scrolling: bool, tx_msg_in: Sender, ) { thread::spawn(move || { - explore_sync(config, parent.clone(), focused_path, fallback_focus) - .and_then(|buf| { - tx_msg_in - .send(Task::new( - MsgIn::Internal(InternalMsg::SetDirectory(buf)), - None, - )) - .map_err(Error::new) - }) - .unwrap_or_else(|e| { - tx_msg_in - .send(Task::new( - MsgIn::External(ExternalMsg::LogError(e.to_string())), - None, - )) - .unwrap_or_default(); // Let's not panic if xplr closes. - }) + explore_sync( + config, + parent.clone(), + focused_path, + fallback_focus, + vimlike_scrolling, + ) + .and_then(|buf| { + tx_msg_in + .send(Task::new( + MsgIn::Internal(InternalMsg::SetDirectory(buf)), + None, + )) + .map_err(Error::new) + }) + .unwrap_or_else(|e| { + tx_msg_in + .send(Task::new( + MsgIn::External(ExternalMsg::LogError(e.to_string())), + None, + )) + .unwrap_or_default(); // Let's not panic if xplr closes. + }) }); } @@ -101,6 +109,7 @@ pub(crate) fn explore_recursive_async( parent: PathBuf, focused_path: Option, fallback_focus: usize, + vimlike_scrolling: bool, tx_msg_in: Sender, ) { explore_async( @@ -108,6 +117,7 @@ pub(crate) fn explore_recursive_async( parent.clone(), focused_path, fallback_focus, + vimlike_scrolling, tx_msg_in.clone(), ); if let Some(grand_parent) = parent.parent() { @@ -116,6 +126,7 @@ pub(crate) fn explore_recursive_async( grand_parent.into(), parent.file_name().map(|p| p.into()), 0, + vimlike_scrolling, tx_msg_in, ); } @@ -130,7 +141,7 @@ mod tests { let config = ExplorerConfig::default(); let path = PathBuf::from("."); - let r = explore_sync(config, path, None, 0); + let r = explore_sync(config, path, None, 0, false); assert!(r.is_ok()); } @@ -140,7 +151,7 @@ mod tests { let config = ExplorerConfig::default(); let path = PathBuf::from("/there/is/no/path"); - let r = explore_sync(config, path, None, 0); + let r = explore_sync(config, path, None, 0, false); assert!(r.is_err()); } @@ -169,7 +180,7 @@ mod tests { let path = PathBuf::from("."); let (tx_msg_in, rx_msg_in) = mpsc::channel(); - explore_async(config, path, None, 0, tx_msg_in.clone()); + explore_async(config, path, None, 0, false, tx_msg_in.clone()); let task = rx_msg_in.recv().unwrap(); let dbuf = extract_dirbuf_from_msg(task.msg); diff --git a/src/input.rs b/src/input.rs index dd1fba5..b9f9115 100644 --- a/src/input.rs +++ b/src/input.rs @@ -647,7 +647,7 @@ impl Key { Self::ShiftZ => Some('Z'), Self::Space => Some(' '), - Self::Special(c) => Some(c.to_owned()), + Self::Special(c) => Some(*c), _ => None, } diff --git a/src/runner.rs b/src/runner.rs index 90c5b53..a3c900e 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -8,7 +8,8 @@ use crate::explorer; use crate::lua; use crate::pipe; use crate::pwd_watcher; -use crate::ui; +use crate::ui::NO_COLOR; +use crate::ui::UI; use crate::yaml; use anyhow::{bail, Error, Result}; use crossterm::event; @@ -89,7 +90,7 @@ fn call( let focus_index = app .directory_buffer .as_ref() - .map(|d| d.scroll_state.get_focus()) + .map(|d| d.focus) .unwrap_or_default() .to_string(); @@ -279,16 +280,14 @@ impl Runner { app.explorer_config.clone(), app.pwd.clone().into(), self.focused_path, - app.directory_buffer - .as_ref() - .map(|d| d.scroll_state.get_focus()) - .unwrap_or(0), + app.directory_buffer.as_ref().map(|d| d.focus).unwrap_or(0), + app.config.general.vimlike_scrolling, tx_msg_in.clone(), ); tx_pwd_watcher.send(app.pwd.clone())?; let mut result = Ok(None); - let session_path = app.session_path.to_owned(); + let session_path = app.session_path.clone(); term::enable_raw_mode()?; @@ -344,6 +343,9 @@ impl Runner { None, ))?; + // UI + let mut ui = UI::new(&lua); + 'outer: for task in rx_msg_in { match app.handle_task(task) { Ok(a) => { @@ -433,8 +435,9 @@ impl Runner { .map(|n| n.relative_path.clone().into()), app.directory_buffer .as_ref() - .map(|d| d.scroll_state.get_focus()) + .map(|d| d.focus) .unwrap_or(0), + app.config.general.vimlike_scrolling, tx_msg_in.clone(), ); tx_pwd_watcher.send(app.pwd.clone())?; @@ -448,8 +451,9 @@ impl Runner { .map(|n| n.relative_path.clone().into()), app.directory_buffer .as_ref() - .map(|d| d.scroll_state.get_focus()) + .map(|d| d.focus) .unwrap_or(0), + app.config.general.vimlike_scrolling, tx_msg_in.clone(), ); tx_pwd_watcher.send(app.pwd.clone())?; @@ -479,7 +483,7 @@ impl Runner { tx_pwd_watcher.send(app.pwd.clone())?; // OSC 7: Change CWD - if !(*ui::NO_COLOR) { + if !(*NO_COLOR) { write!( terminal.backend_mut(), "\x1b]7;file://{}{}\x1b\\", @@ -496,7 +500,7 @@ impl Runner { } // UI - terminal.draw(|f| ui::draw(f, &mut app, &lua))?; + terminal.draw(|f| ui.draw(f, &app))?; } EnableMouse => { diff --git a/src/ui.rs b/src/ui.rs index 2f62610..d1813c8 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -60,6 +60,32 @@ pub fn string_to_text<'a>(string: String) -> Text<'a> { } } +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct Rect { + x: u16, + y: u16, + height: u16, + width: u16, +} + +impl From for Rect { + fn from(tui: TuiRect) -> Self { + Self { + x: tui.x, + y: tui.y, + height: tui.height, + width: tui.width, + } + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct ContentRendererArg { + pub app: app::LuaContextLight, + pub screen_size: Rect, + pub layout_size: Rect, +} + #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct LayoutOptions { @@ -81,7 +107,7 @@ impl LayoutOptions { self.margin = other.margin.or(self.margin); self.horizontal_margin = other.horizontal_margin.or(self.horizontal_margin); self.vertical_margin = other.vertical_margin.or(self.vertical_margin); - self.constraints = other.constraints.to_owned().or(self.constraints); + self.constraints = other.constraints.clone().or(self.constraints); self } } @@ -154,7 +180,7 @@ impl Layout { }, ) => Self::Horizontal { config: sconfig.extend(oconfig), - splits: osplits.to_owned(), + splits: osplits.clone(), }, ( @@ -168,9 +194,9 @@ impl Layout { }, ) => Self::Vertical { config: sconfig.extend(oconfig), - splits: osplits.to_owned(), + splits: osplits.clone(), }, - (_, other) => other.to_owned(), + (_, other) => other.clone(), } } @@ -192,7 +218,7 @@ impl Layout { }, other => { if other == *target { - replacement.to_owned() + replacement.clone() } else { other } @@ -364,14 +390,10 @@ impl Style { pub fn extend(mut self, other: &Self) -> Self { self.fg = other.fg.or(self.fg); self.bg = other.bg.or(self.bg); - self.add_modifiers = extend_optional_modifiers( - self.add_modifiers, - other.add_modifiers.to_owned(), - ); - self.sub_modifiers = extend_optional_modifiers( - self.sub_modifiers, - other.sub_modifiers.to_owned(), - ); + self.add_modifiers = + extend_optional_modifiers(self.add_modifiers, other.add_modifiers.clone()); + self.sub_modifiers = + extend_optional_modifiers(self.sub_modifiers, other.sub_modifiers.clone()); self } } @@ -594,12 +616,12 @@ pub struct ResolvedNodeUiMetadata { impl From for ResolvedNodeUiMetadata { fn from(node: ResolvedNode) -> Self { Self { - absolute_path: node.absolute_path.to_owned(), - extension: node.extension.to_owned(), + absolute_path: node.absolute_path.clone(), + extension: node.extension.clone(), is_dir: node.is_dir, is_file: node.is_file, is_readonly: node.is_readonly, - mime_essence: node.mime_essence.to_owned(), + mime_essence: node.mime_essence.clone(), size: node.size, human_size: node.human_size, created: node.created, @@ -663,21 +685,21 @@ impl NodeUiMetadata { style: Style, ) -> Self { Self { - parent: node.parent.to_owned(), - relative_path: node.relative_path.to_owned(), - absolute_path: node.absolute_path.to_owned(), - extension: node.extension.to_owned(), + parent: node.parent.clone(), + relative_path: node.relative_path.clone(), + absolute_path: node.absolute_path.clone(), + extension: node.extension.clone(), is_symlink: node.is_symlink, is_broken: node.is_broken, is_dir: node.is_dir, is_file: node.is_file, is_readonly: node.is_readonly, - mime_essence: node.mime_essence.to_owned(), + mime_essence: node.mime_essence.clone(), size: node.size, - human_size: node.human_size.to_owned(), - permissions: node.permissions.to_owned(), - canonical: node.canonical.to_owned().map(ResolvedNode::into), - symlink: node.symlink.to_owned().map(ResolvedNode::into), + human_size: node.human_size.clone(), + permissions: node.permissions, + canonical: node.canonical.clone().map(ResolvedNode::into), + symlink: node.symlink.clone().map(ResolvedNode::into), created: node.created, last_modified: node.last_modified, uid: node.uid, @@ -703,7 +725,7 @@ pub fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> { .borders(TuiBorders::from_bits_truncate( config .borders - .to_owned() + .clone() .unwrap_or_default() .iter() .map(|b| b.bits()) @@ -718,797 +740,726 @@ pub fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> { .border_style(config.border_style) } -fn draw_table( - f: &mut Frame, - screen_size: TuiRect, - layout_size: TuiRect, - app: &mut app::App, - lua: &Lua, -) { - let panel_config = &app.config.general.panel_ui; - let config = panel_config.default.to_owned().extend(&panel_config.table); - let app_config = app.config.to_owned(); - let header_height = app_config.general.table.header.height.unwrap_or(1); - let height: usize = - (layout_size.height.max(header_height + 2) - (header_height + 2)).into(); - let row_style = app_config.general.table.row.style.to_owned(); +pub struct UI<'lua> { + pub lua: &'lua Lua, + pub screen_size: TuiRect, +} - let rows = app - .directory_buffer - .as_mut() - .map(|dir| { - dir.scroll_state.skipped_rows = dir.scroll_state.calc_skipped_rows( - height, - dir.total, - app.config.general.vimlike_scrolling, - ); - dir.nodes - .iter() - .enumerate() - .skip(dir.scroll_state.skipped_rows) - .take(height) - .map(|(index, node)| { - let is_focused = dir.scroll_state.get_focus() == index; +impl<'lua> UI<'lua> { + pub fn new(lua: &'lua Lua) -> Self { + let screen_size = Default::default(); + Self { lua, screen_size } + } +} - let is_selected = app - .selection - .iter() - .any(|s| s.absolute_path == node.absolute_path); +impl UI<'_> { + fn draw_table(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) { + let panel_config = &app.config.general.panel_ui; + let config = panel_config.default.clone().extend(&panel_config.table); + let app_config = app.config.clone(); + let header_height = app_config.general.table.header.height.unwrap_or(1); + let height: usize = + (layout_size.height.max(header_height + 2) - (header_height + 2)).into(); + let row_style = app_config.general.table.row.style.clone(); - let is_first = index == 0; - let is_last = index == dir.total.max(1) - 1; + let rows = app + .directory_buffer + .as_ref() + .map(|dir| { + dir.nodes + .iter() + .enumerate() + .skip(height * (dir.focus / height.max(1))) + .take(height) + .map(|(index, node)| { + let is_focused = dir.focus == index; - let tree = app_config - .general - .table - .tree - .to_owned() - .map(|t| { - if is_last { - t.2.format - } else if is_first { - t.0.format - } else { - t.1.format - } - }) - .unwrap_or_default(); + let is_selected = app + .selection + .iter() + .any(|s| s.absolute_path == node.absolute_path); - let node_type = app_config.node_types.get(node); + let is_first = index == 0; + let is_last = index == dir.total.max(1) - 1; - let (relative_index, is_before_focus, is_after_focus) = - match dir.scroll_state.get_focus().cmp(&index) { - Ordering::Greater => { - (dir.scroll_state.get_focus() - index, true, false) - } - Ordering::Less => { - (index - dir.scroll_state.get_focus(), false, true) - } - Ordering::Equal => (0, false, false), + let tree = app_config + .general + .table + .tree + .clone() + .map(|t| { + if is_last { + t.2.format + } else if is_first { + t.0.format + } else { + t.1.format + } + }) + .unwrap_or_default(); + + let node_type = app_config.node_types.get(node); + + let (relative_index, is_before_focus, is_after_focus) = + match dir.focus.cmp(&index) { + Ordering::Greater => (dir.focus - index, true, false), + Ordering::Less => (index - dir.focus, false, true), + Ordering::Equal => (0, false, false), + }; + + let (mut prefix, mut suffix, mut style) = { + let ui = app_config.general.default_ui.clone(); + (ui.prefix, ui.suffix, ui.style.extend(&node_type.style)) }; - let (mut prefix, mut suffix, mut style) = { - let ui = app_config.general.default_ui.to_owned(); - (ui.prefix, ui.suffix, ui.style.extend(&node_type.style)) - }; + if is_focused && is_selected { + let ui = app_config.general.focus_selection_ui.clone(); + prefix = ui.prefix.clone().or(prefix); + suffix = ui.suffix.clone().or(suffix); + style = style.extend(&ui.style); + } else if is_selected { + let ui = app_config.general.selection_ui.clone(); + prefix = ui.prefix.clone().or(prefix); + suffix = ui.suffix.clone().or(suffix); + style = style.extend(&ui.style); + } else if is_focused { + let ui = app_config.general.focus_ui.clone(); + prefix = ui.prefix.clone().or(prefix); + suffix = ui.suffix.clone().or(suffix); + style = style.extend(&ui.style); + }; - if is_focused && is_selected { - let ui = app_config.general.focus_selection_ui.to_owned(); - prefix = ui.prefix.to_owned().or(prefix); - suffix = ui.suffix.to_owned().or(suffix); - style = style.extend(&ui.style); - } else if is_selected { - let ui = app_config.general.selection_ui.to_owned(); - prefix = ui.prefix.to_owned().or(prefix); - suffix = ui.suffix.to_owned().or(suffix); - style = style.extend(&ui.style); - } else if is_focused { - let ui = app_config.general.focus_ui.to_owned(); - prefix = ui.prefix.to_owned().or(prefix); - suffix = ui.suffix.to_owned().or(suffix); - style = style.extend(&ui.style); - }; + let meta = NodeUiMetadata::new( + node, + index, + relative_index, + is_before_focus, + is_after_focus, + tree.unwrap_or_default(), + prefix.unwrap_or_default(), + suffix.unwrap_or_default(), + is_selected, + is_focused, + dir.total, + node_type.meta, + style, + ); - let meta = NodeUiMetadata::new( - node, - index, - relative_index, - is_before_focus, - is_after_focus, - tree.unwrap_or_default(), - prefix.unwrap_or_default(), - suffix.unwrap_or_default(), - is_selected, - is_focused, - dir.total, - node_type.meta, - style, - ); - - let cols = lua::serialize::(lua, &meta) - .map(|v| { - app_config - .general - .table - .row - .cols - .to_owned() - .unwrap_or_default() - .iter() - .filter_map(|c| { - c.format.as_ref().map(|f| { - let out = lua::call(lua, f, v.clone()) - .unwrap_or_else(|e| format!("{e:?}")); - (string_to_text(out), c.style.to_owned()) + let cols = lua::serialize::(self.lua, &meta) + .map(|v| { + app_config + .general + .table + .row + .cols + .clone() + .unwrap_or_default() + .iter() + .filter_map(|c| { + c.format.as_ref().map(|f| { + let out = lua::call(self.lua, f, v.clone()) + .unwrap_or_else(|e| format!("{e:?}")); + (string_to_text(out), c.style.clone()) + }) }) - }) - .collect::>() - }) - .unwrap_or_default() - .into_iter() - .map(|(text, style)| Cell::from(text).style(style)) - .collect::>(); + .collect::>() + }) + .unwrap_or_default() + .into_iter() + .map(|(text, style)| Cell::from(text).style(style)) + .collect::>(); - Row::new(cols).style(row_style.to_owned()) - }) - .collect::>() - }) - .unwrap_or_default(); + Row::new(cols).style(row_style.clone()) + }) + .collect::>() + }) + .unwrap_or_default(); - let table_constraints: Vec = app_config - .general - .table - .col_widths - .to_owned() - .unwrap_or_default() - .into_iter() - .map(|c| c.to_tui(screen_size, layout_size)) - .collect(); + let table_constraints: Vec = app_config + .general + .table + .col_widths + .clone() + .unwrap_or_default() + .into_iter() + .map(|c| c.to_tui(self.screen_size, layout_size)) + .collect(); - let pwd = if let Some(vroot) = app.vroot.as_ref() { - app.pwd.strip_prefix(vroot).unwrap_or(&app.pwd) - } else { - &app.pwd + let pwd = if let Some(vroot) = app.vroot.as_ref() { + app.pwd.strip_prefix(vroot).unwrap_or(&app.pwd) + } else { + &app.pwd + } + .trim_matches('/'); + + let pwd = path::escape(pwd); + + let vroot_indicator = if app.vroot.is_some() { "vroot:" } else { "" }; + + let node_count = app.directory_buffer.as_ref().map(|d| d.total).unwrap_or(0); + let node_count = if node_count == 0 { + String::new() + } else { + format!("({node_count}) ") + }; + + let table = Table::new(rows, table_constraints) + .style(app_config.general.table.style.clone()) + .highlight_style(app_config.general.focus_ui.style.clone()) + .column_spacing(app_config.general.table.col_spacing.unwrap_or_default()) + .block(block( + config, + format!(" {vroot_indicator}/{pwd} {node_count}"), + )); + + let table = table.clone().header( + Row::new( + app_config + .general + .table + .header + .cols + .clone() + .unwrap_or_default() + .iter() + .map(|c| { + Cell::from(c.format.clone().unwrap_or_default()) + .style(c.style.clone()) + }) + .collect::>(), + ) + .height(header_height) + .style(app_config.general.table.header.style.clone()), + ); + + f.render_widget(table, layout_size); } - .trim_matches('/'); - let pwd = path::escape(pwd); + fn draw_selection(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) { + let panel_config = &app.config.general.panel_ui; + let config = panel_config.default.clone().extend(&panel_config.selection); - let vroot_indicator = if app.vroot.is_some() { "vroot:" } else { "" }; + let selection_count = app.selection.len(); - let node_count = app.directory_buffer.as_ref().map(|d| d.total).unwrap_or(0); - let node_count = if node_count == 0 { - String::new() - } else { - format!("({node_count}) ") - }; + let selection: Vec = app + .selection + .iter() + .rev() + .take((layout_size.height.max(2) - 2).into()) + .rev() + .map(|n| { + let out = app + .config + .general + .selection + .item + .format + .as_ref() + .map(|f| { + lua::serialize::(self.lua, n) + .and_then(|n| lua::call(self.lua, f, n)) + .unwrap_or_else(|e| format!("{e:?}")) + }) + .unwrap_or_else(|| n.absolute_path.clone()); + string_to_text(out) + }) + .map(|i| { + ListItem::new(i).style(app.config.general.selection.item.style.clone()) + }) + .collect(); - let table = Table::new(rows, table_constraints) - .style(app_config.general.table.style.to_owned()) - .highlight_style(app_config.general.focus_ui.style.to_owned()) - .column_spacing(app_config.general.table.col_spacing.unwrap_or_default()) - .block(block( + // Selected items + let selection_count = if selection_count == 0 { + String::new() + } else { + format!("({selection_count}) ") + }; + + let selection_list = List::new(selection) + .block(block(config, format!(" Selection {selection_count}"))); + + f.render_widget(selection_list, layout_size); + } + + fn draw_help_menu(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) { + let panel_config = &app.config.general.panel_ui; + + let config = panel_config.default.clone().extend(&panel_config.help_menu); + + let help_menu_rows = app + .mode + .help_menu() + .into_iter() + .map(|l| match l { + HelpMenuLine::Paragraph(p) => Row::new([Cell::from(p)].to_vec()), + HelpMenuLine::KeyMap(k, remaps, h) => Row::new({ + if app.config.general.hide_remaps_in_help_menu { + [Cell::from(k), Cell::from(h)].to_vec() + } else { + [Cell::from(k), Cell::from(remaps.join("|")), Cell::from(h)] + .to_vec() + } + }), + }) + .collect::>(); + + let widths = if app.config.general.hide_remaps_in_help_menu { + vec![TuiConstraint::Percentage(20), TuiConstraint::Percentage(80)] + } else { + vec![ + TuiConstraint::Percentage(20), + TuiConstraint::Percentage(20), + TuiConstraint::Percentage(60), + ] + }; + let help_menu = Table::new(help_menu_rows, widths).block(block( config, - format!(" {vroot_indicator}/{pwd} {node_count}"), + format!(" Help [{}{}] ", &app.mode.name, read_only_indicator(app)), )); + f.render_widget(help_menu, layout_size); + } - let table = table.to_owned().header( - Row::new( - app_config - .general - .table - .header - .cols - .to_owned() - .unwrap_or_default() - .iter() - .map(|c| { - Cell::from(c.format.to_owned().unwrap_or_default()) - .style(c.style.to_owned()) - }) - .collect::>(), - ) - .height(header_height) - .style(app_config.general.table.header.style.to_owned()), - ); + fn draw_input_buffer( + &mut self, + f: &mut Frame, + layout_size: TuiRect, + app: &app::App, + ) { + if let Some(input) = app.input.buffer.as_ref() { + let panel_config = &app.config.general.panel_ui; + let config = panel_config + .default + .clone() + .extend(&panel_config.input_and_logs); - f.render_widget(table, layout_size); -} - -fn draw_selection( - f: &mut Frame, - _screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - lua: &Lua, -) { - let panel_config = &app.config.general.panel_ui; - let config = panel_config - .default - .to_owned() - .extend(&panel_config.selection); - - let selection_count = app.selection.len(); - - let selection: Vec = app - .selection - .iter() - .rev() - .take((layout_size.height.max(2) - 2).into()) - .rev() - .map(|n| { - let out = app - .config - .general - .selection - .item - .format + let cursor_offset_left = config + .borders .as_ref() - .map(|f| { - lua::serialize::(lua, n) - .and_then(|n| lua::call(lua, f, n)) - .unwrap_or_else(|e| format!("{e:?}")) - }) - .unwrap_or_else(|| n.absolute_path.clone()); - string_to_text(out) - }) - .map(|i| { - ListItem::new(i).style(app.config.general.selection.item.style.to_owned()) - }) - .collect(); + .map(|b| b.contains(&Border::Left)) + .unwrap_or(false) as u16 + + app.input.prompt.chars().count() as u16; - // Selected items - let selection_count = if selection_count == 0 { - String::new() - } else { - format!("({selection_count}) ") - }; + let cursor_offset_right = config + .borders + .as_ref() + .map(|b| b.contains(&Border::Right)) + .unwrap_or(false) as u16 + + 1; - let selection_list = List::new(selection) - .block(block(config, format!(" Selection {selection_count}"))); + let offset_width = cursor_offset_left + cursor_offset_right; + let width = layout_size.width.max(offset_width) - offset_width; + let scroll = input.visual_scroll(width.into()) as u16; - f.render_widget(selection_list, layout_size); -} + let input_buf = Paragraph::new(Line::from(vec![ + Span::styled( + app.input.prompt.clone(), + app.config.general.prompt.style.clone(), + ), + Span::raw(input.value()), + ])) + .scroll((0, scroll)) + .block(block( + config, + format!( + " Input [{}{}]{} ", + app.mode.name, + read_only_indicator(app), + selection_indicator(app), + ), + )); -fn draw_help_menu( - f: &mut Frame, - _screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - _: &Lua, -) { - let panel_config = &app.config.general.panel_ui; + f.render_widget(input_buf, layout_size); + f.set_cursor( + // Put cursor past the end of the input text + layout_size.x + + (input.visual_cursor() as u16).min(width) + + cursor_offset_left, + // Move one line down, from the border to the input line + layout_size.y + 1, + ); + }; + } - let config = panel_config - .default - .to_owned() - .extend(&panel_config.help_menu); - - let help_menu_rows = app - .mode - .help_menu() - .into_iter() - .map(|l| match l { - HelpMenuLine::Paragraph(p) => Row::new([Cell::from(p)].to_vec()), - HelpMenuLine::KeyMap(k, remaps, h) => Row::new({ - if app.config.general.hide_remaps_in_help_menu { - [Cell::from(k), Cell::from(h)].to_vec() - } else { - [Cell::from(k), Cell::from(remaps.join("|")), Cell::from(h)].to_vec() - } - }), - }) - .collect::>(); - - let widths = if app.config.general.hide_remaps_in_help_menu { - vec![TuiConstraint::Percentage(20), TuiConstraint::Percentage(80)] - } else { - vec![ - TuiConstraint::Percentage(20), - TuiConstraint::Percentage(20), - TuiConstraint::Percentage(60), - ] - }; - let help_menu = Table::new(help_menu_rows, widths).block(block( - config, - format!(" Help [{}{}] ", &app.mode.name, read_only_indicator(app)), - )); - f.render_widget(help_menu, layout_size); -} - -fn draw_input_buffer( - f: &mut Frame, - _screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - _: &Lua, -) { - if let Some(input) = app.input.buffer.as_ref() { + fn draw_sort_n_filter( + &mut self, + f: &mut Frame, + layout_size: TuiRect, + app: &app::App, + ) { let panel_config = &app.config.general.panel_ui; let config = panel_config .default - .to_owned() + .clone() + .extend(&panel_config.sort_and_filter); + let ui = app.config.general.sort_and_filter_ui.clone(); + let filter_by: &IndexSet = &app.explorer_config.filters; + let sort_by: &IndexSet = &app.explorer_config.sorters; + let search = app.explorer_config.searcher.as_ref(); + + let defaultui = &ui.default_identifier; + let forwardui = defaultui + .clone() + .extend(&ui.sort_direction_identifiers.forward); + let reverseui = defaultui + .clone() + .extend(&ui.sort_direction_identifiers.reverse); + + let orderedui = defaultui + .clone() + .extend(&ui.search_direction_identifiers.ordered); + let unorderedui = defaultui + .clone() + .extend(&ui.search_direction_identifiers.unordered); + + let is_ordered_search = search.as_ref().map(|s| !s.unordered).unwrap_or(false); + + let mut spans = filter_by + .iter() + .map(|f| { + ui.filter_identifiers + .get(&f.filter) + .map(|u| { + let ui = defaultui.clone().extend(u); + ( + Span::styled( + ui.format.clone().unwrap_or_default(), + ui.style.clone(), + ), + Span::styled(f.input.clone(), ui.style), + ) + }) + .unwrap_or((Span::raw("f"), Span::raw(""))) + }) + .chain(search.iter().map(|s| { + ui.search_identifiers + .get(&s.algorithm) + .map(|u| { + let direction = if s.unordered { + &unorderedui + } else { + &orderedui + }; + let ui = defaultui.clone().extend(u); + let f = ui + .format + .as_ref() + .map(|f| format!("{f}{p}", p = &s.pattern)) + .unwrap_or_else(|| s.pattern.clone()); + ( + Span::styled(f, ui.style), + Span::styled( + direction.format.clone().unwrap_or_default(), + direction.style.clone(), + ), + ) + }) + .unwrap_or((Span::raw("/"), Span::raw(&s.pattern))) + })) + .chain( + sort_by + .iter() + .map(|s| { + let direction = if s.reverse { &reverseui } else { &forwardui }; + ui.sorter_identifiers + .get(&s.sorter) + .map(|u| { + let ui = defaultui.clone().extend(u); + ( + Span::styled( + ui.format.clone().unwrap_or_default(), + ui.style, + ), + Span::styled( + direction.format.clone().unwrap_or_default(), + direction.style.clone(), + ), + ) + }) + .unwrap_or((Span::raw("s"), Span::raw(""))) + }) + .take(if !is_ordered_search { sort_by.len() } else { 0 }), + ) + .zip(std::iter::repeat(Span::styled( + ui.separator.format.clone().unwrap_or_default(), + ui.separator.style.clone(), + ))) + .flat_map(|((a, b), c)| vec![a, b, c]) + .collect::>(); + + spans.pop(); + + let item_count = filter_by.len() + sort_by.len(); + let item_count = if item_count == 0 { + String::new() + } else { + format!("({item_count}) ") + }; + + let p = Paragraph::new(Line::from(spans)) + .block(block(config, format!(" Sort & filter {item_count}"))); + + f.render_widget(p, layout_size); + } + + fn draw_logs(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) { + let panel_config = &app.config.general.panel_ui; + let config = panel_config + .default + .clone() .extend(&panel_config.input_and_logs); + let logs_config = app.config.general.logs.clone(); + let logs = if app.logs_hidden { + vec![] + } else { + app.logs + .iter() + .rev() + .take(layout_size.height as usize) + .map(|log| { + let fd = format_description!("[hour]:[minute]:[second]"); + let time = + log.created_at.format(fd).unwrap_or_else(|_| "when?".into()); + let cfg = match log.level { + app::LogLevel::Info => &logs_config.info, + app::LogLevel::Warning => &logs_config.warning, + app::LogLevel::Success => &logs_config.success, + app::LogLevel::Error => &logs_config.error, + }; - let cursor_offset_left = config - .borders - .as_ref() - .map(|b| b.contains(&Border::Left)) - .unwrap_or(false) as u16 - + app.input.prompt.chars().count() as u16; + let prefix = + format!("{time}|{0}", cfg.format.clone().unwrap_or_default()); - let cursor_offset_right = config - .borders - .as_ref() - .map(|b| b.contains(&Border::Right)) - .unwrap_or(false) as u16 - + 1; + let padding = " ".repeat(prefix.chars().count()); - let offset_width = cursor_offset_left + cursor_offset_right; - let width = layout_size.width.max(offset_width) - offset_width; - let scroll = input.visual_scroll(width.into()) as u16; + let txt = log + .message + .lines() + .enumerate() + .map(|(i, line)| { + if i == 0 { + format!("{prefix}) {line}") + } else { + format!("{padding} {line}") + } + }) + .take(layout_size.height as usize) + .collect::>() + .join("\n"); - let input_buf = Paragraph::new(Line::from(vec![ - Span::styled( - app.input.prompt.to_owned(), - app.config.general.prompt.style.to_owned(), - ), - Span::raw(input.value()), - ])) - .scroll((0, scroll)) - .block(block( + ListItem::new(txt).style(cfg.style.clone()) + }) + .collect::>() + }; + + let logs_count = app.logs.len(); + let logs_count = if logs_count == 0 { + String::new() + } else { + format!(" ({logs_count})") + }; + + let logs_list = List::new(logs).block(block( config, format!( - " Input [{}{}]{} ", + " Logs{} [{}{}]{} ", + logs_count, app.mode.name, read_only_indicator(app), - selection_indicator(app), + selection_indicator(app) ), )); - f.render_widget(input_buf, layout_size); - f.set_cursor( - // Put cursor past the end of the input text - layout_size.x - + (input.visual_cursor() as u16).min(width) - + cursor_offset_left, - // Move one line down, from the border to the input line - layout_size.y + 1, - ); - }; -} + f.render_widget(logs_list, layout_size); + } -fn draw_sort_n_filter( - f: &mut Frame, - _screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - _: &Lua, -) { - let panel_config = &app.config.general.panel_ui; - let config = panel_config - .default - .to_owned() - .extend(&panel_config.sort_and_filter); - let ui = app.config.general.sort_and_filter_ui.to_owned(); - let filter_by: &IndexSet = &app.explorer_config.filters; - let sort_by: &IndexSet = &app.explorer_config.sorters; - let search = app.explorer_config.searcher.as_ref(); + fn draw_nothing(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) { + let panel_config = &app.config.general.panel_ui; + let config = panel_config.default.clone(); + let nothing = Paragraph::new("").block(block(config, "".into())); + f.render_widget(nothing, layout_size); + } - let defaultui = &ui.default_identifier; - let forwardui = defaultui - .to_owned() - .extend(&ui.sort_direction_identifiers.forward); - let reverseui = defaultui - .to_owned() - .extend(&ui.sort_direction_identifiers.reverse); + fn draw_dynamic( + &mut self, + f: &mut Frame, + layout_size: TuiRect, + app: &app::App, + func: &str, + ) { + let ctx = ContentRendererArg { + app: app.to_lua_ctx_light(), + layout_size: layout_size.into(), + screen_size: self.screen_size.into(), + }; - let orderedui = defaultui - .to_owned() - .extend(&ui.search_direction_identifiers.ordered); - let unorderedui = defaultui - .to_owned() - .extend(&ui.search_direction_identifiers.unordered); + let panel: CustomPanel = lua::serialize(self.lua, &ctx) + .and_then(|arg| lua::call(self.lua, func, arg)) + .unwrap_or_else(|e| CustomPanel::CustomParagraph { + ui: app.config.general.panel_ui.default.clone(), + body: format!("{e:?}"), + }); - let is_ordered_search = search.as_ref().map(|s| !s.unordered).unwrap_or(false); + self.draw_static(f, layout_size, app, panel); + } - let mut spans = filter_by - .iter() - .map(|f| { - ui.filter_identifiers - .get(&f.filter) - .map(|u| { - let ui = defaultui.to_owned().extend(u); - ( - Span::styled( - ui.format.to_owned().unwrap_or_default(), - ui.style.to_owned(), - ), - Span::styled(f.input.to_owned(), ui.style), - ) - }) - .unwrap_or((Span::raw("f"), Span::raw(""))) - }) - .chain(search.iter().map(|s| { - ui.search_identifiers - .get(&s.algorithm) - .map(|u| { - let direction = if s.unordered { - &unorderedui - } else { - &orderedui - }; - let ui = defaultui.to_owned().extend(u); - let f = ui - .format - .as_ref() - .map(|f| format!("{f}{p}", p = &s.pattern)) - .unwrap_or_else(|| s.pattern.clone()); - ( - Span::styled(f, ui.style), - Span::styled( - direction.format.to_owned().unwrap_or_default(), - direction.style.to_owned(), - ), - ) - }) - .unwrap_or((Span::raw("/"), Span::raw(&s.pattern))) - })) - .chain( - sort_by - .iter() - .map(|s| { - let direction = if s.reverse { &reverseui } else { &forwardui }; - ui.sorter_identifiers - .get(&s.sorter) - .map(|u| { - let ui = defaultui.to_owned().extend(u); - ( - Span::styled( - ui.format.to_owned().unwrap_or_default(), - ui.style, - ), - Span::styled( - direction.format.to_owned().unwrap_or_default(), - direction.style.to_owned(), - ), - ) - }) - .unwrap_or((Span::raw("s"), Span::raw(""))) - }) - .take(if !is_ordered_search { sort_by.len() } else { 0 }), - ) - .zip(std::iter::repeat(Span::styled( - ui.separator.format.to_owned().unwrap_or_default(), - ui.separator.style.to_owned(), - ))) - .flat_map(|((a, b), c)| vec![a, b, c]) - .collect::>(); + fn draw_static( + &mut self, + f: &mut Frame, + layout_size: TuiRect, + app: &app::App, + panel: CustomPanel, + ) { + let defaultui = app.config.general.panel_ui.default.clone(); + match panel { + CustomPanel::CustomParagraph { ui, body } => { + let config = defaultui.extend(&ui); + let body = string_to_text(body); + let content = Paragraph::new(body).block(block(config, "".into())); + f.render_widget(content, layout_size); + } - spans.pop(); + CustomPanel::CustomList { ui, body } => { + let config = defaultui.extend(&ui); - let item_count = filter_by.len() + sort_by.len(); - let item_count = if item_count == 0 { - String::new() - } else { - format!("({item_count}) ") - }; + let items = body + .into_iter() + .map(string_to_text) + .map(ListItem::new) + .collect::>(); - let p = Paragraph::new(Line::from(spans)) - .block(block(config, format!(" Sort & filter {item_count}"))); + let content = List::new(items).block(block(config, "".into())); + f.render_widget(content, layout_size); + } - f.render_widget(p, layout_size); -} - -fn draw_logs( - f: &mut Frame, - _screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - _: &Lua, -) { - let panel_config = &app.config.general.panel_ui; - let config = panel_config - .default - .to_owned() - .extend(&panel_config.input_and_logs); - let logs_config = app.config.general.logs.to_owned(); - let logs = if app.logs_hidden { - vec![] - } else { - app.logs - .iter() - .rev() - .take(layout_size.height as usize) - .map(|log| { - let fd = format_description!("[hour]:[minute]:[second]"); - let time = log.created_at.format(fd).unwrap_or_else(|_| "when?".into()); - let cfg = match log.level { - app::LogLevel::Info => &logs_config.info, - app::LogLevel::Warning => &logs_config.warning, - app::LogLevel::Success => &logs_config.success, - app::LogLevel::Error => &logs_config.error, - }; - - let prefix = - format!("{time}|{0}", cfg.format.to_owned().unwrap_or_default()); - - let padding = " ".repeat(prefix.chars().count()); - - let txt = log - .message - .lines() - .enumerate() - .map(|(i, line)| { - if i == 0 { - format!("{prefix}) {line}") - } else { - format!("{padding} {line}") - } + CustomPanel::CustomTable { + ui, + widths, + col_spacing, + body, + } => { + let config = defaultui.extend(&ui); + let rows = body + .into_iter() + .map(|cols| { + Row::new( + cols.into_iter() + .map(string_to_text) + .map(Cell::from) + .collect::>(), + ) }) - .take(layout_size.height as usize) - .collect::>() - .join("\n"); + .collect::>(); - ListItem::new(txt).style(cfg.style.to_owned()) - }) - .collect::>() - }; + let widths = widths + .into_iter() + .map(|w| w.to_tui(self.screen_size, layout_size)) + .collect::>(); - let logs_count = app.logs.len(); - let logs_count = if logs_count == 0 { - String::new() - } else { - format!(" ({logs_count})") - }; + let content = Table::new(rows, widths) + .column_spacing(col_spacing.unwrap_or(1)) + .block(block(config, "".into())); - let logs_list = List::new(logs).block(block( - config, - format!( - " Logs{} [{}{}]{} ", - logs_count, - app.mode.name, - read_only_indicator(app), - selection_indicator(app) - ), - )); + f.render_widget(content, layout_size); + } - f.render_widget(logs_list, layout_size); -} - -pub fn draw_nothing( - f: &mut Frame, - _screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - _lua: &Lua, -) { - let panel_config = &app.config.general.panel_ui; - let config = panel_config.default.to_owned(); - let nothing = Paragraph::new("").block(block(config, "".into())); - f.render_widget(nothing, layout_size); -} - -pub fn draw_dynamic( - f: &mut Frame, - screen_size: TuiRect, - layout_size: TuiRect, - app: &mut app::App, - func: &str, - lua: &Lua, -) { - let ctx = ContentRendererArg { - app: app.to_lua_ctx_light(), - layout_size: layout_size.into(), - screen_size: screen_size.into(), - }; - - let panel: CustomPanel = lua::serialize(lua, &ctx) - .and_then(|arg| lua::call(lua, func, arg)) - .unwrap_or_else(|e| CustomPanel::CustomParagraph { - ui: app.config.general.panel_ui.default.clone(), - body: format!("{e:?}"), - }); - - draw_static(f, screen_size, layout_size, app, panel, lua); -} - -pub fn draw_static( - f: &mut Frame, - screen_size: TuiRect, - layout_size: TuiRect, - app: &mut app::App, - panel: CustomPanel, - _lua: &Lua, -) { - let defaultui = app.config.general.panel_ui.default.clone(); - match panel { - CustomPanel::CustomParagraph { ui, body } => { - let config = defaultui.extend(&ui); - let body = string_to_text(body); - let content = Paragraph::new(body).block(block(config, "".into())); - f.render_widget(content, layout_size); + CustomPanel::CustomLayout(layout) => { + self.draw_layout(layout, f, layout_size, app); + } } + } - CustomPanel::CustomList { ui, body } => { - let config = defaultui.extend(&ui); - - let items = body - .into_iter() - .map(string_to_text) - .map(ListItem::new) - .collect::>(); - - let content = List::new(items).block(block(config, "".into())); - f.render_widget(content, layout_size); - } - - CustomPanel::CustomTable { - ui, - widths, - col_spacing, - body, - } => { - let config = defaultui.extend(&ui); - let rows = body - .into_iter() - .map(|cols| { - Row::new( - cols.into_iter() - .map(string_to_text) - .map(Cell::from) - .collect::>(), + fn draw_layout( + &mut self, + layout: Layout, + f: &mut Frame, + layout_size: TuiRect, + app: &app::App, + ) { + match layout { + Layout::Nothing => self.draw_nothing(f, layout_size, app), + Layout::Table => self.draw_table(f, layout_size, app), + Layout::SortAndFilter => self.draw_sort_n_filter(f, layout_size, app), + Layout::HelpMenu => self.draw_help_menu(f, layout_size, app), + Layout::Selection => self.draw_selection(f, layout_size, app), + Layout::InputAndLogs => { + if app.input.buffer.is_some() { + self.draw_input_buffer(f, layout_size, app); + } else { + self.draw_logs(f, layout_size, app); + }; + } + Layout::Static(panel) => self.draw_static(f, layout_size, app, *panel), + Layout::Dynamic(ref func) => self.draw_dynamic(f, layout_size, app, func), + Layout::CustomContent(content) => { + draw_custom_content(self, f, layout_size, app, *content) + } + Layout::Horizontal { config, splits } => { + let chunks = TuiLayout::default() + .direction(Direction::Horizontal) + .constraints( + config + .constraints + .clone() + .unwrap_or_default() + .iter() + .map(|c| c.to_tui(self.screen_size, layout_size)) + .collect::>(), ) - }) - .collect::>(); + .horizontal_margin( + config + .horizontal_margin + .or(config.margin) + .unwrap_or_default(), + ) + .vertical_margin( + config.vertical_margin.or(config.margin).unwrap_or_default(), + ) + .split(layout_size); - let widths = widths - .into_iter() - .map(|w| w.to_tui(screen_size, layout_size)) - .collect::>(); + splits + .into_iter() + .zip(chunks.iter()) + .for_each(|(split, chunk)| self.draw_layout(split, f, *chunk, app)); + } - let content = Table::new(rows, widths) - .column_spacing(col_spacing.unwrap_or(1)) - .block(block(config, "".into())); + Layout::Vertical { config, splits } => { + let chunks = TuiLayout::default() + .direction(Direction::Vertical) + .constraints( + config + .constraints + .clone() + .unwrap_or_default() + .iter() + .map(|c| c.to_tui(self.screen_size, layout_size)) + .collect::>(), + ) + .horizontal_margin( + config + .horizontal_margin + .or(config.margin) + .unwrap_or_default(), + ) + .vertical_margin( + config.vertical_margin.or(config.margin).unwrap_or_default(), + ) + .split(layout_size); - f.render_widget(content, layout_size); - } - - CustomPanel::CustomLayout(layout) => { - draw_layout(layout, f, screen_size, layout_size, app, _lua); + splits + .into_iter() + .zip(chunks.iter()) + .for_each(|(split, chunk)| self.draw_layout(split, f, *chunk, app)); + } } } -} -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct Rect { - x: u16, - y: u16, - height: u16, - width: u16, -} - -impl From for Rect { - fn from(tui: TuiRect) -> Self { - Self { - x: tui.x, - y: tui.y, - height: tui.height, - width: tui.width, - } + pub fn draw(&mut self, f: &mut Frame, app: &app::App) { + self.screen_size = f.size(); + let layout = app.mode.layout.as_ref().unwrap_or(&app.layout).clone(); + self.draw_layout(layout, f, self.screen_size, app); } } -#[derive(Debug, Clone, Serialize)] -pub struct ContentRendererArg { - pub app: app::LuaContextLight, - pub screen_size: Rect, - pub layout_size: Rect, -} - -pub fn draw_layout( - layout: Layout, - f: &mut Frame, - screen_size: TuiRect, - layout_size: TuiRect, - app: &mut app::App, - lua: &Lua, -) { - match layout { - Layout::Nothing => draw_nothing(f, screen_size, layout_size, app, lua), - Layout::Table => draw_table(f, screen_size, layout_size, app, lua), - Layout::SortAndFilter => { - draw_sort_n_filter(f, screen_size, layout_size, app, lua) - } - Layout::HelpMenu => draw_help_menu(f, screen_size, layout_size, app, lua), - Layout::Selection => draw_selection(f, screen_size, layout_size, app, lua), - Layout::InputAndLogs => { - if app.input.buffer.is_some() { - draw_input_buffer(f, screen_size, layout_size, app, lua); - } else { - draw_logs(f, screen_size, layout_size, app, lua); - }; - } - Layout::Static(panel) => { - draw_static(f, screen_size, layout_size, app, *panel, lua) - } - Layout::Dynamic(ref func) => { - draw_dynamic(f, screen_size, layout_size, app, func, lua) - } - Layout::CustomContent(content) => { - draw_custom_content(f, screen_size, layout_size, app, *content, lua) - } - Layout::Horizontal { config, splits } => { - let chunks = TuiLayout::default() - .direction(Direction::Horizontal) - .constraints( - config - .constraints - .to_owned() - .unwrap_or_default() - .iter() - .map(|c| c.to_tui(screen_size, layout_size)) - .collect::>(), - ) - .horizontal_margin( - config - .horizontal_margin - .or(config.margin) - .unwrap_or_default(), - ) - .vertical_margin( - config.vertical_margin.or(config.margin).unwrap_or_default(), - ) - .split(layout_size); - - splits - .into_iter() - .zip(chunks.iter()) - .for_each(|(split, chunk)| { - draw_layout(split, f, screen_size, *chunk, app, lua) - }); - } - - Layout::Vertical { config, splits } => { - let chunks = TuiLayout::default() - .direction(Direction::Vertical) - .constraints( - config - .constraints - .to_owned() - .unwrap_or_default() - .iter() - .map(|c| c.to_tui(screen_size, layout_size)) - .collect::>(), - ) - .horizontal_margin( - config - .horizontal_margin - .or(config.margin) - .unwrap_or_default(), - ) - .vertical_margin( - config.vertical_margin.or(config.margin).unwrap_or_default(), - ) - .split(layout_size); - - splits - .into_iter() - .zip(chunks.iter()) - .for_each(|(split, chunk)| { - draw_layout(split, f, screen_size, *chunk, app, lua) - }); - } - } -} - -pub fn draw(f: &mut Frame, app: &mut app::App, lua: &Lua) { - let screen_size = f.size(); - let layout = app.mode.layout.as_ref().unwrap_or(&app.layout).to_owned(); - - draw_layout(layout, f, screen_size, screen_size, app, lua); -} - #[cfg(test)] mod tests { use super::*; @@ -1543,7 +1494,7 @@ mod tests { }; assert_eq!( - a.to_owned().extend(&b), + a.clone().extend(&b), Style { fg: Some(Color::Red), bg: Some(Color::Blue), @@ -1563,7 +1514,7 @@ mod tests { ); assert_eq!( - a.to_owned().extend(&c), + a.clone().extend(&c), Style { fg: Some(Color::Cyan), bg: Some(Color::Magenta), From 90df0a2b5aaeb96e3ada3f9d1f13914d5379555b Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Wed, 1 May 2024 14:38:17 +0530 Subject: [PATCH 20/28] vimlike_scrolling -> paginated_scrolling Inspired by @ElSamhaa 's PR https://github.com/sayanarijit/xplr/pull/704 --- benches/criterion.rs | 3 +- docs/en/src/upgrade-guide.md | 6 +- src/app.rs | 1 - src/config.rs | 5 +- src/directory_buffer.rs | 226 ----------------------------------- src/explorer.rs | 51 ++++---- src/init.lua | 10 +- src/runner.rs | 3 - src/ui.rs | 40 ++++++- 9 files changed, 76 insertions(+), 269 deletions(-) diff --git a/benches/criterion.rs b/benches/criterion.rs index f9bddee..cff705b 100644 --- a/benches/criterion.rs +++ b/benches/criterion.rs @@ -98,6 +98,7 @@ fn draw_benchmark(c: &mut Criterion) { }); let lua = mlua::Lua::new(); + let mut ui = ui::UI::new(&lua); let mut app = app::App::create("xplr".into(), None, PWD.into(), &lua, None, [].into()) .expect("failed to create app"); @@ -121,7 +122,7 @@ fn draw_benchmark(c: &mut Criterion) { c.bench_function("draw on terminal", |b| { b.iter(|| { - terminal.draw(|f| ui::draw(f, &mut app, &lua)).unwrap(); + terminal.draw(|f| ui.draw(f, &app)).unwrap(); }) }); diff --git a/docs/en/src/upgrade-guide.md b/docs/en/src/upgrade-guide.md index 38d5347..af56522 100644 --- a/docs/en/src/upgrade-guide.md +++ b/docs/en/src/upgrade-guide.md @@ -128,8 +128,10 @@ compatibility. and move focused or selected files, without having to change directory. - Use `xplr.util.debug()` to debug lua values. - Since v0.21.8: - - You can set `xplr.config.general.vimlike_scrolling = true` to enable - vim-like scrolling. + - Scroll behavior will default to vim-like continuous scrolling. You can set + `xplr.config.general.paginated_scrolling = true` to revert back to the + paginated scrolling. + - Set `xplr.config.general.scroll_padding` to customize the scroll padding. Thanks to @noahmayr for contributing to a major part of this release. diff --git a/src/app.rs b/src/app.rs index dd521d2..ea6384e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -753,7 +753,6 @@ impl App { self.pwd.clone().into(), focus.as_ref().map(PathBuf::from), self.directory_buffer.as_ref().map(|d| d.focus).unwrap_or(0), - self.config.general.vimlike_scrolling, ) { Ok(dir) => self.set_directory(dir), Err(e) => { diff --git a/src/config.rs b/src/config.rs index 64663c5..27ceeaf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -355,7 +355,10 @@ pub struct GeneralConfig { pub global_key_bindings: KeyBindings, #[serde(default)] - pub vimlike_scrolling: bool, + pub paginated_scrolling: bool, + + #[serde(default)] + pub scroll_padding: usize, } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs index a625b40..c6e755d 100644 --- a/src/directory_buffer.rs +++ b/src/directory_buffer.rs @@ -1,121 +1,7 @@ use crate::node::Node; use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; use time::OffsetDateTime; -#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] -pub struct ScrollState { - pub current_focus: usize, - pub last_focus: Option, - pub skipped_rows: usize, - /* The number of visible next lines when scrolling towards either ends of the view port */ - pub initial_preview_cushion: usize, - - pub vimlike_scrolling: bool, -} - -impl ScrollState { - pub fn new(current_focus: usize, total: usize, vimlike_scrolling: bool) -> Self { - let initial_preview_cushion = 5; - Self { - current_focus, - last_focus: None, - skipped_rows: 0, - initial_preview_cushion, - vimlike_scrolling, - } - .update_skipped_rows(initial_preview_cushion + 1, total) - } - - pub fn set_focus(mut self, current_focus: usize) -> Self { - self.last_focus = Some(self.current_focus); - self.current_focus = current_focus; - self - } - - pub fn update_skipped_rows(self, height: usize, total: usize) -> Self { - if self.vimlike_scrolling { - self.update_skipped_rows_vimlike(height, total) - } else { - self.update_skipped_rows_paginated(height) - } - } - - pub fn update_skipped_rows_paginated(mut self, height: usize) -> Self { - self.skipped_rows = height * (self.current_focus / height.max(1)); - self - } - - pub fn update_skipped_rows_vimlike(mut self, height: usize, total: usize) -> Self { - let preview_cushion = if height >= self.initial_preview_cushion * 3 { - self.initial_preview_cushion - } else if height >= 9 { - 3 - } else if height >= 3 { - 1 - } else { - 0 - }; - - let current_focus = self.current_focus; - let last_focus = self.last_focus; - let first_visible_row = self.skipped_rows; - - // Calculate the cushion rows at the start and end of the view port - let start_cushion_row = first_visible_row + preview_cushion; - let end_cushion_row = (first_visible_row + height) - .saturating_sub(preview_cushion + 1) - .min(total.saturating_sub(preview_cushion + 1)); - - self.skipped_rows = if current_focus == 0 { - // When focus goes to first node - 0 - } else if current_focus == total.saturating_sub(1) { - // When focus goes to last node - total.saturating_sub(height) - } else if current_focus > start_cushion_row && current_focus <= end_cushion_row { - // If within cushioned area; do nothing - first_visible_row - } else if let Some(last_focus) = last_focus { - match current_focus.cmp(&last_focus) { - Ordering::Greater => { - // When scrolling down the cushioned area - if current_focus > total.saturating_sub(preview_cushion + 1) { - // When focusing the last nodes; always view the full last page - total.saturating_sub(height) - } else { - // When scrolling down the cushioned area without reaching the last nodes - current_focus - .saturating_sub(height.saturating_sub(preview_cushion + 1)) - } - } - - Ordering::Less => { - // When scrolling up the cushioned area - if current_focus < preview_cushion { - // When focusing the first nodes; always view the full first page - 0 - } else if current_focus > end_cushion_row { - // When scrolling up from the last rows; do nothing - first_visible_row - } else { - // When scrolling up the cushioned area without reaching the first nodes - current_focus.saturating_sub(preview_cushion) - } - } - Ordering::Equal => { - // Do nothing - first_visible_row - } - } - } else { - // Just entered dir - first_visible_row - }; - self - } -} - #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct DirectoryBuffer { pub parent: String, @@ -149,115 +35,3 @@ fn now() -> OffsetDateTime { .ok() .unwrap_or_else(OffsetDateTime::now_utc) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_update_skipped_rows_paginated() { - let state = ScrollState { - current_focus: 10, - last_focus: Some(8), - skipped_rows: 0, - initial_preview_cushion: 5, - vimlike_scrolling: false, - }; - - let height = 5; - let total = 100; - - let state = state.update_skipped_rows(height, total); - assert_eq!( - state.skipped_rows, - height * (state.current_focus / height.max(1)) - ); - } - - #[test] - fn test_update_skipped_rows_entered_directory() { - let state = ScrollState { - current_focus: 100, - last_focus: None, - skipped_rows: 0, - initial_preview_cushion: 5, - vimlike_scrolling: true, - }; - - let height = 5; - let total = 200; - - let result = state.update_skipped_rows(height, total).skipped_rows; - assert_eq!(result, 0); - } - - #[test] - fn test_calc_skipped_rows_top_of_directory() { - let state = ScrollState { - current_focus: 0, - last_focus: Some(8), - skipped_rows: 5, - initial_preview_cushion: 5, - vimlike_scrolling: true, - }; - - let height = 5; - let total = 20; - - let result = state.update_skipped_rows(height, total).skipped_rows; - assert_eq!(result, 0); - } - - #[test] - fn test_calc_skipped_rows_bottom_of_directory() { - let state = ScrollState { - current_focus: 19, - last_focus: Some(18), - skipped_rows: 15, - initial_preview_cushion: 5, - vimlike_scrolling: true, - }; - - let height = 5; - let total = 20; - - let result = state.update_skipped_rows(height, total).skipped_rows; - assert_eq!(result, 15); - } - - #[test] - fn test_calc_skipped_rows_scrolling_down() { - let state = ScrollState { - current_focus: 12, - last_focus: Some(10), - skipped_rows: 10, - initial_preview_cushion: 5, - vimlike_scrolling: true, - }; - - let height = 5; - let total = 20; - - let result = state.update_skipped_rows(height, total).skipped_rows; - assert_eq!(result, 10); - } - - #[test] - fn test_calc_skipped_rows_scrolling_up() { - let state = ScrollState { - current_focus: 8, - last_focus: Some(10), - skipped_rows: 10, - initial_preview_cushion: 5, - vimlike_scrolling: true, - }; - - let height = 5; - let total = 20; - - let result = state.update_skipped_rows(height, total).skipped_rows; - assert_eq!(result, 7); - } - - // Add more tests for other scenarios... -} diff --git a/src/explorer.rs b/src/explorer.rs index 2ab45dc..1f66d56 100644 --- a/src/explorer.rs +++ b/src/explorer.rs @@ -45,7 +45,6 @@ pub(crate) fn explore_sync( parent: PathBuf, focused_path: Option, fallback_focus: usize, - vimlike_scrolling: bool, ) -> Result { let nodes = explore(&parent, &config)?; let focus_index = if config.searcher.is_some() { @@ -74,33 +73,26 @@ pub(crate) fn explore_async( parent: PathBuf, focused_path: Option, fallback_focus: usize, - vimlike_scrolling: bool, tx_msg_in: Sender, ) { thread::spawn(move || { - explore_sync( - config, - parent.clone(), - focused_path, - fallback_focus, - vimlike_scrolling, - ) - .and_then(|buf| { - tx_msg_in - .send(Task::new( - MsgIn::Internal(InternalMsg::SetDirectory(buf)), - None, - )) - .map_err(Error::new) - }) - .unwrap_or_else(|e| { - tx_msg_in - .send(Task::new( - MsgIn::External(ExternalMsg::LogError(e.to_string())), - None, - )) - .unwrap_or_default(); // Let's not panic if xplr closes. - }) + explore_sync(config, parent.clone(), focused_path, fallback_focus) + .and_then(|buf| { + tx_msg_in + .send(Task::new( + MsgIn::Internal(InternalMsg::SetDirectory(buf)), + None, + )) + .map_err(Error::new) + }) + .unwrap_or_else(|e| { + tx_msg_in + .send(Task::new( + MsgIn::External(ExternalMsg::LogError(e.to_string())), + None, + )) + .unwrap_or_default(); // Let's not panic if xplr closes. + }) }); } @@ -109,7 +101,6 @@ pub(crate) fn explore_recursive_async( parent: PathBuf, focused_path: Option, fallback_focus: usize, - vimlike_scrolling: bool, tx_msg_in: Sender, ) { explore_async( @@ -117,7 +108,6 @@ pub(crate) fn explore_recursive_async( parent.clone(), focused_path, fallback_focus, - vimlike_scrolling, tx_msg_in.clone(), ); if let Some(grand_parent) = parent.parent() { @@ -126,7 +116,6 @@ pub(crate) fn explore_recursive_async( grand_parent.into(), parent.file_name().map(|p| p.into()), 0, - vimlike_scrolling, tx_msg_in, ); } @@ -141,7 +130,7 @@ mod tests { let config = ExplorerConfig::default(); let path = PathBuf::from("."); - let r = explore_sync(config, path, None, 0, false); + let r = explore_sync(config, path, None, 0); assert!(r.is_ok()); } @@ -151,7 +140,7 @@ mod tests { let config = ExplorerConfig::default(); let path = PathBuf::from("/there/is/no/path"); - let r = explore_sync(config, path, None, 0, false); + let r = explore_sync(config, path, None, 0); assert!(r.is_err()); } @@ -180,7 +169,7 @@ mod tests { let path = PathBuf::from("."); let (tx_msg_in, rx_msg_in) = mpsc::channel(); - explore_async(config, path, None, 0, false, tx_msg_in.clone()); + explore_async(config, path, None, 0, tx_msg_in.clone()); let task = rx_msg_in.recv().unwrap(); let dbuf = extract_dirbuf_from_msg(task.msg); diff --git a/src/init.lua b/src/init.lua index 903a651..0e2ed3c 100644 --- a/src/init.lua +++ b/src/init.lua @@ -91,10 +91,16 @@ xplr.config.general.enable_recover_mode = false -- Type: boolean xplr.config.general.hide_remaps_in_help_menu = false --- Set it to `true` if you want vim-like scrolling. +-- Set it to `true` if you want paginated scrolling. -- -- Type: boolean -xplr.config.general.vimlike_scrolling = false +xplr.config.general.paginated_scrolling = false + +-- Set the padding value to the scroll area. Only applicable when +-- `xplr.config.general.paginated_scrolling` is set to `false`. +-- +-- Type: boolean +xplr.config.general.scroll_padding = 5 -- Set it to `true` if you want the cursor to stay in the same position when -- the focus is on the first path and you navigate to the previous path diff --git a/src/runner.rs b/src/runner.rs index a3c900e..c5e2c1e 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -281,7 +281,6 @@ impl Runner { app.pwd.clone().into(), self.focused_path, app.directory_buffer.as_ref().map(|d| d.focus).unwrap_or(0), - app.config.general.vimlike_scrolling, tx_msg_in.clone(), ); tx_pwd_watcher.send(app.pwd.clone())?; @@ -437,7 +436,6 @@ impl Runner { .as_ref() .map(|d| d.focus) .unwrap_or(0), - app.config.general.vimlike_scrolling, tx_msg_in.clone(), ); tx_pwd_watcher.send(app.pwd.clone())?; @@ -453,7 +451,6 @@ impl Runner { .as_ref() .map(|d| d.focus) .unwrap_or(0), - app.config.general.vimlike_scrolling, tx_msg_in.clone(), ); tx_pwd_watcher.send(app.pwd.clone())?; diff --git a/src/ui.rs b/src/ui.rs index d1813c8..dfebd7c 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -743,12 +743,18 @@ pub fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> { pub struct UI<'lua> { pub lua: &'lua Lua, pub screen_size: TuiRect, + pub scrolltop: usize, } impl<'lua> UI<'lua> { pub fn new(lua: &'lua Lua) -> Self { let screen_size = Default::default(); - Self { lua, screen_size } + let scrolltop = 0; + Self { + lua, + scrolltop, + screen_size, + } } } @@ -766,10 +772,40 @@ impl UI<'_> { .directory_buffer .as_ref() .map(|dir| { + // Scroll + if app.config.general.paginated_scrolling { + // Paginated scrolling + self.scrolltop = height * (dir.focus / height.max(1)) + } else { + // vim-like-scrolling + self.scrolltop = match dir.focus.cmp(&self.scrolltop) { + Ordering::Greater => { + // Scrolling down + if dir.focus >= self.scrolltop + height { + dir.focus - height + 1 + } else { + self.scrolltop + } + } + Ordering::Less => dir.focus, + Ordering::Equal => self.scrolltop, + }; + + // Add padding if possible + let padding = app.config.general.scroll_padding; + if padding != 0 { + if dir.focus < self.scrolltop + padding { + self.scrolltop = dir.focus.saturating_sub(padding); + } else if dir.focus >= self.scrolltop + height - padding { + self.scrolltop = dir.focus + padding - height + 1; + }; + } + }; + dir.nodes .iter() .enumerate() - .skip(height * (dir.focus / height.max(1))) + .skip(self.scrolltop) .take(height) .map(|(index, node)| { let is_focused = dir.focus == index; From 6d7ccce2825fe6b5ade7ceb7629df8ceb3fff1f6 Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Wed, 1 May 2024 15:30:20 +0530 Subject: [PATCH 21/28] Pass scrolltop in custom Lua function --- docs/en/src/general-config.md | 11 ++++++-- docs/en/src/layout.md | 8 ++++++ docs/en/src/upgrade-guide.md | 2 ++ src/compat.rs | 3 ++ src/init.lua | 52 +++++++++++++++++------------------ src/ui.rs | 9 +++--- 6 files changed, 53 insertions(+), 32 deletions(-) diff --git a/docs/en/src/general-config.md b/docs/en/src/general-config.md index a986348..b559f3e 100644 --- a/docs/en/src/general-config.md +++ b/docs/en/src/general-config.md @@ -42,9 +42,16 @@ Set it to `true` if you want to hide all remaps in the help menu. Type: boolean -#### xplr.config.general.vimlike_scrolling +#### xplr.config.general.paginated_scrolling -Set it to `true` if you want vim-like scrolling. +Set it to `true` if you want paginated scrolling. + +Type: boolean + +#### xplr.config.general.scroll_padding + +Set the padding value to the scroll area. +Only applicable when `xplr.config.general.paginated_scrolling = false`. Type: boolean diff --git a/docs/en/src/layout.md b/docs/en/src/layout.md index ec603a8..a053193 100644 --- a/docs/en/src/layout.md +++ b/docs/en/src/layout.md @@ -495,6 +495,7 @@ It contains the following information: - [layout_size][37] - [screen_size][37] +- [scrolltop][57] - [app][38] ### Size @@ -508,6 +509,12 @@ It contains the following information: Every field is of integer type. +### scrolltop + +Type: integer + +The start index of the visible nodes in the table. + ### app This is a lightweight version of the [Lua Context][39]. In this context, the @@ -587,3 +594,4 @@ Hence, only the following fields are available. [54]: borders.md#border-type [55]: #customlayout [56]: sum-type.md +[57]: #scrolltop diff --git a/docs/en/src/upgrade-guide.md b/docs/en/src/upgrade-guide.md index af56522..2009b04 100644 --- a/docs/en/src/upgrade-guide.md +++ b/docs/en/src/upgrade-guide.md @@ -132,6 +132,8 @@ compatibility. `xplr.config.general.paginated_scrolling = true` to revert back to the paginated scrolling. - Set `xplr.config.general.scroll_padding` to customize the scroll padding. + - The calculated `scrolltop` value will be passed as part of the + `Content Rendeder Argument` in `Dynamic` layout renderer functions. Thanks to @noahmayr for contributing to a major part of this release. diff --git a/src/compat.rs b/src/compat.rs index 640503b..469777c 100644 --- a/src/compat.rs +++ b/src/compat.rs @@ -85,6 +85,7 @@ pub fn draw_custom_content( app: app.to_lua_ctx_light(), layout_size: layout_size.into(), screen_size: ui.screen_size.into(), + scrolltop: ui.scrolltop as u16, }; let render = lua::serialize(ui.lua, &ctx) @@ -121,6 +122,7 @@ pub fn draw_custom_content( app: app.to_lua_ctx_light(), layout_size: layout_size.into(), screen_size: ui.screen_size.into(), + scrolltop: ui.scrolltop as u16, }; let items = lua::serialize(ui.lua, &ctx) @@ -182,6 +184,7 @@ pub fn draw_custom_content( app: app.to_lua_ctx_light(), layout_size: layout_size.into(), screen_size: ui.screen_size.into(), + scrolltop: ui.scrolltop as u16, }; let rows = lua::serialize(ui.lua, &ctx) diff --git a/src/init.lua b/src/init.lua index 0e2ed3c..e5b93c9 100644 --- a/src/init.lua +++ b/src/init.lua @@ -96,8 +96,8 @@ xplr.config.general.hide_remaps_in_help_menu = false -- Type: boolean xplr.config.general.paginated_scrolling = false --- Set the padding value to the scroll area. Only applicable when --- `xplr.config.general.paginated_scrolling` is set to `false`. +-- Set the padding value to the scroll area. +-- Only applicable when `xplr.config.general.paginated_scrolling = false`. -- -- Type: boolean xplr.config.general.scroll_padding = 5 @@ -489,7 +489,7 @@ xplr.config.general.sort_and_filter_ui.search_identifiers = { -- -- Type: nullable string xplr.config.general.sort_and_filter_ui.search_direction_identifiers.ordered.format = -"↓" + "↓" -- The shape of unordered indicator for search ordering identifiers in Sort & filter panel. -- @@ -687,7 +687,7 @@ xplr.config.general.panel_ui.sort_and_filter.border_style = {} -- Type: nullable list of [Node Sorter](https://xplr.dev/en/sorting#node-sorter-applicable) xplr.config.general.initial_sorting = { { sorter = "ByCanonicalIsDir", reverse = true }, - { sorter = "ByIRelativePath", reverse = false }, + { sorter = "ByIRelativePath", reverse = false }, } -- The name of one of the modes to use when xplr loads. @@ -1336,23 +1336,23 @@ xplr.config.modes.builtin.default = { } xplr.config.modes.builtin.default.key_bindings.on_key["v"] = - xplr.config.modes.builtin.default.key_bindings.on_key["space"] + xplr.config.modes.builtin.default.key_bindings.on_key["space"] xplr.config.modes.builtin.default.key_bindings.on_key["V"] = - xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-a"] + xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-a"] xplr.config.modes.builtin.default.key_bindings.on_key["/"] = - xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-f"] + xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-f"] xplr.config.modes.builtin.default.key_bindings.on_key["h"] = - xplr.config.modes.builtin.default.key_bindings.on_key["left"] + xplr.config.modes.builtin.default.key_bindings.on_key["left"] xplr.config.modes.builtin.default.key_bindings.on_key["j"] = - xplr.config.modes.builtin.default.key_bindings.on_key["down"] + xplr.config.modes.builtin.default.key_bindings.on_key["down"] xplr.config.modes.builtin.default.key_bindings.on_key["k"] = - xplr.config.modes.builtin.default.key_bindings.on_key["up"] + xplr.config.modes.builtin.default.key_bindings.on_key["up"] xplr.config.modes.builtin.default.key_bindings.on_key["l"] = - xplr.config.modes.builtin.default.key_bindings.on_key["right"] + xplr.config.modes.builtin.default.key_bindings.on_key["right"] xplr.config.modes.builtin.default.key_bindings.on_key["tab"] = - xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-i"] -- compatibility workaround + xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-i"] -- compatibility workaround xplr.config.modes.builtin.default.key_bindings.on_key["?"] = - xplr.config.general.global_key_bindings.on_key["f1"] + xplr.config.general.global_key_bindings.on_key["f1"] -- The builtin debug error mode. -- @@ -2004,9 +2004,9 @@ xplr.config.modes.builtin.number = { } xplr.config.modes.builtin.number.key_bindings.on_key["j"] = - xplr.config.modes.builtin.number.key_bindings.on_key["down"] + xplr.config.modes.builtin.number.key_bindings.on_key["down"] xplr.config.modes.builtin.number.key_bindings.on_key["k"] = - xplr.config.modes.builtin.number.key_bindings.on_key["up"] + xplr.config.modes.builtin.number.key_bindings.on_key["up"] -- The builtin go to mode. -- @@ -2490,9 +2490,9 @@ xplr.config.modes.builtin.search = { } xplr.config.modes.builtin.search.key_bindings.on_key["ctrl-n"] = - xplr.config.modes.builtin.search.key_bindings.on_key["down"] + xplr.config.modes.builtin.search.key_bindings.on_key["down"] xplr.config.modes.builtin.search.key_bindings.on_key["ctrl-p"] = - xplr.config.modes.builtin.search.key_bindings.on_key["up"] + xplr.config.modes.builtin.search.key_bindings.on_key["up"] -- The builtin filter mode. -- @@ -3172,7 +3172,7 @@ xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m) r = r .. "×" else local symlink_path = - xplr.util.shorten(m.symlink.absolute_path, { base = m.parent }) + xplr.util.shorten(m.symlink.absolute_path, { base = m.parent }) if m.symlink.is_dir then symlink_path = symlink_path .. "/" end @@ -3194,14 +3194,14 @@ xplr.fn.builtin.fmt_general_table_row_cols_2 = function(m) local T = xplr.util.paint("T", { fg = "Red" }) return xplr.util - .permissions_rwx(m.permissions) - :gsub("r", r) - :gsub("w", w) - :gsub("x", x) - :gsub("s", s) - :gsub("S", S) - :gsub("t", t) - :gsub("T", T) + .permissions_rwx(m.permissions) + :gsub("r", r) + :gsub("w", w) + :gsub("x", x) + :gsub("s", s) + :gsub("S", S) + :gsub("t", t) + :gsub("T", T) end -- Renders the fourth column in the table diff --git a/src/ui.rs b/src/ui.rs index dfebd7c..18ffcb3 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -84,6 +84,7 @@ pub struct ContentRendererArg { pub app: app::LuaContextLight, pub screen_size: Rect, pub layout_size: Rect, + pub scrolltop: u16, } #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] @@ -764,8 +765,7 @@ impl UI<'_> { let config = panel_config.default.clone().extend(&panel_config.table); let app_config = app.config.clone(); let header_height = app_config.general.table.header.height.unwrap_or(1); - let height: usize = - (layout_size.height.max(header_height + 2) - (header_height + 2)).into(); + let height: usize = layout_size.height.saturating_sub(header_height + 2).into(); let row_style = app_config.general.table.row.style.clone(); let rows = app @@ -777,12 +777,12 @@ impl UI<'_> { // Paginated scrolling self.scrolltop = height * (dir.focus / height.max(1)) } else { - // vim-like-scrolling + // Vim-like-scrolling self.scrolltop = match dir.focus.cmp(&self.scrolltop) { Ordering::Greater => { // Scrolling down if dir.focus >= self.scrolltop + height { - dir.focus - height + 1 + dir.focus.saturating_sub(height + 1) } else { self.scrolltop } @@ -1326,6 +1326,7 @@ impl UI<'_> { app: app.to_lua_ctx_light(), layout_size: layout_size.into(), screen_size: self.screen_size.into(), + scrolltop: self.scrolltop as u16, }; let panel: CustomPanel = lua::serialize(self.lua, &ctx) From c2a11059c8d0061b660bea1be33e7dce7d7c2aaf Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Wed, 1 May 2024 16:59:13 +0530 Subject: [PATCH 22/28] Add yazi an alternative --- Cargo.toml | 2 -- docs/en/src/alternatives.md | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5f552be..df29832 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,5 +92,3 @@ strip = true [features] default = ["vendored-lua"] vendored-lua = ["mlua/vendored"] - - diff --git a/docs/en/src/alternatives.md b/docs/en/src/alternatives.md index 273e9d8..b763817 100644 --- a/docs/en/src/alternatives.md +++ b/docs/en/src/alternatives.md @@ -15,6 +15,7 @@ These are the alternative TUI/CLI file managers/explorers you might want to chec - [clifm][11] - [clifm][12] (non curses) - [felix][14] +- [yazi][15] [add more][13] @@ -32,3 +33,4 @@ These are the alternative TUI/CLI file managers/explorers you might want to chec [12]: https://github.com/leo-arch/clifm [13]: community.md [14]: https://github.com/kyoheiu/felix +[15]: https://github.com/sxyazi/yazi From ad8afa9d38bef21ba72abcee03759db2725cc932 Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Wed, 1 May 2024 17:04:23 +0530 Subject: [PATCH 23/28] Update deps --- Cargo.lock | 297 +++++++++++++++++++++++++++-------------------------- 1 file changed, 154 insertions(+), 143 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ef9bdd..11929a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,18 +16,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "android-tzdata" @@ -69,9 +69,9 @@ checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "arrayvec" @@ -96,9 +96,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "beef" @@ -114,9 +114,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "serde", ] @@ -134,9 +134,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "cassowary" @@ -161,9 +161,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.90" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" [[package]] name = "cfg-if" @@ -173,16 +173,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -214,9 +214,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.3" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", ] @@ -355,7 +355,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "crossterm_winapi", "libc", "mio", @@ -501,9 +501,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "either" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "equivalent" @@ -557,9 +557,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -568,9 +568,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", @@ -578,9 +578,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", @@ -647,9 +647,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", @@ -658,9 +658,9 @@ dependencies = [ [[package]] name = "indoc" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "is-terminal" @@ -693,9 +693,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jf" @@ -724,9 +724,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libm" @@ -736,13 +736,12 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", - "redox_syscall", ] [[package]] @@ -753,9 +752,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -796,9 +795,9 @@ dependencies = [ [[package]] name = "luajit-src" -version = "210.5.7+d06beb0" +version = "210.5.8+5790d25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d251fdacdabbf87704cf48ac1f8b1eb23d6e10855c3ee08e5beb25b4be2e9e4" +checksum = "441f18d9ad792e871fc2f7f2cb8902c386f6f56fdbddef3b835b61475e375346" dependencies = [ "cc", "which", @@ -806,9 +805,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memoffset" @@ -855,9 +854,9 @@ dependencies = [ [[package]] name = "mlua" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868d02cb5eb97761bbf6bd6922c1c7a88b8ea252bbf43bd8350a0bf8497a1fc0" +checksum = "6d9bed6bce296397a9d6a86f995dd10a547a4e6949825d45225906bdcbfe7367" dependencies = [ "bstr", "erased-serde", @@ -871,9 +870,9 @@ dependencies = [ [[package]] name = "mlua-sys" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2847b42764435201d8cbee1f517edb79c4cca4181877b90047587c89e1b7bce4" +checksum = "d16a9ba1dd2c6ac971b204262d434c24d65067038598f0638b64e5dca28d52b8" dependencies = [ "cc", "cfg-if", @@ -979,9 +978,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -989,15 +988,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -1099,29 +1098,29 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "ratatui" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcb12f8fbf6c62614b0d56eb352af54f6a22410c3b079eb53ee93c7b97dd31d8" +checksum = "a564a852040e82671dc50a37d88f3aa83bbc690dfc6844cfe7a2591620206a80" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cassowary", "compact_str", "crossterm", @@ -1138,9 +1137,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -1158,18 +1157,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", ] [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", @@ -1178,9 +1177,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -1201,9 +1200,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rustc-hash" @@ -1213,11 +1212,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -1226,9 +1225,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "ryu" @@ -1253,9 +1252,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" dependencies = [ "serde_derive", ] @@ -1272,20 +1271,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.60", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -1294,9 +1293,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.33" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0623d197252096520c6f2a5e1171ee436e5af99a5d7caa2891e55e61950e6d9" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", "itoa", @@ -1328,9 +1327,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -1362,9 +1361,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smawk" @@ -1384,12 +1383,12 @@ dependencies = [ [[package]] name = "stability" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd1b177894da2a2d9120208c3386066af06a488255caabc5de8ddca22dbc3ce" +checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a" dependencies = [ "quote", - "syn 1.0.109", + "syn 2.0.60", ] [[package]] @@ -1423,7 +1422,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.53", + "syn 2.0.60", ] [[package]] @@ -1439,9 +1438,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.53" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -1478,22 +1477,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.60", ] [[package]] @@ -1508,9 +1507,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -1531,9 +1530,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -1612,9 +1611,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "unicode_categories" @@ -1707,7 +1706,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.60", "wasm-bindgen-shared", ] @@ -1729,7 +1728,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1752,15 +1751,14 @@ dependencies = [ [[package]] name = "which" -version = "6.0.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c" +checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" dependencies = [ "either", "home", - "once_cell", "rustix", - "windows-sys 0.52.0", + "winsafe", ] [[package]] @@ -1781,11 +1779,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -1800,7 +1798,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1818,7 +1816,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1838,17 +1836,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -1859,9 +1858,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -1871,9 +1870,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -1883,9 +1882,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -1895,9 +1900,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -1907,9 +1912,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -1919,9 +1924,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -1931,9 +1936,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] name = "xdg" @@ -1994,5 +2005,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.60", ] From a48dae008c47465184bf1b8fbe5dbcd6b36eb1de Mon Sep 17 00:00:00 2001 From: Abhinav Natarajan Date: Fri, 3 May 2024 03:23:18 +0100 Subject: [PATCH 24/28] Fix node type resolution Fix node_type for directory with extension --- src/config.rs | 2 +- src/lua/util.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 27ceeaf..5f34a32 100644 --- a/src/config.rs +++ b/src/config.rs @@ -104,7 +104,7 @@ impl NodeTypesConfig { node_type = node_type.extend(conf); } - if let Some(conf) = self.extension.get(&node.extension) { + if let (Some(conf), false) = (self.extension.get(&node.extension), node.is_dir) { node_type = node_type.extend(conf); } diff --git a/src/lua/util.rs b/src/lua/util.rs index 63abbe9..6d761e3 100644 --- a/src/lua/util.rs +++ b/src/lua/util.rs @@ -160,7 +160,7 @@ pub fn is_dir<'a>(util: Table<'a>, lua: &Lua) -> Result> { /// ``` pub fn is_file<'a>(util: Table<'a>, lua: &Lua) -> Result> { let func = - lua.create_function(move |_, path: String| Ok(PathBuf::from(path).is_dir()))?; + lua.create_function(move |_, path: String| Ok(PathBuf::from(path).is_file()))?; util.set("is_file", func)?; Ok(util) } From e15c1e8a8ce331e4336d1c5cfd407878da525ee5 Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Fri, 3 May 2024 11:09:09 +0530 Subject: [PATCH 25/28] Lock ratatui --- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11929a4..cb863a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1116,9 +1116,9 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.26.2" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a564a852040e82671dc50a37d88f3aa83bbc690dfc6844cfe7a2591620206a80" +checksum = "bcb12f8fbf6c62614b0d56eb352af54f6a22410c3b079eb53ee93c7b97dd31d8" dependencies = [ "bitflags 2.5.0", "cassowary", @@ -1383,12 +1383,12 @@ dependencies = [ [[package]] name = "stability" -version = "0.2.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a" +checksum = "ebd1b177894da2a2d9120208c3386066af06a488255caabc5de8ddca22dbc3ce" dependencies = [ "quote", - "syn 2.0.60", + "syn 1.0.109", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index df29832..7aa1739 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,7 @@ version = "2.0.4" default-features = false [dependencies.tui] -version = "0.26.1" +version = "=0.26.1" # https://github.com/ratatui-org/ratatui/issues/1032 default-features = false features = ['crossterm', 'serde'] package = 'ratatui' From 89d7bccce866c028cb8a5efebef5558608495ec0 Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Sun, 5 May 2024 19:56:57 +0530 Subject: [PATCH 26/28] Update docs --- README.md | 15 +-------------- RELEASE.md | 4 ++-- docs/en/src/SUMMARY.md | 4 ---- docs/en/src/alternatives.md | 2 +- docs/en/src/awesome-hacks.md | 3 +-- docs/en/src/community.md | 12 ------------ docs/en/src/contribute.md | 32 -------------------------------- docs/en/src/install.md | 18 ------------------ docs/en/src/introduction.md | 3 --- docs/en/src/sum-type.md | 6 ------ docs/en/src/upgrade-guide.md | 3 --- docs/en/src/writing-plugins.md | 6 +----- docs/script/deploy-cf.sh | 11 +++++++++++ 13 files changed, 17 insertions(+), 102 deletions(-) delete mode 100644 docs/en/src/community.md delete mode 100644 docs/en/src/contribute.md create mode 100644 docs/script/deploy-cf.sh diff --git a/README.md b/README.md index 2d42ca6..9758bc6 100644 --- a/README.md +++ b/README.md @@ -12,18 +12,6 @@ A hackable, minimal, fast TUI file explorer - - - - - -Matrix - - - - - -

@@ -38,7 +26,6 @@ https://user-images.githubusercontent.com/11632726/166747867-8a4573f2-cb2f-43a6- [Hacks] [Plugins] [Integrations] - [Community] xplr is a terminal UI based file explorer that aims to increase our terminal @@ -60,7 +47,7 @@ integration][15], enabling you to achieve insane terminal productivity. - [[Article] What is a TUI file explorer & why would you need one? ~ xplr.stck.me](https://xplr.stck.me/post/25252/What-is-a-TUI-file-explorer-why-would-you-need-one) -- [[Article] FOSSPicks - Linux Magazine](https://www.linux-magazine.com/Issues/2022/258/FOSSPicks/(offset)/6) +- [[Article] FOSSPicks - Linux Magazine]() ## Packaging diff --git a/RELEASE.md b/RELEASE.md index 8f30cda..75bdd3e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -3,8 +3,8 @@ See [install.md](./docs/en/src/install.md#build-from-source) Note: xplr ships with vendored luajit. If the platform can't compile this, -you need to grep out the feature "vendored" from the "mlua" dependency -specified in [Cargo.toml](./Cargo.toml), and static link luajit yourself. +you need to compile using `--no-default-features` argument to avoid using +vendored luajit, so that you can static link luajit yourself. # Release diff --git a/docs/en/src/SUMMARY.md b/docs/en/src/SUMMARY.md index 210136c..2087022 100644 --- a/docs/en/src/SUMMARY.md +++ b/docs/en/src/SUMMARY.md @@ -39,8 +39,6 @@ - [Awesome Integrations][20] - [Alternatives][22] - [Upgrade Guide][23] -- [Community][24] -- [Contribute][25] [1]: introduction.md [2]: quickstart.md @@ -64,8 +62,6 @@ [20]: awesome-integrations.md [22]: alternatives.md [23]: upgrade-guide.md -[24]: community.md -[25]: contribute.md [26]: column-renderer.md [27]: key-bindings.md [28]: configure-key-bindings.md diff --git a/docs/en/src/alternatives.md b/docs/en/src/alternatives.md index b763817..0fe9c8c 100644 --- a/docs/en/src/alternatives.md +++ b/docs/en/src/alternatives.md @@ -31,6 +31,6 @@ These are the alternative TUI/CLI file managers/explorers you might want to chec [10]: https://git.2f30.org/noice/ [11]: https://github.com/pasqu4le/clifm [12]: https://github.com/leo-arch/clifm -[13]: community.md +[13]: https://github.com/sayanarijit/xplr/edit/dev/docs/en/src/alternatives.md [14]: https://github.com/kyoheiu/felix [15]: https://github.com/sxyazi/yazi diff --git a/docs/en/src/awesome-hacks.md b/docs/en/src/awesome-hacks.md index fa4be46..8cfb030 100644 --- a/docs/en/src/awesome-hacks.md +++ b/docs/en/src/awesome-hacks.md @@ -6,7 +6,7 @@ too small or too niche for a full fledge [plugin][2]. Do you have something cool to share? -[Edit this file][3] or [share them here][4] or [let us know][5]. +[Edit this file][3] or [share them here][4]. You can try these hacks by writing them to a file, say `hack.lua` and passing it to xplr with `--extra-config` or `-C`. @@ -526,7 +526,6 @@ xplr.config.modes.builtin.default.key_bindings.on_key.T = { [2]: plugin.md [3]: https://github.com/sayanarijit/xplr/edit/main/docs/en/src/awesome-hacks.md [4]: https://github.com/sayanarijit/xplr/discussions/categories/show-and-tell -[5]: community.md [6]: https://gifyu.com/image/rGSR [7]: https://s4.gifyu.com/images/xplr-bookmark.gif [8]: https://github.com/sayanarijit diff --git a/docs/en/src/community.md b/docs/en/src/community.md deleted file mode 100644 index a280b7a..0000000 --- a/docs/en/src/community.md +++ /dev/null @@ -1,12 +0,0 @@ -# Community - -Building an active community of awesome people and learning stuff together is -one of my reasons to publish this tool and maintain it. Hence, please feel free -to reach out via your preferred way. - -- Real-time chat lovers can join our [**matrix room**][3] or [**discord channel**][1]. -- Forum discussion veterans can [**start a new GitHub discussion**][2]. - -[1]: https://discord.gg/JmasSPCcz3 -[2]: https://github.com/sayanarijit/xplr/discussions -[3]: https://matrix.to/#/#xplr-pub:matrix.org diff --git a/docs/en/src/contribute.md b/docs/en/src/contribute.md deleted file mode 100644 index a01ddba..0000000 --- a/docs/en/src/contribute.md +++ /dev/null @@ -1,32 +0,0 @@ -If you like xplr, and want to contribute, that would be really awesome. - -You can contribute to this project in the following ways - -- Contribute your time and expertise (read [CONTRIBUTING.md][1] for instructions). - - - **Developers:** You can help me improve my code, fix things, implement features etc. - - **Repository maintainers:** You can save the users from the pain of managing xplr in their system manually. - - **Code Reviewers:** Teach me your ways of code. - - **Designers:** You can make the logo even more awesome, donate stickers and blog post worthy pictures. - - **Bloggers, YouTubers & broadcasters:** You can help spread the word. - -- Contribute by donating or sponsoring me via any of the following ways. - - [GitHub Sponsors][5] - - [Open Collective][2] - - [ko-fi][3] - - [liberapay][6] - - [PayPal][7] - -For further queries or concern related to `xplr`, [just ask us][4]. - -### Backers - - - -[1]: https://github.com/sayanarijit/xplr/blob/main/CONTRIBUTING.md -[2]: https://opencollective.com/xplr -[3]: https://ko-fi.com/sayanarijit -[4]: community.md -[5]: https://github.com/sponsors/sayanarijit?o=esb -[6]: https://liberapay.com/sayanarijit -[7]: https://paypal.me/sayanarijit diff --git a/docs/en/src/install.md b/docs/en/src/install.md index 114e7ee..518247e 100644 --- a/docs/en/src/install.md +++ b/docs/en/src/install.md @@ -238,23 +238,6 @@ cargo build --locked --release --bin xplr sudo cp target/release/xplr /usr/local/bin/ ``` -## Android - -### [Termux][24] - -```bash -pkg install rust make binutils -cargo install --locked xplr - -# Run -~/.cargo/bin/xplr -``` - -> Please note that xplr isn't heavily tested on Termux, hence things might need -> a little tweaking and fixing for a smooth user experience. - -![termux demo][23] - [1]: #direct-download [2]: #from-cratesio [3]: #build-from-source @@ -278,7 +261,6 @@ cargo install --locked xplr [21]: https://www.gnu.org/software/make/ [22]: https://git-scm.com/ [23]: https://github.com/sayanarijit/xplr/assets/11632726/3b61e8c8-76f0-48e8-8734-50e9e7e495b7 -[24]: https://termux.dev/ [25]: https://gifyu.com/image/tF2D [26]: https://github.com/sayanarijit/xplr/releases/latest/download/xplr-linux-musl.tar.gz [27]: https://pkgs.alpinelinux.org/packages?name=xplr diff --git a/docs/en/src/introduction.md b/docs/en/src/introduction.md index d48d8ce..5e68489 100644 --- a/docs/en/src/introduction.md +++ b/docs/en/src/introduction.md @@ -84,8 +84,6 @@ Some of the coolest features xplr provide beside the basic stuff: (`:` `q` `s`). - Quit with failure (`ctrl-c`). -**Q.** What features should be added here? [let us know][20]. - [1]: layouts.md [2]: configure-key-bindings.md [3]: awesome-plugins.md @@ -105,6 +103,5 @@ Some of the coolest features xplr provide beside the basic stuff: [17]: node_types.md [18]: https://github.com/sayanarijit/xplr/blob/main/src/init.lua [19]: messages.md#startfifo -[20]: community.md [21]: messages.md#virtual-root [22]: configuration.md#hooks diff --git a/docs/en/src/sum-type.md b/docs/en/src/sum-type.md index 26e93dd..f7df1a5 100644 --- a/docs/en/src/sum-type.md +++ b/docs/en/src/sum-type.md @@ -90,13 +90,7 @@ And then we'd go on documenting whatever `Layout Config` is. So, there you go. This is exactly what sum types are - glorified enums that can have nested types in each branch. ---- - -If you're still confused about something, or if you found an error in this -explanation, feel free to [discuss together][5]. - [1]: https://en.wikipedia.org/wiki/Tagged_union [2]: layout.md [3]: message.md [4]: style.md#color -[5]: community.md diff --git a/docs/en/src/upgrade-guide.md b/docs/en/src/upgrade-guide.md index 2009b04..511b965 100644 --- a/docs/en/src/upgrade-guide.md +++ b/docs/en/src/upgrade-guide.md @@ -221,8 +221,6 @@ Thanks to @noahmayr for contributing to a major part of this release. - ScrollUpHalf ---- { - ScrollDownHalf -- } -Like this project so far? **[Please consider contributing][5]**. - #### [v0.17.6][45] -> [v0.18.0][46] - Key binding `f` `r` and `f` `R` will now filter using regex. @@ -488,7 +486,6 @@ Else do the following: [2]: https://github.com/sayanarijit/xplr/releases/tag/v0.13.7 [3]: https://github.com/sayanarijit/xplr/releases/tag/v0.14.7 [4]: https://github.com/sayanarijit/xplr/pull/229#issue-662426960 -[5]: contribute.md [6]: https://github.com/sayanarijit/xplr/releases/tag/v0.12.1 [7]: https://docs.rs/xplr/latest/xplr/app/enum.ExternalMsg.html#variant.CallLua [8]: https://docs.rs/xplr/latest/xplr/app/enum.ExternalMsg.html#variant.CallLuaSilently diff --git a/docs/en/src/writing-plugins.md b/docs/en/src/writing-plugins.md index 21786e3..e56e038 100644 --- a/docs/en/src/writing-plugins.md +++ b/docs/en/src/writing-plugins.md @@ -70,8 +70,6 @@ Visit [Awesome Plugins][5] for xplr plugin examples. - [Tip: A list of hacks yet to make it as Lua plugins][15] - [Tip: Some UI and themeing tips][12] -- [Tip: A list of handy utility functions][13] -- [Tip: Share tips and tricks working with Lua][14] - [Tutorial: Adding a New Mode][6] - [Example: Using Environment Variables and Pipes][7] - [Example: Using Lua Function Calls][8] @@ -92,7 +90,5 @@ Visit [Awesome Plugins][5] for xplr plugin examples. [10]: column-renderer.md#example-customizing-table-renderer [11]: layout.md#example-render-a-custom-dynamic-table [12]: https://github.com/sayanarijit/xplr/discussions/274 -[13]: https://github.com/sayanarijit/xplr/discussions/273 -[14]: https://github.com/sayanarijit/xplr/discussions/250 -[15]: https://github.com/sayanarijit/xplr/wiki/Hacks +[15]: awesome-hacks.md [16]: https://github.com/sayanarijit/xplr/discussions/529#discussioncomment-4073734 diff --git a/docs/script/deploy-cf.sh b/docs/script/deploy-cf.sh new file mode 100644 index 0000000..1acd71a --- /dev/null +++ b/docs/script/deploy-cf.sh @@ -0,0 +1,11 @@ +v="0.4.37" + +curl -L https://github.com/rust-lang/mdBook/releases/download/v$v/mdbook-v$v-x86_64-unknown-linux-gnu.tar.gz -o mdbook.tgz \ + && tar xzvf mdbook.tgz \ + && ./mdbook build docs/en \ + && mkdir dist \ + && mv -v docs/en/book/html dist/en \ + && mv -v assets dist \ + && mv -v docs/landing/index.html docs/landing/css docs/landing/js dist \ + && rm -v mdbook \ + && rm -v mdbook.tgz From 41648ced34c654f04e35183967607ad39ebbe363 Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Sun, 5 May 2024 20:01:36 +0530 Subject: [PATCH 27/28] Linting fixes --- src/app.rs | 2 +- src/runner.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app.rs b/src/app.rs index ea6384e..e4700df 100644 --- a/src/app.rs +++ b/src/app.rs @@ -387,7 +387,7 @@ impl App { } if let Some(sorters) = &config.general.initial_sorting { - explorer_config.sorters = sorters.clone(); + explorer_config.sorters.clone_from(sorters); }; let hostname = gethostname().to_string_lossy().to_string(); diff --git a/src/runner.rs b/src/runner.rs index c5e2c1e..4470135 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -474,7 +474,7 @@ impl Runner { } if app.pwd != last_pwd { - last_pwd = app.pwd.clone(); + last_pwd.clone_from(&app.pwd); // $PWD watcher tx_pwd_watcher.send(app.pwd.clone())?; From 805e1594edf34782b4b7c55bc6705d52cfc0b969 Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Sun, 5 May 2024 23:31:38 +0530 Subject: [PATCH 28/28] Fix vim scrolling --- src/ui.rs | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index 18ffcb3..5ff5d99 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -778,27 +778,15 @@ impl UI<'_> { self.scrolltop = height * (dir.focus / height.max(1)) } else { // Vim-like-scrolling - self.scrolltop = match dir.focus.cmp(&self.scrolltop) { - Ordering::Greater => { - // Scrolling down - if dir.focus >= self.scrolltop + height { - dir.focus.saturating_sub(height + 1) - } else { - self.scrolltop - } - } - Ordering::Less => dir.focus, - Ordering::Equal => self.scrolltop, - }; - - // Add padding if possible let padding = app.config.general.scroll_padding; - if padding != 0 { - if dir.focus < self.scrolltop + padding { - self.scrolltop = dir.focus.saturating_sub(padding); - } else if dir.focus >= self.scrolltop + height - padding { - self.scrolltop = dir.focus + padding - height + 1; - }; + if dir.focus >= (self.scrolltop + height).saturating_sub(padding) { + // Scrolling down + self.scrolltop = (dir.focus + padding + 1) + .saturating_sub(height) + .min(dir.total.saturating_sub(height)); + } else if dir.focus < self.scrolltop + padding { + // Scrolling up + self.scrolltop = dir.focus.saturating_sub(padding); } };