Skip to content

Commit

Permalink
vimlike_scrolling -> paginated_scrolling
Browse files Browse the repository at this point in the history
Inspired by @ElSamhaa 's PR #704
  • Loading branch information
sayanarijit committed May 1, 2024
1 parent ce52bcd commit 90df0a2
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 269 deletions.
3 changes: 2 additions & 1 deletion benches/criterion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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();
})
});

Expand Down
6 changes: 4 additions & 2 deletions docs/en/src/upgrade-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
1 change: 0 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
5 changes: 4 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
226 changes: 0 additions & 226 deletions src/directory_buffer.rs
Original file line number Diff line number Diff line change
@@ -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<usize>,
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,
Expand Down Expand Up @@ -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...
}
51 changes: 20 additions & 31 deletions src/explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ pub(crate) fn explore_sync(
parent: PathBuf,
focused_path: Option<PathBuf>,
fallback_focus: usize,
vimlike_scrolling: bool,
) -> Result<DirectoryBuffer> {
let nodes = explore(&parent, &config)?;
let focus_index = if config.searcher.is_some() {
Expand Down Expand Up @@ -74,33 +73,26 @@ pub(crate) fn explore_async(
parent: PathBuf,
focused_path: Option<PathBuf>,
fallback_focus: usize,
vimlike_scrolling: bool,
tx_msg_in: Sender<Task>,
) {
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.
})
});
}

Expand All @@ -109,15 +101,13 @@ pub(crate) fn explore_recursive_async(
parent: PathBuf,
focused_path: Option<PathBuf>,
fallback_focus: usize,
vimlike_scrolling: bool,
tx_msg_in: Sender<Task>,
) {
explore_async(
config.clone(),
parent.clone(),
focused_path,
fallback_focus,
vimlike_scrolling,
tx_msg_in.clone(),
);
if let Some(grand_parent) = parent.parent() {
Expand All @@ -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,
);
}
Expand All @@ -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());
}
Expand All @@ -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());
}
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 90df0a2

Please sign in to comment.