Adds vim-like scrolling

pull/704/head
Ahmed ElSamhaa 2 months ago
parent 7c6dffc2c6
commit e834242f5d

@ -121,7 +121,7 @@ fn draw_benchmark(c: &mut Criterion) {
c.bench_function("draw on terminal", |b| { c.bench_function("draw on terminal", |b| {
b.iter(|| { b.iter(|| {
terminal.draw(|f| ui::draw(f, &app, &lua)).unwrap(); terminal.draw(|f| ui::draw(f, &mut app, &lua)).unwrap();
}) })
}); });

@ -752,7 +752,10 @@ impl App {
self.explorer_config.clone(), self.explorer_config.clone(),
self.pwd.clone().into(), self.pwd.clone().into(),
focus.as_ref().map(PathBuf::from), 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), Ok(dir) => self.set_directory(dir),
Err(e) => { Err(e) => {
@ -791,7 +794,7 @@ impl App {
} }
} }
dir.focus = 0; dir.scroll_state.set_focus(0);
if save_history { if save_history {
if let Some(n) = self.focused_node() { if let Some(n) = self.focused_node() {
@ -809,7 +812,7 @@ impl App {
history = history.push(n.absolute_path.clone()); 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() { if let Some(n) = dir.focused_node() {
self.history = history.push(n.absolute_path.clone()); self.history = history.push(n.absolute_path.clone());
@ -822,14 +825,15 @@ impl App {
let bounded = self.config.general.enforce_bounded_index_navigation; let bounded = self.config.general.enforce_bounded_index_navigation;
if let Some(dir) = self.directory_buffer_mut() { if let Some(dir) = self.directory_buffer_mut() {
dir.focus = if dir.focus == 0 { if dir.scroll_state.current_focus == 0 {
if bounded { if bounded {
dir.focus dir.scroll_state.set_focus(dir.scroll_state.current_focus);
} else { } else {
dir.total.saturating_sub(1) dir.scroll_state.set_focus(dir.total.saturating_sub(1));
} }
} else { } else {
dir.focus.saturating_sub(1) dir.scroll_state
.set_focus(dir.scroll_state.current_focus.saturating_sub(1));
}; };
}; };
Ok(self) Ok(self)
@ -882,7 +886,8 @@ impl App {
history = history.push(n.absolute_path.clone()); 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() { if let Some(n) = self.focused_node() {
self.history = history.push(n.absolute_path.clone()); self.history = history.push(n.absolute_path.clone());
} }
@ -907,14 +912,15 @@ impl App {
let bounded = self.config.general.enforce_bounded_index_navigation; let bounded = self.config.general.enforce_bounded_index_navigation;
if let Some(dir) = self.directory_buffer_mut() { 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 { if bounded {
dir.focus dir.scroll_state.set_focus(dir.scroll_state.current_focus);
} else { } else {
0 dir.scroll_state.set_focus(0);
} }
} else { } else {
dir.focus + 1 dir.scroll_state
.set_focus(dir.scroll_state.current_focus + 1);
} }
}; };
Ok(self) Ok(self)
@ -967,10 +973,12 @@ impl App {
history = history.push(n.absolute_path.clone()); history = history.push(n.absolute_path.clone());
} }
dir.focus = dir dir.scroll_state.set_focus(
.focus dir.scroll_state
.saturating_add(index) .current_focus
.min(dir.total.saturating_sub(1)); .saturating_add(index)
.min(dir.total.saturating_sub(1)),
);
if let Some(n) = self.focused_node() { if let Some(n) = self.focused_node() {
self.history = history.push(n.absolute_path.clone()); self.history = history.push(n.absolute_path.clone());
@ -1238,7 +1246,8 @@ impl App {
fn focus_by_index(mut self, index: usize) -> Result<Self> { fn focus_by_index(mut self, index: usize) -> Result<Self> {
let history = self.history.clone(); let history = self.history.clone();
if let Some(dir) = self.directory_buffer_mut() { 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() { if let Some(n) = self.focused_node() {
self.history = history.push(n.absolute_path.clone()); self.history = history.push(n.absolute_path.clone());
} }
@ -1275,7 +1284,7 @@ impl App {
history = history.push(n.absolute_path.clone()); history = history.push(n.absolute_path.clone());
} }
} }
dir_buf.focus = focus; dir_buf.scroll_state.set_focus(focus);
if save_history { if save_history {
if let Some(n) = dir_buf.focused_node() { if let Some(n) = dir_buf.focused_node() {
self.history = history.push(n.absolute_path.clone()); self.history = history.push(n.absolute_path.clone());

@ -2,31 +2,93 @@ use crate::node::Node;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::OffsetDateTime; use time::OffsetDateTime;
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct ScrollState {
pub current_focus: usize,
pub last_focus: Option<usize>,
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)] #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct DirectoryBuffer { pub struct DirectoryBuffer {
pub parent: String, pub parent: String,
pub nodes: Vec<Node>, pub nodes: Vec<Node>,
pub total: usize, pub total: usize,
pub focus: usize, pub scroll_state: ScrollState,
#[serde(skip, default = "now")] #[serde(skip, default = "now")]
pub explored_at: OffsetDateTime, pub explored_at: OffsetDateTime,
} }
impl DirectoryBuffer { impl DirectoryBuffer {
pub fn new(parent: String, nodes: Vec<Node>, focus: usize) -> Self { pub fn new(parent: String, nodes: Vec<Node>, current_focus: usize) -> Self {
let total = nodes.len(); let total = nodes.len();
Self { Self {
parent, parent,
nodes, nodes,
total, total,
focus, scroll_state: ScrollState {
current_focus,
last_focus: None,
skipped_rows: 0,
},
explored_at: now(), explored_at: now(),
} }
} }
pub fn focused_node(&self) -> Option<&Node> { pub fn focused_node(&self) -> Option<&Node> {
self.nodes.get(self.focus) self.nodes.get(self.scroll_state.current_focus)
} }
} }

@ -89,7 +89,7 @@ fn call(
let focus_index = app let focus_index = app
.directory_buffer .directory_buffer
.as_ref() .as_ref()
.map(|d| d.focus) .map(|d| d.scroll_state.current_focus)
.unwrap_or_default() .unwrap_or_default()
.to_string(); .to_string();
@ -279,7 +279,10 @@ impl Runner {
app.explorer_config.clone(), app.explorer_config.clone(),
app.pwd.clone().into(), app.pwd.clone().into(),
self.focused_path, 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_msg_in.clone(),
); );
tx_pwd_watcher.send(app.pwd.clone())?; tx_pwd_watcher.send(app.pwd.clone())?;
@ -430,7 +433,7 @@ impl Runner {
.map(|n| n.relative_path.clone().into()), .map(|n| n.relative_path.clone().into()),
app.directory_buffer app.directory_buffer
.as_ref() .as_ref()
.map(|d| d.focus) .map(|d| d.scroll_state.current_focus)
.unwrap_or(0), .unwrap_or(0),
tx_msg_in.clone(), tx_msg_in.clone(),
); );
@ -445,7 +448,7 @@ impl Runner {
.map(|n| n.relative_path.clone().into()), .map(|n| n.relative_path.clone().into()),
app.directory_buffer app.directory_buffer
.as_ref() .as_ref()
.map(|d| d.focus) .map(|d| d.scroll_state.current_focus)
.unwrap_or(0), .unwrap_or(0),
tx_msg_in.clone(), tx_msg_in.clone(),
); );
@ -493,7 +496,7 @@ impl Runner {
} }
// UI // UI
terminal.draw(|f| ui::draw(f, &app, &lua))?; terminal.draw(|f| ui::draw(f, &mut app, &lua))?;
} }
EnableMouse => { EnableMouse => {

@ -722,7 +722,7 @@ fn draw_table(
f: &mut Frame, f: &mut Frame,
screen_size: TuiRect, screen_size: TuiRect,
layout_size: TuiRect, layout_size: TuiRect,
app: &app::App, app: &mut app::App,
lua: &Lua, lua: &Lua,
) { ) {
let panel_config = &app.config.general.panel_ui; let panel_config = &app.config.general.panel_ui;
@ -735,15 +735,16 @@ fn draw_table(
let rows = app let rows = app
.directory_buffer .directory_buffer
.as_ref() .as_mut()
.map(|dir| { .map(|dir| {
dir.scroll_state.skipped_rows = dir.scroll_state.calc_skipped_rows(height, dir.total);
dir.nodes dir.nodes
.iter() .iter()
.enumerate() .enumerate()
.skip(height * (dir.focus / height.max(1))) .skip(dir.scroll_state.skipped_rows)
.take(height) .take(height)
.map(|(index, node)| { .map(|(index, node)| {
let is_focused = dir.focus == index; let is_focused = dir.scroll_state.current_focus == index;
let is_selected = app let is_selected = app
.selection .selection
@ -772,9 +773,13 @@ fn draw_table(
let node_type = app_config.node_types.get(node); let node_type = app_config.node_types.get(node);
let (relative_index, is_before_focus, is_after_focus) = let (relative_index, is_before_focus, is_after_focus) =
match dir.focus.cmp(&index) { match dir.scroll_state.current_focus.cmp(&index) {
Ordering::Greater => (dir.focus - index, true, false), Ordering::Greater => {
Ordering::Less => (index - dir.focus, false, true), (dir.scroll_state.current_focus - index, true, false)
}
Ordering::Less => {
(index - dir.scroll_state.current_focus, false, true)
}
Ordering::Equal => (0, false, false), Ordering::Equal => (0, false, false),
}; };
@ -1284,7 +1289,7 @@ pub fn draw_dynamic(
f: &mut Frame, f: &mut Frame,
screen_size: TuiRect, screen_size: TuiRect,
layout_size: TuiRect, layout_size: TuiRect,
app: &app::App, app: &mut app::App,
func: &str, func: &str,
lua: &Lua, lua: &Lua,
) { ) {
@ -1308,7 +1313,7 @@ pub fn draw_static(
f: &mut Frame, f: &mut Frame,
screen_size: TuiRect, screen_size: TuiRect,
layout_size: TuiRect, layout_size: TuiRect,
app: &app::App, app: &mut app::App,
panel: CustomPanel, panel: CustomPanel,
_lua: &Lua, _lua: &Lua,
) { ) {
@ -1402,7 +1407,7 @@ pub fn draw_layout(
f: &mut Frame, f: &mut Frame,
screen_size: TuiRect, screen_size: TuiRect,
layout_size: TuiRect, layout_size: TuiRect,
app: &app::App, app: &mut app::App,
lua: &Lua, lua: &Lua,
) { ) {
match layout { 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 screen_size = f.size();
let layout = app.mode.layout.as_ref().unwrap_or(&app.layout).to_owned(); let layout = app.mode.layout.as_ref().unwrap_or(&app.layout).to_owned();

Loading…
Cancel
Save