Revert vimlike scrolling

Use stateful ui widget.
pull/709/head
Arijit Basu 2 months ago committed by Arijit Basu
parent 6fb0781fe4
commit ce52bcdf94

@ -335,12 +335,12 @@ impl App {
&config &config
.general .general
.initial_mode .initial_mode
.to_owned() .clone()
.unwrap_or_else(|| "default".into()), .unwrap_or_else(|| "default".into()),
) { ) {
Some(m) => m.clone().sanitized( Some(m) => m.clone().sanitized(
config.general.read_only, config.general.read_only,
config.general.global_key_bindings.to_owned(), config.general.global_key_bindings.clone(),
), ),
None => { None => {
bail!("'default' mode is missing") bail!("'default' mode is missing")
@ -351,7 +351,7 @@ impl App {
&config &config
.general .general
.initial_layout .initial_layout
.to_owned() .clone()
.unwrap_or_else(|| "default".into()), .unwrap_or_else(|| "default".into()),
) { ) {
Some(l) => l.clone(), Some(l) => l.clone(),
@ -752,10 +752,8 @@ 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 self.directory_buffer.as_ref().map(|d| d.focus).unwrap_or(0),
.as_ref() self.config.general.vimlike_scrolling,
.map(|d| d.scroll_state.get_focus())
.unwrap_or(0),
) { ) {
Ok(dir) => self.set_directory(dir), Ok(dir) => self.set_directory(dir),
Err(e) => { Err(e) => {
@ -794,7 +792,7 @@ impl App {
} }
} }
dir.scroll_state.set_focus(0); dir.focus = 0;
if save_history { if save_history {
if let Some(n) = self.focused_node() { if let Some(n) = self.focused_node() {
@ -812,7 +810,7 @@ impl App {
history = history.push(n.absolute_path.clone()); 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() { if let Some(n) = dir.focused_node() {
self.history = history.push(n.absolute_path.clone()); self.history = history.push(n.absolute_path.clone());
@ -823,15 +821,15 @@ impl App {
fn focus_previous(mut self) -> Result<Self> { fn focus_previous(mut self) -> Result<Self> {
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() {
if dir.scroll_state.get_focus() == 0 { dir.focus = if dir.focus == 0 {
if !bounded { if bounded {
dir.scroll_state.set_focus(dir.total.saturating_sub(1)); dir.focus
} else {
dir.total.saturating_sub(1)
} }
} else { } else {
dir.scroll_state dir.focus.saturating_sub(1)
.set_focus(dir.scroll_state.get_focus().saturating_sub(1));
}; };
}; };
Ok(self) Ok(self)
@ -884,8 +882,7 @@ impl App {
history = history.push(n.absolute_path.clone()); history = history.push(n.absolute_path.clone());
} }
dir.scroll_state dir.focus = dir.focus.saturating_sub(index);
.set_focus(dir.scroll_state.get_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());
} }
@ -908,16 +905,18 @@ impl App {
fn focus_next(mut self) -> Result<Self> { fn focus_next(mut self) -> Result<Self> {
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() {
if (dir.scroll_state.get_focus() + 1) == dir.total { dir.focus = if (dir.focus + 1) == dir.total {
if !bounded { if bounded {
dir.scroll_state.set_focus(0); dir.focus
} else {
0
} }
} else { } else {
dir.scroll_state.set_focus(dir.scroll_state.get_focus() + 1); dir.focus + 1
} }
}; };
Ok(self) Ok(self)
} }
@ -968,12 +967,10 @@ impl App {
history = history.push(n.absolute_path.clone()); history = history.push(n.absolute_path.clone());
} }
dir.scroll_state.set_focus( dir.focus = dir
dir.scroll_state .focus
.get_focus()
.saturating_add(index) .saturating_add(index)
.min(dir.total.saturating_sub(1)), .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());
@ -998,7 +995,7 @@ impl App {
fn follow_symlink(self) -> Result<Self> { fn follow_symlink(self) -> Result<Self> {
if let Some(pth) = self if let Some(pth) = self
.focused_node() .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) self.focus_path(&pth, true)
} else { } else {
@ -1241,8 +1238,7 @@ 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.scroll_state dir.focus = index.min(dir.total.saturating_sub(1));
.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());
} }
@ -1279,7 +1275,7 @@ impl App {
history = history.push(n.absolute_path.clone()); history = history.push(n.absolute_path.clone());
} }
} }
dir_buf.scroll_state.set_focus(focus); dir_buf.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());
@ -1386,7 +1382,7 @@ impl App {
self = self.push_mode(); self = self.push_mode();
self.mode = mode.sanitized( self.mode = mode.sanitized(
self.config.general.read_only, self.config.general.read_only,
self.config.general.global_key_bindings.to_owned(), self.config.general.global_key_bindings.clone(),
); );
// Hooks // Hooks
@ -1411,7 +1407,7 @@ impl App {
self = self.push_mode(); self = self.push_mode();
self.mode = mode.sanitized( self.mode = mode.sanitized(
self.config.general.read_only, self.config.general.read_only,
self.config.general.global_key_bindings.to_owned(), self.config.general.global_key_bindings.clone(),
); );
// Hooks // Hooks
@ -1438,7 +1434,7 @@ impl App {
fn switch_layout_builtin(mut self, layout: &str) -> Result<Self> { fn switch_layout_builtin(mut self, layout: &str) -> Result<Self> {
if let Some(l) = self.config.layouts.builtin.get(layout) { if let Some(l) = self.config.layouts.builtin.get(layout) {
self.layout = l.to_owned(); self.layout = l.clone();
// Hooks // Hooks
if !self.hooks.on_layout_switch.is_empty() { if !self.hooks.on_layout_switch.is_empty() {
@ -1454,7 +1450,7 @@ impl App {
fn switch_layout_custom(mut self, layout: &str) -> Result<Self> { fn switch_layout_custom(mut self, layout: &str) -> Result<Self> {
if let Some(l) = self.config.layouts.get_custom(layout) { if let Some(l) = self.config.layouts.get_custom(layout) {
self.layout = l.to_owned(); self.layout = l.clone();
// Hooks // Hooks
if !self.hooks.on_layout_switch.is_empty() { if !self.hooks.on_layout_switch.is_empty() {
@ -1579,7 +1575,7 @@ impl App {
pub fn select(mut self) -> Result<Self> { pub fn select(mut self) -> Result<Self> {
let count = self.selection.len(); 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); self.selection.insert(n);
} }
@ -1634,7 +1630,7 @@ impl App {
pub fn un_select(mut self) -> Result<Self> { pub fn un_select(mut self) -> Result<Self> {
let count = self.selection.len(); 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 self.selection
.retain(|s| s.absolute_path != n.absolute_path); .retain(|s| s.absolute_path != n.absolute_path);
} }
@ -1808,7 +1804,7 @@ impl App {
.config .config
.general .general
.initial_sorting .initial_sorting
.to_owned() .clone()
.unwrap_or_default(); .unwrap_or_default();
Ok(self) Ok(self)
} }

@ -7,7 +7,7 @@ use crate::ui::block;
use crate::ui::string_to_text; use crate::ui::string_to_text;
use crate::ui::Constraint; use crate::ui::Constraint;
use crate::ui::ContentRendererArg; use crate::ui::ContentRendererArg;
use mlua::Lua; use crate::ui::UI;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tui::layout::Constraint as TuiConstraint; use tui::layout::Constraint as TuiConstraint;
use tui::layout::Rect as TuiRect; use tui::layout::Rect as TuiRect;
@ -60,12 +60,11 @@ pub struct CustomContent {
/// A cursed function from crate::ui. /// A cursed function from crate::ui.
pub fn draw_custom_content( pub fn draw_custom_content(
ui: &mut UI,
f: &mut Frame, f: &mut Frame,
screen_size: TuiRect,
layout_size: TuiRect, layout_size: TuiRect,
app: &app::App, app: &app::App,
content: CustomContent, content: CustomContent,
lua: &Lua,
) { ) {
let config = app.config.general.panel_ui.default.clone(); let config = app.config.general.panel_ui.default.clone();
let title = content.title; let title = content.title;
@ -85,12 +84,12 @@ pub fn draw_custom_content(
let ctx = ContentRendererArg { let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(), app: app.to_lua_ctx_light(),
layout_size: layout_size.into(), 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| { .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()); .unwrap_or_else(|e| e.to_string());
@ -121,12 +120,12 @@ pub fn draw_custom_content(
let ctx = ContentRendererArg { let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(), app: app.to_lua_ctx_light(),
layout_size: layout_size.into(), 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| { .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![format!("{e:?}")])
}) })
.unwrap_or_else(|e| vec![e.to_string()]) .unwrap_or_else(|e| vec![e.to_string()])
@ -161,7 +160,7 @@ pub fn draw_custom_content(
let widths = widths let widths = widths
.into_iter() .into_iter()
.map(|w| w.to_tui(screen_size, layout_size)) .map(|w| w.to_tui(ui.screen_size, layout_size))
.collect::<Vec<TuiConstraint>>(); .collect::<Vec<TuiConstraint>>();
let content = Table::new(rows, widths) let content = Table::new(rows, widths)
@ -182,12 +181,12 @@ pub fn draw_custom_content(
let ctx = ContentRendererArg { let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(), app: app.to_lua_ctx_light(),
layout_size: layout_size.into(), 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| { .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![format!("{e:?}")]])
}) })
.unwrap_or_else(|e| vec![vec![e.to_string()]]) .unwrap_or_else(|e| vec![vec![e.to_string()]])
@ -204,7 +203,7 @@ pub fn draw_custom_content(
let widths = widths let widths = widths
.into_iter() .into_iter()
.map(|w| w.to_tui(screen_size, layout_size)) .map(|w| w.to_tui(ui.screen_size, layout_size))
.collect::<Vec<TuiConstraint>>(); .collect::<Vec<TuiConstraint>>();
let mut content = Table::new(rows, &widths).block(block( let mut content = Table::new(rows, &widths).block(block(

@ -55,7 +55,7 @@ pub struct NodeTypeConfig {
impl NodeTypeConfig { impl NodeTypeConfig {
pub fn extend(mut self, other: &Self) -> Self { pub fn extend(mut self, other: &Self) -> Self {
self.style = self.style.extend(&other.style); self.style = self.style.extend(&other.style);
self.meta.extend(other.meta.to_owned()); self.meta.extend(other.meta.clone());
self self
} }
} }
@ -85,11 +85,11 @@ pub struct NodeTypesConfig {
impl NodeTypesConfig { impl NodeTypesConfig {
pub fn get(&self, node: &Node) -> NodeTypeConfig { pub fn get(&self, node: &Node) -> NodeTypeConfig {
let mut node_type = if node.is_symlink { let mut node_type = if node.is_symlink {
self.symlink.to_owned() self.symlink.clone()
} else if node.is_dir { } else if node.is_dir {
self.directory.to_owned() self.directory.clone()
} else { } else {
self.file.to_owned() self.file.clone()
}; };
let mut me = node.mime_essence.splitn(2, '/'); let mut me = node.mime_essence.splitn(2, '/');
@ -141,7 +141,7 @@ pub struct UiElement {
impl UiElement { impl UiElement {
pub fn extend(mut self, other: &Self) -> Self { 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.style = self.style.extend(&other.style);
self self
} }
@ -641,8 +641,8 @@ impl PanelUiConfig {
pub fn extend(mut self, other: &Self) -> Self { pub fn extend(mut self, other: &Self) -> Self {
self.title = self.title.extend(&other.title); self.title = self.title.extend(&other.title);
self.style = self.style.extend(&other.style); self.style = self.style.extend(&other.style);
self.borders = other.borders.to_owned().or(self.borders); self.borders = other.borders.clone().or(self.borders);
self.border_type = other.border_type.to_owned().or(self.border_type); self.border_type = other.border_type.or(self.border_type);
self.border_style = self.border_style.extend(&other.border_style); self.border_style = self.border_style.extend(&other.border_style);
self self
} }

@ -1,32 +1,52 @@
use crate::node::Node; use crate::node::Node;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use time::OffsetDateTime; use time::OffsetDateTime;
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub struct ScrollState { pub struct ScrollState {
current_focus: usize, pub current_focus: usize,
pub last_focus: Option<usize>, pub last_focus: Option<usize>,
pub skipped_rows: usize, pub skipped_rows: usize,
/* The number of visible next lines when scrolling towards either ends of the view port */ /* The number of visible next lines when scrolling towards either ends of the view port */
pub initial_preview_cushion: usize, pub initial_preview_cushion: usize,
pub vimlike_scrolling: bool,
} }
impl ScrollState { 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.last_focus = Some(self.current_focus);
self.current_focus = current_focus; self.current_focus = current_focus;
self
} }
pub fn get_focus(&self) -> usize { pub fn update_skipped_rows(self, height: usize, total: usize) -> Self {
self.current_focus if self.vimlike_scrolling {
self.update_skipped_rows_vimlike(height, total)
} else {
self.update_skipped_rows_paginated(height)
}
} }
pub fn calc_skipped_rows( pub fn update_skipped_rows_paginated(mut self, height: usize) -> Self {
&self, self.skipped_rows = height * (self.current_focus / height.max(1));
height: usize, self
total: usize, }
vimlike_scrolling: bool,
) -> usize { pub fn update_skipped_rows_vimlike(mut self, height: usize, total: usize) -> Self {
let preview_cushion = if height >= self.initial_preview_cushion * 3 { let preview_cushion = if height >= self.initial_preview_cushion * 3 {
self.initial_preview_cushion self.initial_preview_cushion
} else if height >= 9 { } else if height >= 9 {
@ -47,30 +67,30 @@ impl ScrollState {
.saturating_sub(preview_cushion + 1) .saturating_sub(preview_cushion + 1)
.min(total.saturating_sub(preview_cushion + 1)); .min(total.saturating_sub(preview_cushion + 1));
if !vimlike_scrolling { self.skipped_rows = if current_focus == 0 {
height * (self.current_focus / height.max(1))
} else if last_focus.is_none() {
// Just entered the directory
0
} else if current_focus == 0 {
// When focus goes to first node // When focus goes to first node
0 0
} else if current_focus == total.saturating_sub(1) { } else if current_focus == total.saturating_sub(1) {
// When focus goes to last node // When focus goes to last node
total.saturating_sub(height) total.saturating_sub(height)
} else if (start_cushion_row..=end_cushion_row).contains(&current_focus) { } else if current_focus > start_cushion_row && current_focus <= end_cushion_row {
// If within cushioned area; do nothing // If within cushioned area; do nothing
first_visible_row first_visible_row
} else if current_focus > last_focus.unwrap() { } else if let Some(last_focus) = last_focus {
match current_focus.cmp(&last_focus) {
Ordering::Greater => {
// When scrolling down the cushioned area // When scrolling down the cushioned area
if current_focus > total.saturating_sub(preview_cushion + 1) { if current_focus > total.saturating_sub(preview_cushion + 1) {
// When focusing the last nodes; always view the full last page // When focusing the last nodes; always view the full last page
total.saturating_sub(height) total.saturating_sub(height)
} else { } else {
// When scrolling down the cushioned area without reaching the last nodes // When scrolling down the cushioned area without reaching the last nodes
current_focus.saturating_sub(height.saturating_sub(preview_cushion + 1)) current_focus
.saturating_sub(height.saturating_sub(preview_cushion + 1))
}
} }
} else if current_focus < last_focus.unwrap() {
Ordering::Less => {
// When scrolling up the cushioned area // When scrolling up the cushioned area
if current_focus < preview_cushion { if current_focus < preview_cushion {
// When focusing the first nodes; always view the full first page // When focusing the first nodes; always view the full first page
@ -82,11 +102,18 @@ impl ScrollState {
// When scrolling up the cushioned area without reaching the first nodes // When scrolling up the cushioned area without reaching the first nodes
current_focus.saturating_sub(preview_cushion) current_focus.saturating_sub(preview_cushion)
} }
} else { }
// If nothing matches; do nothing Ordering::Equal => {
// Do nothing
first_visible_row first_visible_row
} }
} }
} else {
// Just entered dir
first_visible_row
};
self
}
} }
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
@ -94,31 +121,26 @@ pub struct DirectoryBuffer {
pub parent: String, pub parent: String,
pub nodes: Vec<Node>, pub nodes: Vec<Node>,
pub total: usize, pub total: usize,
pub scroll_state: ScrollState, pub focus: usize,
#[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>, current_focus: usize) -> Self { pub fn new(parent: String, nodes: Vec<Node>, focus: usize) -> Self {
let total = nodes.len(); let total = nodes.len();
Self { Self {
parent, parent,
nodes, nodes,
total, total,
scroll_state: ScrollState { focus,
current_focus,
last_focus: None,
skipped_rows: 0,
initial_preview_cushion: 5,
},
explored_at: now(), explored_at: now(),
} }
} }
pub fn focused_node(&self) -> Option<&Node> { 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::*; use super::*;
#[test] #[test]
fn test_calc_skipped_rows_non_vimlike_scrolling() { fn test_update_skipped_rows_paginated() {
let state = ScrollState { let state = ScrollState {
current_focus: 10, current_focus: 10,
last_focus: Some(8), last_focus: Some(8),
skipped_rows: 0, skipped_rows: 0,
initial_preview_cushion: 5, initial_preview_cushion: 5,
vimlike_scrolling: false,
}; };
let height = 5; let height = 5;
let total = 20; let total = 100;
let vimlike_scrolling = false;
let result = state.calc_skipped_rows(height, total, vimlike_scrolling); let state = state.update_skipped_rows(height, total);
assert_eq!(result, height * (state.current_focus / height.max(1))); assert_eq!(
state.skipped_rows,
height * (state.current_focus / height.max(1))
);
} }
#[test] #[test]
fn test_calc_skipped_rows_entered_directory() { fn test_update_skipped_rows_entered_directory() {
let state = ScrollState { let state = ScrollState {
current_focus: 10, current_focus: 100,
last_focus: None, last_focus: None,
skipped_rows: 0, skipped_rows: 0,
initial_preview_cushion: 5, initial_preview_cushion: 5,
vimlike_scrolling: true,
}; };
let height = 5; let height = 5;
let total = 20; let total = 200;
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); assert_eq!(result, 0);
} }
@ -173,13 +198,13 @@ mod tests {
last_focus: Some(8), last_focus: Some(8),
skipped_rows: 5, skipped_rows: 5,
initial_preview_cushion: 5, initial_preview_cushion: 5,
vimlike_scrolling: true,
}; };
let height = 5; let height = 5;
let total = 20; 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); assert_eq!(result, 0);
} }
@ -190,13 +215,13 @@ mod tests {
last_focus: Some(18), last_focus: Some(18),
skipped_rows: 15, skipped_rows: 15,
initial_preview_cushion: 5, initial_preview_cushion: 5,
vimlike_scrolling: true,
}; };
let height = 5; let height = 5;
let total = 20; 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); assert_eq!(result, 15);
} }
@ -207,13 +232,13 @@ mod tests {
last_focus: Some(10), last_focus: Some(10),
skipped_rows: 10, skipped_rows: 10,
initial_preview_cushion: 5, initial_preview_cushion: 5,
vimlike_scrolling: true,
}; };
let height = 5; let height = 5;
let total = 20; 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); assert_eq!(result, 10);
} }
@ -224,13 +249,13 @@ mod tests {
last_focus: Some(10), last_focus: Some(10),
skipped_rows: 10, skipped_rows: 10,
initial_preview_cushion: 5, initial_preview_cushion: 5,
vimlike_scrolling: true,
}; };
let height = 5; let height = 5;
let total = 20; 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); assert_eq!(result, 7);
} }

@ -22,5 +22,5 @@ pub fn runtime_dir() -> PathBuf {
else { else {
return env::temp_dir(); return env::temp_dir();
}; };
dir.to_owned() dir.clone()
} }

@ -45,6 +45,7 @@ pub(crate) fn explore_sync(
parent: PathBuf, parent: PathBuf,
focused_path: Option<PathBuf>, focused_path: Option<PathBuf>,
fallback_focus: usize, fallback_focus: usize,
vimlike_scrolling: bool,
) -> Result<DirectoryBuffer> { ) -> Result<DirectoryBuffer> {
let nodes = explore(&parent, &config)?; let nodes = explore(&parent, &config)?;
let focus_index = if config.searcher.is_some() { let focus_index = if config.searcher.is_some() {
@ -73,10 +74,17 @@ pub(crate) fn explore_async(
parent: PathBuf, parent: PathBuf,
focused_path: Option<PathBuf>, focused_path: Option<PathBuf>,
fallback_focus: usize, fallback_focus: usize,
vimlike_scrolling: bool,
tx_msg_in: Sender<Task>, tx_msg_in: Sender<Task>,
) { ) {
thread::spawn(move || { thread::spawn(move || {
explore_sync(config, parent.clone(), focused_path, fallback_focus) explore_sync(
config,
parent.clone(),
focused_path,
fallback_focus,
vimlike_scrolling,
)
.and_then(|buf| { .and_then(|buf| {
tx_msg_in tx_msg_in
.send(Task::new( .send(Task::new(
@ -101,6 +109,7 @@ pub(crate) fn explore_recursive_async(
parent: PathBuf, parent: PathBuf,
focused_path: Option<PathBuf>, focused_path: Option<PathBuf>,
fallback_focus: usize, fallback_focus: usize,
vimlike_scrolling: bool,
tx_msg_in: Sender<Task>, tx_msg_in: Sender<Task>,
) { ) {
explore_async( explore_async(
@ -108,6 +117,7 @@ pub(crate) fn explore_recursive_async(
parent.clone(), parent.clone(),
focused_path, focused_path,
fallback_focus, fallback_focus,
vimlike_scrolling,
tx_msg_in.clone(), tx_msg_in.clone(),
); );
if let Some(grand_parent) = parent.parent() { if let Some(grand_parent) = parent.parent() {
@ -116,6 +126,7 @@ pub(crate) fn explore_recursive_async(
grand_parent.into(), grand_parent.into(),
parent.file_name().map(|p| p.into()), parent.file_name().map(|p| p.into()),
0, 0,
vimlike_scrolling,
tx_msg_in, tx_msg_in,
); );
} }
@ -130,7 +141,7 @@ mod tests {
let config = ExplorerConfig::default(); let config = ExplorerConfig::default();
let path = PathBuf::from("."); 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()); assert!(r.is_ok());
} }
@ -140,7 +151,7 @@ mod tests {
let config = ExplorerConfig::default(); let config = ExplorerConfig::default();
let path = PathBuf::from("/there/is/no/path"); 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()); assert!(r.is_err());
} }
@ -169,7 +180,7 @@ mod tests {
let path = PathBuf::from("."); let path = PathBuf::from(".");
let (tx_msg_in, rx_msg_in) = mpsc::channel(); 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 task = rx_msg_in.recv().unwrap();
let dbuf = extract_dirbuf_from_msg(task.msg); let dbuf = extract_dirbuf_from_msg(task.msg);

@ -647,7 +647,7 @@ impl Key {
Self::ShiftZ => Some('Z'), Self::ShiftZ => Some('Z'),
Self::Space => Some(' '), Self::Space => Some(' '),
Self::Special(c) => Some(c.to_owned()), Self::Special(c) => Some(*c),
_ => None, _ => None,
} }

@ -8,7 +8,8 @@ use crate::explorer;
use crate::lua; use crate::lua;
use crate::pipe; use crate::pipe;
use crate::pwd_watcher; use crate::pwd_watcher;
use crate::ui; use crate::ui::NO_COLOR;
use crate::ui::UI;
use crate::yaml; use crate::yaml;
use anyhow::{bail, Error, Result}; use anyhow::{bail, Error, Result};
use crossterm::event; use crossterm::event;
@ -89,7 +90,7 @@ fn call(
let focus_index = app let focus_index = app
.directory_buffer .directory_buffer
.as_ref() .as_ref()
.map(|d| d.scroll_state.get_focus()) .map(|d| d.focus)
.unwrap_or_default() .unwrap_or_default()
.to_string(); .to_string();
@ -279,16 +280,14 @@ 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 app.directory_buffer.as_ref().map(|d| d.focus).unwrap_or(0),
.as_ref() app.config.general.vimlike_scrolling,
.map(|d| d.scroll_state.get_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())?;
let mut result = Ok(None); let mut result = Ok(None);
let session_path = app.session_path.to_owned(); let session_path = app.session_path.clone();
term::enable_raw_mode()?; term::enable_raw_mode()?;
@ -344,6 +343,9 @@ impl Runner {
None, None,
))?; ))?;
// UI
let mut ui = UI::new(&lua);
'outer: for task in rx_msg_in { 'outer: for task in rx_msg_in {
match app.handle_task(task) { match app.handle_task(task) {
Ok(a) => { Ok(a) => {
@ -433,8 +435,9 @@ 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.scroll_state.get_focus()) .map(|d| d.focus)
.unwrap_or(0), .unwrap_or(0),
app.config.general.vimlike_scrolling,
tx_msg_in.clone(), tx_msg_in.clone(),
); );
tx_pwd_watcher.send(app.pwd.clone())?; tx_pwd_watcher.send(app.pwd.clone())?;
@ -448,8 +451,9 @@ 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.scroll_state.get_focus()) .map(|d| d.focus)
.unwrap_or(0), .unwrap_or(0),
app.config.general.vimlike_scrolling,
tx_msg_in.clone(), tx_msg_in.clone(),
); );
tx_pwd_watcher.send(app.pwd.clone())?; tx_pwd_watcher.send(app.pwd.clone())?;
@ -479,7 +483,7 @@ impl Runner {
tx_pwd_watcher.send(app.pwd.clone())?; tx_pwd_watcher.send(app.pwd.clone())?;
// OSC 7: Change CWD // OSC 7: Change CWD
if !(*ui::NO_COLOR) { if !(*NO_COLOR) {
write!( write!(
terminal.backend_mut(), terminal.backend_mut(),
"\x1b]7;file://{}{}\x1b\\", "\x1b]7;file://{}{}\x1b\\",
@ -496,7 +500,7 @@ impl Runner {
} }
// UI // UI
terminal.draw(|f| ui::draw(f, &mut app, &lua))?; terminal.draw(|f| ui.draw(f, &app))?;
} }
EnableMouse => { EnableMouse => {

@ -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<TuiRect> 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)] #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct LayoutOptions { pub struct LayoutOptions {
@ -81,7 +107,7 @@ impl LayoutOptions {
self.margin = other.margin.or(self.margin); self.margin = other.margin.or(self.margin);
self.horizontal_margin = other.horizontal_margin.or(self.horizontal_margin); self.horizontal_margin = other.horizontal_margin.or(self.horizontal_margin);
self.vertical_margin = other.vertical_margin.or(self.vertical_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 self
} }
} }
@ -154,7 +180,7 @@ impl Layout {
}, },
) => Self::Horizontal { ) => Self::Horizontal {
config: sconfig.extend(oconfig), config: sconfig.extend(oconfig),
splits: osplits.to_owned(), splits: osplits.clone(),
}, },
( (
@ -168,9 +194,9 @@ impl Layout {
}, },
) => Self::Vertical { ) => Self::Vertical {
config: sconfig.extend(oconfig), 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 => { other => {
if other == *target { if other == *target {
replacement.to_owned() replacement.clone()
} else { } else {
other other
} }
@ -364,14 +390,10 @@ impl Style {
pub fn extend(mut self, other: &Self) -> Self { pub fn extend(mut self, other: &Self) -> Self {
self.fg = other.fg.or(self.fg); self.fg = other.fg.or(self.fg);
self.bg = other.bg.or(self.bg); self.bg = other.bg.or(self.bg);
self.add_modifiers = extend_optional_modifiers( self.add_modifiers =
self.add_modifiers, extend_optional_modifiers(self.add_modifiers, other.add_modifiers.clone());
other.add_modifiers.to_owned(), self.sub_modifiers =
); extend_optional_modifiers(self.sub_modifiers, other.sub_modifiers.clone());
self.sub_modifiers = extend_optional_modifiers(
self.sub_modifiers,
other.sub_modifiers.to_owned(),
);
self self
} }
} }
@ -594,12 +616,12 @@ pub struct ResolvedNodeUiMetadata {
impl From<ResolvedNode> for ResolvedNodeUiMetadata { impl From<ResolvedNode> for ResolvedNodeUiMetadata {
fn from(node: ResolvedNode) -> Self { fn from(node: ResolvedNode) -> Self {
Self { Self {
absolute_path: node.absolute_path.to_owned(), absolute_path: node.absolute_path.clone(),
extension: node.extension.to_owned(), extension: node.extension.clone(),
is_dir: node.is_dir, is_dir: node.is_dir,
is_file: node.is_file, is_file: node.is_file,
is_readonly: node.is_readonly, is_readonly: node.is_readonly,
mime_essence: node.mime_essence.to_owned(), mime_essence: node.mime_essence.clone(),
size: node.size, size: node.size,
human_size: node.human_size, human_size: node.human_size,
created: node.created, created: node.created,
@ -663,21 +685,21 @@ impl NodeUiMetadata {
style: Style, style: Style,
) -> Self { ) -> Self {
Self { Self {
parent: node.parent.to_owned(), parent: node.parent.clone(),
relative_path: node.relative_path.to_owned(), relative_path: node.relative_path.clone(),
absolute_path: node.absolute_path.to_owned(), absolute_path: node.absolute_path.clone(),
extension: node.extension.to_owned(), extension: node.extension.clone(),
is_symlink: node.is_symlink, is_symlink: node.is_symlink,
is_broken: node.is_broken, is_broken: node.is_broken,
is_dir: node.is_dir, is_dir: node.is_dir,
is_file: node.is_file, is_file: node.is_file,
is_readonly: node.is_readonly, is_readonly: node.is_readonly,
mime_essence: node.mime_essence.to_owned(), mime_essence: node.mime_essence.clone(),
size: node.size, size: node.size,
human_size: node.human_size.to_owned(), human_size: node.human_size.clone(),
permissions: node.permissions.to_owned(), permissions: node.permissions,
canonical: node.canonical.to_owned().map(ResolvedNode::into), canonical: node.canonical.clone().map(ResolvedNode::into),
symlink: node.symlink.to_owned().map(ResolvedNode::into), symlink: node.symlink.clone().map(ResolvedNode::into),
created: node.created, created: node.created,
last_modified: node.last_modified, last_modified: node.last_modified,
uid: node.uid, uid: node.uid,
@ -703,7 +725,7 @@ pub fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> {
.borders(TuiBorders::from_bits_truncate( .borders(TuiBorders::from_bits_truncate(
config config
.borders .borders
.to_owned() .clone()
.unwrap_or_default() .unwrap_or_default()
.iter() .iter()
.map(|b| b.bits()) .map(|b| b.bits())
@ -718,37 +740,39 @@ pub fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> {
.border_style(config.border_style) .border_style(config.border_style)
} }
fn draw_table( pub struct UI<'lua> {
f: &mut Frame, pub lua: &'lua Lua,
screen_size: TuiRect, pub screen_size: TuiRect,
layout_size: TuiRect, }
app: &mut app::App,
lua: &Lua, impl<'lua> UI<'lua> {
) { pub fn new(lua: &'lua Lua) -> Self {
let screen_size = Default::default();
Self { lua, screen_size }
}
}
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 panel_config = &app.config.general.panel_ui;
let config = panel_config.default.to_owned().extend(&panel_config.table); let config = panel_config.default.clone().extend(&panel_config.table);
let app_config = app.config.to_owned(); let app_config = app.config.clone();
let header_height = app_config.general.table.header.height.unwrap_or(1); let header_height = app_config.general.table.header.height.unwrap_or(1);
let height: usize = let height: usize =
(layout_size.height.max(header_height + 2) - (header_height + 2)).into(); (layout_size.height.max(header_height + 2) - (header_height + 2)).into();
let row_style = app_config.general.table.row.style.to_owned(); let row_style = app_config.general.table.row.style.clone();
let rows = app let rows = app
.directory_buffer .directory_buffer
.as_mut() .as_ref()
.map(|dir| { .map(|dir| {
dir.scroll_state.skipped_rows = dir.scroll_state.calc_skipped_rows(
height,
dir.total,
app.config.general.vimlike_scrolling,
);
dir.nodes dir.nodes
.iter() .iter()
.enumerate() .enumerate()
.skip(dir.scroll_state.skipped_rows) .skip(height * (dir.focus / height.max(1)))
.take(height) .take(height)
.map(|(index, node)| { .map(|(index, node)| {
let is_focused = dir.scroll_state.get_focus() == index; let is_focused = dir.focus == index;
let is_selected = app let is_selected = app
.selection .selection
@ -762,7 +786,7 @@ fn draw_table(
.general .general
.table .table
.tree .tree
.to_owned() .clone()
.map(|t| { .map(|t| {
if is_last { if is_last {
t.2.format t.2.format
@ -777,35 +801,31 @@ 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.scroll_state.get_focus().cmp(&index) { match dir.focus.cmp(&index) {
Ordering::Greater => { Ordering::Greater => (dir.focus - index, true, false),
(dir.scroll_state.get_focus() - index, true, false) Ordering::Less => (index - dir.focus, false, true),
}
Ordering::Less => {
(index - dir.scroll_state.get_focus(), false, true)
}
Ordering::Equal => (0, false, false), Ordering::Equal => (0, false, false),
}; };
let (mut prefix, mut suffix, mut style) = { let (mut prefix, mut suffix, mut style) = {
let ui = app_config.general.default_ui.to_owned(); let ui = app_config.general.default_ui.clone();
(ui.prefix, ui.suffix, ui.style.extend(&node_type.style)) (ui.prefix, ui.suffix, ui.style.extend(&node_type.style))
}; };
if is_focused && is_selected { if is_focused && is_selected {
let ui = app_config.general.focus_selection_ui.to_owned(); let ui = app_config.general.focus_selection_ui.clone();
prefix = ui.prefix.to_owned().or(prefix); prefix = ui.prefix.clone().or(prefix);
suffix = ui.suffix.to_owned().or(suffix); suffix = ui.suffix.clone().or(suffix);
style = style.extend(&ui.style); style = style.extend(&ui.style);
} else if is_selected { } else if is_selected {
let ui = app_config.general.selection_ui.to_owned(); let ui = app_config.general.selection_ui.clone();
prefix = ui.prefix.to_owned().or(prefix); prefix = ui.prefix.clone().or(prefix);
suffix = ui.suffix.to_owned().or(suffix); suffix = ui.suffix.clone().or(suffix);
style = style.extend(&ui.style); style = style.extend(&ui.style);
} else if is_focused { } else if is_focused {
let ui = app_config.general.focus_ui.to_owned(); let ui = app_config.general.focus_ui.clone();
prefix = ui.prefix.to_owned().or(prefix); prefix = ui.prefix.clone().or(prefix);
suffix = ui.suffix.to_owned().or(suffix); suffix = ui.suffix.clone().or(suffix);
style = style.extend(&ui.style); style = style.extend(&ui.style);
}; };
@ -825,21 +845,21 @@ fn draw_table(
style, style,
); );
let cols = lua::serialize::<NodeUiMetadata>(lua, &meta) let cols = lua::serialize::<NodeUiMetadata>(self.lua, &meta)
.map(|v| { .map(|v| {
app_config app_config
.general .general
.table .table
.row .row
.cols .cols
.to_owned() .clone()
.unwrap_or_default() .unwrap_or_default()
.iter() .iter()
.filter_map(|c| { .filter_map(|c| {
c.format.as_ref().map(|f| { c.format.as_ref().map(|f| {
let out = lua::call(lua, f, v.clone()) let out = lua::call(self.lua, f, v.clone())
.unwrap_or_else(|e| format!("{e:?}")); .unwrap_or_else(|e| format!("{e:?}"));
(string_to_text(out), c.style.to_owned()) (string_to_text(out), c.style.clone())
}) })
}) })
.collect::<Vec<(Text, Style)>>() .collect::<Vec<(Text, Style)>>()
@ -849,7 +869,7 @@ fn draw_table(
.map(|(text, style)| Cell::from(text).style(style)) .map(|(text, style)| Cell::from(text).style(style))
.collect::<Vec<Cell>>(); .collect::<Vec<Cell>>();
Row::new(cols).style(row_style.to_owned()) Row::new(cols).style(row_style.clone())
}) })
.collect::<Vec<Row>>() .collect::<Vec<Row>>()
}) })
@ -859,10 +879,10 @@ fn draw_table(
.general .general
.table .table
.col_widths .col_widths
.to_owned() .clone()
.unwrap_or_default() .unwrap_or_default()
.into_iter() .into_iter()
.map(|c| c.to_tui(screen_size, layout_size)) .map(|c| c.to_tui(self.screen_size, layout_size))
.collect(); .collect();
let pwd = if let Some(vroot) = app.vroot.as_ref() { let pwd = if let Some(vroot) = app.vroot.as_ref() {
@ -884,49 +904,40 @@ fn draw_table(
}; };
let table = Table::new(rows, table_constraints) let table = Table::new(rows, table_constraints)
.style(app_config.general.table.style.to_owned()) .style(app_config.general.table.style.clone())
.highlight_style(app_config.general.focus_ui.style.to_owned()) .highlight_style(app_config.general.focus_ui.style.clone())
.column_spacing(app_config.general.table.col_spacing.unwrap_or_default()) .column_spacing(app_config.general.table.col_spacing.unwrap_or_default())
.block(block( .block(block(
config, config,
format!(" {vroot_indicator}/{pwd} {node_count}"), format!(" {vroot_indicator}/{pwd} {node_count}"),
)); ));
let table = table.to_owned().header( let table = table.clone().header(
Row::new( Row::new(
app_config app_config
.general .general
.table .table
.header .header
.cols .cols
.to_owned() .clone()
.unwrap_or_default() .unwrap_or_default()
.iter() .iter()
.map(|c| { .map(|c| {
Cell::from(c.format.to_owned().unwrap_or_default()) Cell::from(c.format.clone().unwrap_or_default())
.style(c.style.to_owned()) .style(c.style.clone())
}) })
.collect::<Vec<Cell>>(), .collect::<Vec<Cell>>(),
) )
.height(header_height) .height(header_height)
.style(app_config.general.table.header.style.to_owned()), .style(app_config.general.table.header.style.clone()),
); );
f.render_widget(table, layout_size); f.render_widget(table, layout_size);
} }
fn draw_selection( fn draw_selection(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) {
f: &mut Frame,
_screen_size: TuiRect,
layout_size: TuiRect,
app: &app::App,
lua: &Lua,
) {
let panel_config = &app.config.general.panel_ui; let panel_config = &app.config.general.panel_ui;
let config = panel_config let config = panel_config.default.clone().extend(&panel_config.selection);
.default
.to_owned()
.extend(&panel_config.selection);
let selection_count = app.selection.len(); let selection_count = app.selection.len();
@ -945,15 +956,15 @@ fn draw_selection(
.format .format
.as_ref() .as_ref()
.map(|f| { .map(|f| {
lua::serialize::<Node>(lua, n) lua::serialize::<Node>(self.lua, n)
.and_then(|n| lua::call(lua, f, n)) .and_then(|n| lua::call(self.lua, f, n))
.unwrap_or_else(|e| format!("{e:?}")) .unwrap_or_else(|e| format!("{e:?}"))
}) })
.unwrap_or_else(|| n.absolute_path.clone()); .unwrap_or_else(|| n.absolute_path.clone());
string_to_text(out) string_to_text(out)
}) })
.map(|i| { .map(|i| {
ListItem::new(i).style(app.config.general.selection.item.style.to_owned()) ListItem::new(i).style(app.config.general.selection.item.style.clone())
}) })
.collect(); .collect();
@ -970,19 +981,10 @@ fn draw_selection(
f.render_widget(selection_list, layout_size); f.render_widget(selection_list, layout_size);
} }
fn draw_help_menu( fn draw_help_menu(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) {
f: &mut Frame,
_screen_size: TuiRect,
layout_size: TuiRect,
app: &app::App,
_: &Lua,
) {
let panel_config = &app.config.general.panel_ui; let panel_config = &app.config.general.panel_ui;
let config = panel_config let config = panel_config.default.clone().extend(&panel_config.help_menu);
.default
.to_owned()
.extend(&panel_config.help_menu);
let help_menu_rows = app let help_menu_rows = app
.mode .mode
@ -994,7 +996,8 @@ fn draw_help_menu(
if app.config.general.hide_remaps_in_help_menu { if app.config.general.hide_remaps_in_help_menu {
[Cell::from(k), Cell::from(h)].to_vec() [Cell::from(k), Cell::from(h)].to_vec()
} else { } else {
[Cell::from(k), Cell::from(remaps.join("|")), Cell::from(h)].to_vec() [Cell::from(k), Cell::from(remaps.join("|")), Cell::from(h)]
.to_vec()
} }
}), }),
}) })
@ -1017,17 +1020,16 @@ fn draw_help_menu(
} }
fn draw_input_buffer( fn draw_input_buffer(
&mut self,
f: &mut Frame, f: &mut Frame,
_screen_size: TuiRect,
layout_size: TuiRect, layout_size: TuiRect,
app: &app::App, app: &app::App,
_: &Lua,
) { ) {
if let Some(input) = app.input.buffer.as_ref() { if let Some(input) = app.input.buffer.as_ref() {
let panel_config = &app.config.general.panel_ui; let panel_config = &app.config.general.panel_ui;
let config = panel_config let config = panel_config
.default .default
.to_owned() .clone()
.extend(&panel_config.input_and_logs); .extend(&panel_config.input_and_logs);
let cursor_offset_left = config let cursor_offset_left = config
@ -1050,8 +1052,8 @@ fn draw_input_buffer(
let input_buf = Paragraph::new(Line::from(vec![ let input_buf = Paragraph::new(Line::from(vec![
Span::styled( Span::styled(
app.input.prompt.to_owned(), app.input.prompt.clone(),
app.config.general.prompt.style.to_owned(), app.config.general.prompt.style.clone(),
), ),
Span::raw(input.value()), Span::raw(input.value()),
])) ]))
@ -1079,35 +1081,34 @@ fn draw_input_buffer(
} }
fn draw_sort_n_filter( fn draw_sort_n_filter(
&mut self,
f: &mut Frame, f: &mut Frame,
_screen_size: TuiRect,
layout_size: TuiRect, layout_size: TuiRect,
app: &app::App, app: &app::App,
_: &Lua,
) { ) {
let panel_config = &app.config.general.panel_ui; let panel_config = &app.config.general.panel_ui;
let config = panel_config let config = panel_config
.default .default
.to_owned() .clone()
.extend(&panel_config.sort_and_filter); .extend(&panel_config.sort_and_filter);
let ui = app.config.general.sort_and_filter_ui.to_owned(); let ui = app.config.general.sort_and_filter_ui.clone();
let filter_by: &IndexSet<NodeFilterApplicable> = &app.explorer_config.filters; let filter_by: &IndexSet<NodeFilterApplicable> = &app.explorer_config.filters;
let sort_by: &IndexSet<NodeSorterApplicable> = &app.explorer_config.sorters; let sort_by: &IndexSet<NodeSorterApplicable> = &app.explorer_config.sorters;
let search = app.explorer_config.searcher.as_ref(); let search = app.explorer_config.searcher.as_ref();
let defaultui = &ui.default_identifier; let defaultui = &ui.default_identifier;
let forwardui = defaultui let forwardui = defaultui
.to_owned() .clone()
.extend(&ui.sort_direction_identifiers.forward); .extend(&ui.sort_direction_identifiers.forward);
let reverseui = defaultui let reverseui = defaultui
.to_owned() .clone()
.extend(&ui.sort_direction_identifiers.reverse); .extend(&ui.sort_direction_identifiers.reverse);
let orderedui = defaultui let orderedui = defaultui
.to_owned() .clone()
.extend(&ui.search_direction_identifiers.ordered); .extend(&ui.search_direction_identifiers.ordered);
let unorderedui = defaultui let unorderedui = defaultui
.to_owned() .clone()
.extend(&ui.search_direction_identifiers.unordered); .extend(&ui.search_direction_identifiers.unordered);
let is_ordered_search = search.as_ref().map(|s| !s.unordered).unwrap_or(false); let is_ordered_search = search.as_ref().map(|s| !s.unordered).unwrap_or(false);
@ -1118,13 +1119,13 @@ fn draw_sort_n_filter(
ui.filter_identifiers ui.filter_identifiers
.get(&f.filter) .get(&f.filter)
.map(|u| { .map(|u| {
let ui = defaultui.to_owned().extend(u); let ui = defaultui.clone().extend(u);
( (
Span::styled( Span::styled(
ui.format.to_owned().unwrap_or_default(), ui.format.clone().unwrap_or_default(),
ui.style.to_owned(), ui.style.clone(),
), ),
Span::styled(f.input.to_owned(), ui.style), Span::styled(f.input.clone(), ui.style),
) )
}) })
.unwrap_or((Span::raw("f"), Span::raw(""))) .unwrap_or((Span::raw("f"), Span::raw("")))
@ -1138,7 +1139,7 @@ fn draw_sort_n_filter(
} else { } else {
&orderedui &orderedui
}; };
let ui = defaultui.to_owned().extend(u); let ui = defaultui.clone().extend(u);
let f = ui let f = ui
.format .format
.as_ref() .as_ref()
@ -1147,8 +1148,8 @@ fn draw_sort_n_filter(
( (
Span::styled(f, ui.style), Span::styled(f, ui.style),
Span::styled( Span::styled(
direction.format.to_owned().unwrap_or_default(), direction.format.clone().unwrap_or_default(),
direction.style.to_owned(), direction.style.clone(),
), ),
) )
}) })
@ -1162,15 +1163,15 @@ fn draw_sort_n_filter(
ui.sorter_identifiers ui.sorter_identifiers
.get(&s.sorter) .get(&s.sorter)
.map(|u| { .map(|u| {
let ui = defaultui.to_owned().extend(u); let ui = defaultui.clone().extend(u);
( (
Span::styled( Span::styled(
ui.format.to_owned().unwrap_or_default(), ui.format.clone().unwrap_or_default(),
ui.style, ui.style,
), ),
Span::styled( Span::styled(
direction.format.to_owned().unwrap_or_default(), direction.format.clone().unwrap_or_default(),
direction.style.to_owned(), direction.style.clone(),
), ),
) )
}) })
@ -1179,8 +1180,8 @@ fn draw_sort_n_filter(
.take(if !is_ordered_search { sort_by.len() } else { 0 }), .take(if !is_ordered_search { sort_by.len() } else { 0 }),
) )
.zip(std::iter::repeat(Span::styled( .zip(std::iter::repeat(Span::styled(
ui.separator.format.to_owned().unwrap_or_default(), ui.separator.format.clone().unwrap_or_default(),
ui.separator.style.to_owned(), ui.separator.style.clone(),
))) )))
.flat_map(|((a, b), c)| vec![a, b, c]) .flat_map(|((a, b), c)| vec![a, b, c])
.collect::<Vec<Span>>(); .collect::<Vec<Span>>();
@ -1200,19 +1201,13 @@ fn draw_sort_n_filter(
f.render_widget(p, layout_size); f.render_widget(p, layout_size);
} }
fn draw_logs( fn draw_logs(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) {
f: &mut Frame,
_screen_size: TuiRect,
layout_size: TuiRect,
app: &app::App,
_: &Lua,
) {
let panel_config = &app.config.general.panel_ui; let panel_config = &app.config.general.panel_ui;
let config = panel_config let config = panel_config
.default .default
.to_owned() .clone()
.extend(&panel_config.input_and_logs); .extend(&panel_config.input_and_logs);
let logs_config = app.config.general.logs.to_owned(); let logs_config = app.config.general.logs.clone();
let logs = if app.logs_hidden { let logs = if app.logs_hidden {
vec![] vec![]
} else { } else {
@ -1222,7 +1217,8 @@ fn draw_logs(
.take(layout_size.height as usize) .take(layout_size.height as usize)
.map(|log| { .map(|log| {
let fd = format_description!("[hour]:[minute]:[second]"); let fd = format_description!("[hour]:[minute]:[second]");
let time = log.created_at.format(fd).unwrap_or_else(|_| "when?".into()); let time =
log.created_at.format(fd).unwrap_or_else(|_| "when?".into());
let cfg = match log.level { let cfg = match log.level {
app::LogLevel::Info => &logs_config.info, app::LogLevel::Info => &logs_config.info,
app::LogLevel::Warning => &logs_config.warning, app::LogLevel::Warning => &logs_config.warning,
@ -1231,7 +1227,7 @@ fn draw_logs(
}; };
let prefix = let prefix =
format!("{time}|{0}", cfg.format.to_owned().unwrap_or_default()); format!("{time}|{0}", cfg.format.clone().unwrap_or_default());
let padding = " ".repeat(prefix.chars().count()); let padding = " ".repeat(prefix.chars().count());
@ -1250,7 +1246,7 @@ fn draw_logs(
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n"); .join("\n");
ListItem::new(txt).style(cfg.style.to_owned()) ListItem::new(txt).style(cfg.style.clone())
}) })
.collect::<Vec<ListItem>>() .collect::<Vec<ListItem>>()
}; };
@ -1276,50 +1272,42 @@ fn draw_logs(
f.render_widget(logs_list, layout_size); f.render_widget(logs_list, layout_size);
} }
pub fn draw_nothing( fn draw_nothing(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) {
f: &mut Frame,
_screen_size: TuiRect,
layout_size: TuiRect,
app: &app::App,
_lua: &Lua,
) {
let panel_config = &app.config.general.panel_ui; let panel_config = &app.config.general.panel_ui;
let config = panel_config.default.to_owned(); let config = panel_config.default.clone();
let nothing = Paragraph::new("").block(block(config, "".into())); let nothing = Paragraph::new("").block(block(config, "".into()));
f.render_widget(nothing, layout_size); f.render_widget(nothing, layout_size);
} }
pub fn draw_dynamic( fn draw_dynamic(
&mut self,
f: &mut Frame, f: &mut Frame,
screen_size: TuiRect,
layout_size: TuiRect, layout_size: TuiRect,
app: &mut app::App, app: &app::App,
func: &str, func: &str,
lua: &Lua,
) { ) {
let ctx = ContentRendererArg { let ctx = ContentRendererArg {
app: app.to_lua_ctx_light(), app: app.to_lua_ctx_light(),
layout_size: layout_size.into(), layout_size: layout_size.into(),
screen_size: screen_size.into(), screen_size: self.screen_size.into(),
}; };
let panel: CustomPanel = lua::serialize(lua, &ctx) let panel: CustomPanel = lua::serialize(self.lua, &ctx)
.and_then(|arg| lua::call(lua, func, arg)) .and_then(|arg| lua::call(self.lua, func, arg))
.unwrap_or_else(|e| CustomPanel::CustomParagraph { .unwrap_or_else(|e| CustomPanel::CustomParagraph {
ui: app.config.general.panel_ui.default.clone(), ui: app.config.general.panel_ui.default.clone(),
body: format!("{e:?}"), body: format!("{e:?}"),
}); });
draw_static(f, screen_size, layout_size, app, panel, lua); self.draw_static(f, layout_size, app, panel);
} }
pub fn draw_static( fn draw_static(
&mut self,
f: &mut Frame, f: &mut Frame,
screen_size: TuiRect,
layout_size: TuiRect, layout_size: TuiRect,
app: &mut app::App, app: &app::App,
panel: CustomPanel, panel: CustomPanel,
_lua: &Lua,
) { ) {
let defaultui = app.config.general.panel_ui.default.clone(); let defaultui = app.config.general.panel_ui.default.clone();
match panel { match panel {
@ -1364,7 +1352,7 @@ pub fn draw_static(
let widths = widths let widths = widths
.into_iter() .into_iter()
.map(|w| w.to_tui(screen_size, layout_size)) .map(|w| w.to_tui(self.screen_size, layout_size))
.collect::<Vec<TuiConstraint>>(); .collect::<Vec<TuiConstraint>>();
let content = Table::new(rows, widths) let content = Table::new(rows, widths)
@ -1375,68 +1363,35 @@ pub fn draw_static(
} }
CustomPanel::CustomLayout(layout) => { CustomPanel::CustomLayout(layout) => {
draw_layout(layout, f, screen_size, layout_size, app, _lua); self.draw_layout(layout, f, layout_size, app);
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Rect {
x: u16,
y: u16,
height: u16,
width: u16,
}
impl From<TuiRect> for Rect {
fn from(tui: TuiRect) -> Self {
Self {
x: tui.x,
y: tui.y,
height: tui.height,
width: tui.width,
} }
} }
} }
#[derive(Debug, Clone, Serialize)] fn draw_layout(
pub struct ContentRendererArg { &mut self,
pub app: app::LuaContextLight,
pub screen_size: Rect,
pub layout_size: Rect,
}
pub fn draw_layout(
layout: Layout, layout: Layout,
f: &mut Frame, f: &mut Frame,
screen_size: TuiRect,
layout_size: TuiRect, layout_size: TuiRect,
app: &mut app::App, app: &app::App,
lua: &Lua,
) { ) {
match layout { match layout {
Layout::Nothing => draw_nothing(f, screen_size, layout_size, app, lua), Layout::Nothing => self.draw_nothing(f, layout_size, app),
Layout::Table => draw_table(f, screen_size, layout_size, app, lua), Layout::Table => self.draw_table(f, layout_size, app),
Layout::SortAndFilter => { Layout::SortAndFilter => self.draw_sort_n_filter(f, layout_size, app),
draw_sort_n_filter(f, screen_size, layout_size, app, lua) Layout::HelpMenu => self.draw_help_menu(f, layout_size, app),
} Layout::Selection => self.draw_selection(f, layout_size, app),
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 => { Layout::InputAndLogs => {
if app.input.buffer.is_some() { if app.input.buffer.is_some() {
draw_input_buffer(f, screen_size, layout_size, app, lua); self.draw_input_buffer(f, layout_size, app);
} else { } else {
draw_logs(f, screen_size, layout_size, app, lua); self.draw_logs(f, layout_size, app);
}; };
} }
Layout::Static(panel) => { Layout::Static(panel) => self.draw_static(f, layout_size, app, *panel),
draw_static(f, screen_size, layout_size, app, *panel, lua) Layout::Dynamic(ref func) => self.draw_dynamic(f, layout_size, app, func),
}
Layout::Dynamic(ref func) => {
draw_dynamic(f, screen_size, layout_size, app, func, lua)
}
Layout::CustomContent(content) => { Layout::CustomContent(content) => {
draw_custom_content(f, screen_size, layout_size, app, *content, lua) draw_custom_content(self, f, layout_size, app, *content)
} }
Layout::Horizontal { config, splits } => { Layout::Horizontal { config, splits } => {
let chunks = TuiLayout::default() let chunks = TuiLayout::default()
@ -1444,10 +1399,10 @@ pub fn draw_layout(
.constraints( .constraints(
config config
.constraints .constraints
.to_owned() .clone()
.unwrap_or_default() .unwrap_or_default()
.iter() .iter()
.map(|c| c.to_tui(screen_size, layout_size)) .map(|c| c.to_tui(self.screen_size, layout_size))
.collect::<Vec<TuiConstraint>>(), .collect::<Vec<TuiConstraint>>(),
) )
.horizontal_margin( .horizontal_margin(
@ -1464,9 +1419,7 @@ pub fn draw_layout(
splits splits
.into_iter() .into_iter()
.zip(chunks.iter()) .zip(chunks.iter())
.for_each(|(split, chunk)| { .for_each(|(split, chunk)| self.draw_layout(split, f, *chunk, app));
draw_layout(split, f, screen_size, *chunk, app, lua)
});
} }
Layout::Vertical { config, splits } => { Layout::Vertical { config, splits } => {
@ -1475,10 +1428,10 @@ pub fn draw_layout(
.constraints( .constraints(
config config
.constraints .constraints
.to_owned() .clone()
.unwrap_or_default() .unwrap_or_default()
.iter() .iter()
.map(|c| c.to_tui(screen_size, layout_size)) .map(|c| c.to_tui(self.screen_size, layout_size))
.collect::<Vec<TuiConstraint>>(), .collect::<Vec<TuiConstraint>>(),
) )
.horizontal_margin( .horizontal_margin(
@ -1495,18 +1448,16 @@ pub fn draw_layout(
splits splits
.into_iter() .into_iter()
.zip(chunks.iter()) .zip(chunks.iter())
.for_each(|(split, chunk)| { .for_each(|(split, chunk)| self.draw_layout(split, f, *chunk, app));
draw_layout(split, f, screen_size, *chunk, app, lua)
});
} }
} }
} }
pub fn draw(f: &mut Frame, app: &mut app::App, lua: &Lua) { pub fn draw(&mut self, f: &mut Frame, app: &app::App) {
let screen_size = f.size(); self.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).clone();
self.draw_layout(layout, f, self.screen_size, app);
draw_layout(layout, f, screen_size, screen_size, app, lua); }
} }
#[cfg(test)] #[cfg(test)]
@ -1543,7 +1494,7 @@ mod tests {
}; };
assert_eq!( assert_eq!(
a.to_owned().extend(&b), a.clone().extend(&b),
Style { Style {
fg: Some(Color::Red), fg: Some(Color::Red),
bg: Some(Color::Blue), bg: Some(Color::Blue),
@ -1563,7 +1514,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
a.to_owned().extend(&c), a.clone().extend(&c),
Style { Style {
fg: Some(Color::Cyan), fg: Some(Color::Cyan),
bg: Some(Color::Magenta), bg: Some(Color::Magenta),

Loading…
Cancel
Save