mirror of
https://github.com/sayanarijit/xplr.git
synced 2024-09-11 19:07:47 +03:00
Adds vim-like scrolling
This commit is contained in:
parent
7c6dffc2c6
commit
e834242f5d
@ -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();
|
||||
})
|
||||
});
|
||||
|
||||
|
45
src/app.rs
45
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<Self> {
|
||||
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());
|
||||
|
@ -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<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)]
|
||||
pub struct DirectoryBuffer {
|
||||
pub parent: String,
|
||||
pub nodes: Vec<Node>,
|
||||
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<Node>, focus: usize) -> Self {
|
||||
pub fn new(parent: String, nodes: Vec<Node>, 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 => {
|
||||
|
27
src/ui.rs
27
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();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user