mirror of
https://github.com/nushell/nushell.git
synced 2024-11-25 01:43:47 +01:00
Refactor explore
cursor code (#12979)
`explore` has 3 cursor-related structs that are extensively used to track the currently shown "window" of the data being shown. I was finding the cursor code quite difficult to follow, so this PR: - rewrites the base `Cursor` struct from scratch, with some tests - makes big changes to `WindowCursor` - renames `XYCursor` to `WindowCursor2D` - makes some of the cursor functions fallible as a start towards better error handling - changes lots of function names to things that I find more intuitive - adds comments, including ASCII diagrams to explain how the cursors work More work could be done (I'd like to review/change more function names in `WindowCursor` and `WindowCursor2D` and add more tests), but this is the limit of what I can get done in a weekend. I think this part of the code is in a better place now. # Testing performed I did a lot of manual testing in the record view and binary viewer, moving around with arrow keys / page up+down / home+end. This can definitely wait until after the release freeze, this area has very few automated tests and it'd be good to let the changes bake a bit.
This commit is contained in:
parent
e4104d0792
commit
a9c2349ada
@ -17,16 +17,20 @@ use crate::{
|
||||
ConfigMap, Frame, Transition, ViewInfo,
|
||||
},
|
||||
util::create_map,
|
||||
views::cursor::Position,
|
||||
};
|
||||
|
||||
use self::binary_widget::{BinarySettings, BinaryStyle, BinaryWidget};
|
||||
|
||||
use super::{cursor::XYCursor, Layout, View, ViewConfig};
|
||||
use super::{cursor::WindowCursor2D, Layout, View, ViewConfig};
|
||||
|
||||
/// An interactive view that displays binary data in a hex dump format.
|
||||
/// Not finished; many aspects are still WIP.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BinaryView {
|
||||
data: Vec<u8>,
|
||||
cursor: XYCursor,
|
||||
// HACK: we are only using the vertical dimension of the cursor, should we use a plain old WindowCursor?
|
||||
cursor: WindowCursor2D,
|
||||
settings: Settings,
|
||||
}
|
||||
|
||||
@ -40,7 +44,7 @@ impl BinaryView {
|
||||
pub fn new(data: Vec<u8>) -> Self {
|
||||
Self {
|
||||
data,
|
||||
cursor: XYCursor::default(),
|
||||
cursor: WindowCursor2D::default(),
|
||||
settings: Settings::default(),
|
||||
}
|
||||
}
|
||||
@ -95,12 +99,13 @@ impl View for BinaryView {
|
||||
|
||||
let count_rows =
|
||||
BinaryWidget::new(&self.data, self.settings.opts, Default::default()).count_lines();
|
||||
self.cursor = XYCursor::new(count_rows, 0);
|
||||
// TODO: refactor View so setup() is fallible and we don't have to panic here
|
||||
self.cursor = WindowCursor2D::new(count_rows, 1).expect("Failed to create XYCursor");
|
||||
}
|
||||
}
|
||||
|
||||
fn create_binary_widget(v: &BinaryView) -> BinaryWidget<'_> {
|
||||
let start_line = v.cursor.row_starts_at();
|
||||
let start_line = v.cursor.window_origin().row;
|
||||
let count_elements =
|
||||
BinaryWidget::new(&[], v.settings.opts, Default::default()).count_elements();
|
||||
let index = start_line * count_elements;
|
||||
@ -203,7 +208,7 @@ fn config_get_usize(config: &ConfigMap, key: &str, default: usize) -> usize {
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
fn create_report(cursor: XYCursor) -> Report {
|
||||
fn create_report(cursor: WindowCursor2D) -> Report {
|
||||
let covered_percent = report_row_position(cursor);
|
||||
let cursor = report_cursor_position(cursor);
|
||||
let mode = report_mode_name();
|
||||
@ -216,8 +221,8 @@ fn report_mode_name() -> String {
|
||||
String::from("VIEW")
|
||||
}
|
||||
|
||||
fn report_row_position(cursor: XYCursor) -> String {
|
||||
if cursor.row_starts_at() == 0 {
|
||||
fn report_row_position(cursor: WindowCursor2D) -> String {
|
||||
if cursor.window_origin().row == 0 {
|
||||
return String::from("Top");
|
||||
}
|
||||
|
||||
@ -233,10 +238,9 @@ fn report_row_position(cursor: XYCursor) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn report_cursor_position(cursor: XYCursor) -> String {
|
||||
let rows_seen = cursor.row_starts_at();
|
||||
let columns_seen = cursor.column_starts_at();
|
||||
format!("{rows_seen},{columns_seen}")
|
||||
fn report_cursor_position(cursor: WindowCursor2D) -> String {
|
||||
let Position { row, column } = cursor.window_origin();
|
||||
format!("{row},{column}")
|
||||
}
|
||||
|
||||
fn get_percentage(value: usize, max: usize) -> usize {
|
||||
|
@ -1,71 +1,125 @@
|
||||
mod windowcursor;
|
||||
mod xycursor;
|
||||
mod window_cursor;
|
||||
mod window_cursor_2d;
|
||||
|
||||
pub use windowcursor::WindowCursor;
|
||||
pub use xycursor::XYCursor;
|
||||
use anyhow::{bail, Result};
|
||||
pub use window_cursor::WindowCursor;
|
||||
pub use window_cursor_2d::{Position, WindowCursor2D};
|
||||
|
||||
/// A 1-dimensional cursor to track a position from 0 to N
|
||||
///
|
||||
/// Say we have a cursor with size=9, at position 3:
|
||||
/// 0 1 2 3 4 5 6 7 8 9
|
||||
/// | | | C | | | | | |
|
||||
///
|
||||
/// After moving forward by 2 steps:
|
||||
/// 0 1 2 3 4 5 6 7 8 9
|
||||
/// | | | | | C | | | |
|
||||
///
|
||||
/// After moving backward by 6 steps (clamped to 0):
|
||||
/// 0 1 2 3 4 5 6 7 8 9
|
||||
/// C | | | | | | | | |
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Cursor {
|
||||
index: usize,
|
||||
limit: usize,
|
||||
struct Cursor {
|
||||
/// The current position of the cursor
|
||||
position: usize,
|
||||
/// The number of distinct positions the cursor can be at
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
pub fn new(limit: usize) -> Self {
|
||||
Self { index: 0, limit }
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
|
||||
pub fn cap(&self) -> usize {
|
||||
self.limit - self.index
|
||||
}
|
||||
|
||||
pub fn set(&mut self, i: usize) -> bool {
|
||||
if i >= self.limit {
|
||||
return false;
|
||||
/// Constructor to create a new Cursor
|
||||
pub fn new(size: usize) -> Result<Self> {
|
||||
if size == 0 {
|
||||
bail!("Size cannot be zero");
|
||||
}
|
||||
|
||||
self.index = i;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn limit(&mut self, i: usize) -> bool {
|
||||
if self.index > self.limit {
|
||||
self.index = self.limit.saturating_sub(1);
|
||||
return false;
|
||||
}
|
||||
|
||||
self.limit = i;
|
||||
if self.index >= self.limit {
|
||||
self.index = self.limit.saturating_sub(1);
|
||||
}
|
||||
|
||||
true
|
||||
Ok(Cursor { position: 0, size })
|
||||
}
|
||||
|
||||
/// The max position the cursor can be at
|
||||
pub fn end(&self) -> usize {
|
||||
self.limit
|
||||
self.size - 1
|
||||
}
|
||||
|
||||
pub fn next(&mut self, i: usize) -> bool {
|
||||
if self.index + i == self.limit {
|
||||
return false;
|
||||
/// Set the position to a specific value within the bounds [0, end]
|
||||
pub fn set_position(&mut self, pos: usize) {
|
||||
if pos <= self.end() {
|
||||
self.position = pos;
|
||||
} else {
|
||||
// Clamp the position to end if out of bounds
|
||||
self.position = self.end();
|
||||
}
|
||||
|
||||
self.index += i;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, i: usize) -> bool {
|
||||
if self.index < i {
|
||||
return false;
|
||||
/// Set the size of the cursor. The position is clamped if it exceeds the new size
|
||||
pub fn set_size(&mut self, size: usize) -> Result<()> {
|
||||
if size == 0 {
|
||||
bail!("Size cannot be zero");
|
||||
}
|
||||
self.size = size;
|
||||
if self.position > self.end() {
|
||||
self.position = self.end();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
self.index -= i;
|
||||
true
|
||||
/// Move the cursor forward by a specified number of steps
|
||||
pub fn move_forward(&mut self, steps: usize) {
|
||||
if self.position + steps <= self.end() {
|
||||
self.position += steps;
|
||||
} else {
|
||||
self.position = self.end();
|
||||
}
|
||||
}
|
||||
|
||||
/// Move the cursor backward by a specified number of steps
|
||||
pub fn move_backward(&mut self, steps: usize) {
|
||||
if self.position >= steps {
|
||||
self.position -= steps;
|
||||
} else {
|
||||
self.position = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cursor_set_position() {
|
||||
// from 0 to 9
|
||||
let mut cursor = Cursor::new(10).unwrap();
|
||||
cursor.set_position(5);
|
||||
assert_eq!(cursor.position, 5);
|
||||
|
||||
cursor.set_position(15);
|
||||
assert_eq!(cursor.position, 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cursor_move_forward() {
|
||||
// from 0 to 9
|
||||
let mut cursor = Cursor::new(10).unwrap();
|
||||
assert_eq!(cursor.position, 0);
|
||||
cursor.move_forward(3);
|
||||
assert_eq!(cursor.position, 3);
|
||||
|
||||
cursor.move_forward(10);
|
||||
assert_eq!(cursor.position, 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cursor_move_backward() {
|
||||
// from 0 to 9
|
||||
let mut cursor = Cursor::new(10).unwrap();
|
||||
cursor.move_backward(3);
|
||||
assert_eq!(cursor.position, 0);
|
||||
|
||||
cursor.move_forward(5);
|
||||
assert_eq!(cursor.position, 5);
|
||||
cursor.move_backward(3);
|
||||
assert_eq!(cursor.position, 2);
|
||||
cursor.move_backward(3);
|
||||
assert_eq!(cursor.position, 0);
|
||||
}
|
||||
}
|
||||
|
146
crates/nu-explore/src/views/cursor/window_cursor.rs
Normal file
146
crates/nu-explore/src/views/cursor/window_cursor.rs
Normal file
@ -0,0 +1,146 @@
|
||||
use std::cmp::min;
|
||||
|
||||
use super::Cursor;
|
||||
use anyhow::{bail, Ok, Result};
|
||||
|
||||
/// WindowCursor provides a mechanism to navigate through a 1-dimensional range
|
||||
/// using a smaller movable window within the view.
|
||||
///
|
||||
/// View: The larger context or total allowable range for navigation.
|
||||
/// Window: The smaller, focused subset of the view.
|
||||
///
|
||||
/// Example:
|
||||
/// ```plaintext
|
||||
/// 1. Initial view of size 20 with a window of size 5. The absolute cursor position starts at 0.
|
||||
/// View :
|
||||
/// |--------------------|
|
||||
/// Window :
|
||||
/// |X====|
|
||||
///
|
||||
/// 2. After advancing the window by 3, the absolute cursor position becomes 3.
|
||||
/// View :
|
||||
/// |--------------------|
|
||||
/// Window :
|
||||
/// |X====|
|
||||
///
|
||||
/// 3. After advancing the cursor inside the window by 2, the absolute cursor position becomes 5.
|
||||
/// View :
|
||||
/// |--------------------|
|
||||
/// Window :
|
||||
/// |==X==|
|
||||
/// ```
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct WindowCursor {
|
||||
view: Cursor,
|
||||
window: Cursor,
|
||||
}
|
||||
|
||||
impl WindowCursor {
|
||||
pub fn new(view_size: usize, window_size: usize) -> Result<Self> {
|
||||
if window_size > view_size {
|
||||
bail!("Window size cannot be greater than view size");
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
view: Cursor::new(view_size)?,
|
||||
window: Cursor::new(window_size)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn absolute_position(&self) -> usize {
|
||||
self.window_starts_at() + self.window.position
|
||||
}
|
||||
|
||||
pub fn window_relative_position(&self) -> usize {
|
||||
self.window.position
|
||||
}
|
||||
|
||||
pub fn window_starts_at(&self) -> usize {
|
||||
self.view.position
|
||||
}
|
||||
|
||||
pub fn window_size(&self) -> usize {
|
||||
self.window.size
|
||||
}
|
||||
|
||||
pub fn end(&self) -> usize {
|
||||
self.view.end()
|
||||
}
|
||||
|
||||
pub fn set_window_start_position(&mut self, i: usize) {
|
||||
self.view.set_position(i)
|
||||
}
|
||||
|
||||
pub fn move_window_to_end(&mut self) {
|
||||
self.view.set_position(self.end() - self.window_size() + 1);
|
||||
}
|
||||
|
||||
pub fn set_window_size(&mut self, new_size: usize) -> Result<()> {
|
||||
if new_size > self.view.size {
|
||||
// TODO: should we return an error here or clamp? the Ok is copying existing behavior
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.window.set_size(new_size)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next_n(&mut self, n: usize) {
|
||||
for _ in 0..n {
|
||||
self.next();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) {
|
||||
if self.absolute_position() >= self.end() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.window_relative_position() == self.window.end() {
|
||||
self.view.move_forward(1);
|
||||
} else {
|
||||
self.window.move_forward(1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_window(&mut self) {
|
||||
self.move_cursor_to_end_of_window();
|
||||
|
||||
// move window forward by window size, or less if that would send it off the end of the view
|
||||
let window_end = self.window_starts_at() + self.window_size() - 1;
|
||||
let distance_from_window_end_to_view_end = self.end() - window_end;
|
||||
self.view.move_forward(min(
|
||||
distance_from_window_end_to_view_end,
|
||||
self.window_size(),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn prev_n(&mut self, n: usize) {
|
||||
for _ in 0..n {
|
||||
self.prev();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prev(&mut self) {
|
||||
if self.window_relative_position() == 0 {
|
||||
self.view.move_backward(1);
|
||||
} else {
|
||||
self.window.move_backward(1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prev_window(&mut self) {
|
||||
self.move_cursor_to_start_of_window();
|
||||
|
||||
// move the whole window back
|
||||
self.view.move_backward(self.window_size());
|
||||
}
|
||||
|
||||
pub fn move_cursor_to_start_of_window(&mut self) {
|
||||
self.window.move_backward(self.window_size());
|
||||
}
|
||||
|
||||
pub fn move_cursor_to_end_of_window(&mut self) {
|
||||
self.window.move_forward(self.window_size());
|
||||
}
|
||||
}
|
172
crates/nu-explore/src/views/cursor/window_cursor_2d.rs
Normal file
172
crates/nu-explore/src/views/cursor/window_cursor_2d.rs
Normal file
@ -0,0 +1,172 @@
|
||||
use super::WindowCursor;
|
||||
use anyhow::Result;
|
||||
|
||||
/// `WindowCursor2D` manages a 2-dimensional "window" onto a grid of cells, with a cursor that can point to a specific cell.
|
||||
/// For example, consider a 3x3 grid of cells:
|
||||
///
|
||||
/// +---+---+---+
|
||||
/// | a | b | c |
|
||||
/// |---|---|---|
|
||||
/// | d | e | f |
|
||||
/// |---|---|---|
|
||||
/// | g | h | i |
|
||||
/// +---+---+---+
|
||||
///
|
||||
/// A `WindowCursor2D` can be used to track the currently visible section of this grid.
|
||||
/// For example, a 2x2 window onto this grid could initially show the top left 2x2 section:
|
||||
///
|
||||
/// +---+---+
|
||||
/// | a | b |
|
||||
/// |---|---|
|
||||
/// | d | e |
|
||||
/// +---+---+
|
||||
///
|
||||
/// Moving the window down 1 row:
|
||||
///
|
||||
/// +---+---+
|
||||
/// | d | e |
|
||||
/// |---|---|
|
||||
/// | g | h |
|
||||
/// +---+---+
|
||||
///
|
||||
/// Inside the window, the cursor can point to a specific cell.
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct WindowCursor2D {
|
||||
x: WindowCursor,
|
||||
y: WindowCursor,
|
||||
}
|
||||
|
||||
pub struct Position {
|
||||
pub row: usize,
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
impl WindowCursor2D {
|
||||
pub fn new(count_rows: usize, count_columns: usize) -> Result<Self> {
|
||||
Ok(Self {
|
||||
x: WindowCursor::new(count_columns, count_columns)?,
|
||||
y: WindowCursor::new(count_rows, count_rows)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_window_size(&mut self, count_rows: usize, count_columns: usize) -> Result<()> {
|
||||
self.x.set_window_size(count_columns)?;
|
||||
self.y.set_window_size(count_rows)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_window_start_position(&mut self, row: usize, col: usize) {
|
||||
self.x.set_window_start_position(col);
|
||||
self.y.set_window_start_position(row);
|
||||
}
|
||||
|
||||
/// The absolute position of the cursor in the grid (0-indexed, row only)
|
||||
pub fn row(&self) -> usize {
|
||||
self.y.absolute_position()
|
||||
}
|
||||
|
||||
/// The absolute position of the cursor in the grid (0-indexed, column only)
|
||||
pub fn column(&self) -> usize {
|
||||
self.x.absolute_position()
|
||||
}
|
||||
|
||||
/// The absolute position of the cursor in the grid (0-indexed)
|
||||
pub fn position(&self) -> Position {
|
||||
Position {
|
||||
row: self.row(),
|
||||
column: self.column(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn row_limit(&self) -> usize {
|
||||
self.y.end()
|
||||
}
|
||||
|
||||
pub fn window_origin(&self) -> Position {
|
||||
Position {
|
||||
row: self.y.window_starts_at(),
|
||||
column: self.x.window_starts_at(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_relative_position(&self) -> Position {
|
||||
Position {
|
||||
row: self.y.window_relative_position(),
|
||||
column: self.x.window_relative_position(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_width_in_columns(&self) -> usize {
|
||||
self.x.window_size()
|
||||
}
|
||||
|
||||
pub fn next_row(&mut self) {
|
||||
self.y.next_n(1)
|
||||
}
|
||||
|
||||
pub fn next_row_page(&mut self) {
|
||||
self.y.next_window()
|
||||
}
|
||||
|
||||
pub fn row_move_to_end(&mut self) {
|
||||
self.y.move_window_to_end();
|
||||
self.y.move_cursor_to_end_of_window();
|
||||
}
|
||||
|
||||
pub fn row_move_to_start(&mut self) {
|
||||
self.y.move_cursor_to_start_of_window();
|
||||
self.y.set_window_start_position(0);
|
||||
}
|
||||
|
||||
pub fn prev_row(&mut self) {
|
||||
self.y.prev()
|
||||
}
|
||||
|
||||
pub fn prev_row_page(&mut self) {
|
||||
self.y.prev_window()
|
||||
}
|
||||
|
||||
pub fn next_column(&mut self) {
|
||||
self.x.next()
|
||||
}
|
||||
|
||||
pub fn next_column_by(&mut self, i: usize) {
|
||||
self.x.next_n(i)
|
||||
}
|
||||
|
||||
pub fn prev_column(&mut self) {
|
||||
self.x.prev()
|
||||
}
|
||||
|
||||
pub fn prev_column_by(&mut self, i: usize) {
|
||||
self.x.prev_n(i)
|
||||
}
|
||||
|
||||
pub fn next_column_i(&mut self) {
|
||||
self.x
|
||||
.set_window_start_position(self.x.window_starts_at() + 1)
|
||||
}
|
||||
|
||||
pub fn prev_column_i(&mut self) {
|
||||
if self.x.window_starts_at() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.x
|
||||
.set_window_start_position(self.x.window_starts_at() - 1)
|
||||
}
|
||||
|
||||
pub fn next_row_i(&mut self) {
|
||||
self.y
|
||||
.set_window_start_position(self.y.window_starts_at() + 1)
|
||||
}
|
||||
|
||||
pub fn prev_row_i(&mut self) {
|
||||
if self.y.window_starts_at() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.y
|
||||
.set_window_start_position(self.y.window_starts_at() - 1)
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
use super::Cursor;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct WindowCursor {
|
||||
view: Cursor,
|
||||
window: Cursor,
|
||||
}
|
||||
|
||||
impl WindowCursor {
|
||||
pub fn new(limit: usize, window: usize) -> Option<Self> {
|
||||
if window > limit {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
view: Cursor::new(limit),
|
||||
window: Cursor::new(window),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn index(&self) -> usize {
|
||||
self.view.index + self.window.index
|
||||
}
|
||||
|
||||
pub fn offset(&self) -> usize {
|
||||
self.window.index
|
||||
}
|
||||
|
||||
pub fn starts_at(&self) -> usize {
|
||||
self.view.index
|
||||
}
|
||||
|
||||
pub fn cap(&self) -> usize {
|
||||
self.view.cap()
|
||||
}
|
||||
|
||||
pub fn window(&self) -> usize {
|
||||
self.window.end()
|
||||
}
|
||||
|
||||
pub fn end(&self) -> usize {
|
||||
self.view.end()
|
||||
}
|
||||
|
||||
pub fn set_window_at(&mut self, i: usize) -> bool {
|
||||
self.view.set(i)
|
||||
}
|
||||
|
||||
pub fn set_window(&mut self, i: usize) -> bool {
|
||||
if i > self.view.end() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.window.limit(i)
|
||||
}
|
||||
|
||||
pub fn next(&mut self, i: usize) -> bool {
|
||||
if i > self.cap() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut rest = 0;
|
||||
for y in 0..i {
|
||||
if !self.window.next(1) {
|
||||
rest = i - y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..rest {
|
||||
if self.index() + 1 == self.end() {
|
||||
return rest != i;
|
||||
}
|
||||
|
||||
self.view.next(1);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn next_window(&mut self) -> bool {
|
||||
let end_cursor = self.window() - self.offset();
|
||||
self.next(end_cursor);
|
||||
|
||||
let mut index_move = self.window();
|
||||
if index_move + self.starts_at() >= self.end() {
|
||||
index_move = self.end() - self.starts_at();
|
||||
}
|
||||
|
||||
self.next(index_move)
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, i: usize) -> bool {
|
||||
for _ in 0..i {
|
||||
if !self.window.prev(1) {
|
||||
self.view.prev(1);
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn prev_window(&mut self) -> bool {
|
||||
self.prev(self.window() + self.offset())
|
||||
}
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
use super::WindowCursor;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct XYCursor {
|
||||
x: WindowCursor,
|
||||
y: WindowCursor,
|
||||
}
|
||||
|
||||
impl XYCursor {
|
||||
pub fn new(count_rows: usize, count_columns: usize) -> Self {
|
||||
Self {
|
||||
x: WindowCursor::new(count_columns, count_columns).expect("..."),
|
||||
y: WindowCursor::new(count_rows, count_rows).expect("..."),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_window(&mut self, count_rows: usize, count_columns: usize) {
|
||||
self.x.set_window(count_columns);
|
||||
self.y.set_window(count_rows);
|
||||
}
|
||||
|
||||
pub fn set_position(&mut self, row: usize, col: usize) {
|
||||
self.x.set_window_at(col);
|
||||
self.y.set_window_at(row);
|
||||
}
|
||||
|
||||
pub fn row(&self) -> usize {
|
||||
self.y.index()
|
||||
}
|
||||
|
||||
pub fn column(&self) -> usize {
|
||||
self.x.index()
|
||||
}
|
||||
|
||||
pub fn row_limit(&self) -> usize {
|
||||
self.y.end()
|
||||
}
|
||||
|
||||
pub fn row_starts_at(&self) -> usize {
|
||||
self.y.starts_at()
|
||||
}
|
||||
|
||||
pub fn column_starts_at(&self) -> usize {
|
||||
self.x.starts_at()
|
||||
}
|
||||
|
||||
pub fn row_window(&self) -> usize {
|
||||
self.y.offset()
|
||||
}
|
||||
|
||||
pub fn column_window(&self) -> usize {
|
||||
self.x.offset()
|
||||
}
|
||||
|
||||
pub fn column_window_size(&self) -> usize {
|
||||
self.x.window()
|
||||
}
|
||||
|
||||
pub fn next_row(&mut self) -> bool {
|
||||
self.y.next(1)
|
||||
}
|
||||
|
||||
pub fn next_row_page(&mut self) -> bool {
|
||||
self.y.next_window()
|
||||
}
|
||||
|
||||
pub fn row_move_to_end(&mut self) -> bool {
|
||||
self.y.next(self.y.cap())
|
||||
}
|
||||
|
||||
pub fn row_move_to_start(&mut self) -> bool {
|
||||
self.y.prev(self.y.index())
|
||||
}
|
||||
|
||||
pub fn prev_row(&mut self) -> bool {
|
||||
self.y.prev(1)
|
||||
}
|
||||
|
||||
pub fn prev_row_page(&mut self) -> bool {
|
||||
self.y.prev_window()
|
||||
}
|
||||
|
||||
pub fn next_column(&mut self) -> bool {
|
||||
self.x.next(1)
|
||||
}
|
||||
|
||||
pub fn next_column_by(&mut self, i: usize) -> bool {
|
||||
self.x.next(i)
|
||||
}
|
||||
|
||||
pub fn prev_column(&mut self) -> bool {
|
||||
self.x.prev(1)
|
||||
}
|
||||
|
||||
pub fn prev_column_by(&mut self, i: usize) -> bool {
|
||||
self.x.prev(i)
|
||||
}
|
||||
|
||||
pub fn next_column_i(&mut self) -> bool {
|
||||
self.x.set_window_at(self.x.starts_at() + 1)
|
||||
}
|
||||
|
||||
pub fn prev_column_i(&mut self) -> bool {
|
||||
if self.x.starts_at() == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.x.set_window_at(self.x.starts_at() - 1)
|
||||
}
|
||||
|
||||
pub fn next_row_i(&mut self) -> bool {
|
||||
self.y.set_window_at(self.y.starts_at() + 1)
|
||||
}
|
||||
|
||||
pub fn prev_row_i(&mut self) -> bool {
|
||||
if self.y.starts_at() == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.y.set_window_at(self.y.starts_at() - 1)
|
||||
}
|
||||
}
|
@ -160,7 +160,7 @@ impl View for InteractiveView<'_> {
|
||||
.as_mut()
|
||||
.expect("we know that we have a table cause of a flag");
|
||||
|
||||
let was_at_the_top = table.get_current_position().0 == 0;
|
||||
let was_at_the_top = table.get_cursor_position().row == 0;
|
||||
|
||||
if was_at_the_top && matches!(key.code, KeyCode::Up | KeyCode::PageUp) {
|
||||
self.view_mode = false;
|
||||
|
@ -1,4 +1,6 @@
|
||||
use super::{colored_text_widget::ColoredTextWidget, cursor::XYCursor, Layout, View, ViewConfig};
|
||||
use super::{
|
||||
colored_text_widget::ColoredTextWidget, cursor::WindowCursor2D, Layout, View, ViewConfig,
|
||||
};
|
||||
use crate::{
|
||||
nu_common::{NuSpan, NuText},
|
||||
pager::{report::Report, Frame, Transition, ViewInfo},
|
||||
@ -17,7 +19,7 @@ use std::cmp::max;
|
||||
pub struct Preview {
|
||||
underlying_value: Option<Value>,
|
||||
lines: Vec<String>,
|
||||
cursor: XYCursor,
|
||||
cursor: WindowCursor2D,
|
||||
}
|
||||
|
||||
impl Preview {
|
||||
@ -26,8 +28,9 @@ impl Preview {
|
||||
.lines()
|
||||
.map(|line| line.replace('\t', " ")) // tui: doesn't support TAB
|
||||
.collect();
|
||||
let cursor = XYCursor::new(lines.len(), usize::MAX);
|
||||
|
||||
// TODO: refactor so this is fallible and returns a Result instead of panicking
|
||||
let cursor = WindowCursor2D::new(lines.len(), usize::MAX).expect("Failed to create cursor");
|
||||
Self {
|
||||
lines,
|
||||
cursor,
|
||||
@ -38,10 +41,11 @@ impl Preview {
|
||||
|
||||
impl View for Preview {
|
||||
fn draw(&mut self, f: &mut Frame, area: Rect, _: ViewConfig<'_>, layout: &mut Layout) {
|
||||
self.cursor
|
||||
.set_window(area.height as usize, area.width as usize);
|
||||
let _ = self
|
||||
.cursor
|
||||
.set_window_size(area.height as usize, area.width as usize);
|
||||
|
||||
let lines = &self.lines[self.cursor.row_starts_at()..];
|
||||
let lines = &self.lines[self.cursor.window_origin().row..];
|
||||
for (i, line) in lines.iter().enumerate().take(area.height as usize) {
|
||||
let text_widget = ColoredTextWidget::new(line, self.cursor.column());
|
||||
let plain_text = text_widget.get_plain_text(area.width as usize);
|
||||
@ -65,13 +69,13 @@ impl View for Preview {
|
||||
match key.code {
|
||||
KeyCode::Left => {
|
||||
self.cursor
|
||||
.prev_column_by(max(1, self.cursor.column_window_size() / 2));
|
||||
.prev_column_by(max(1, self.cursor.window_width_in_columns() / 2));
|
||||
|
||||
Some(Transition::Ok)
|
||||
}
|
||||
KeyCode::Right => {
|
||||
self.cursor
|
||||
.next_column_by(max(1, self.cursor.column_window_size() / 2));
|
||||
.next_column_by(max(1, self.cursor.window_width_in_columns() / 2));
|
||||
|
||||
Some(Transition::Ok)
|
||||
}
|
||||
@ -128,7 +132,7 @@ impl View for Preview {
|
||||
//
|
||||
// todo: improve somehow?
|
||||
|
||||
self.cursor.set_position(row, 0);
|
||||
self.cursor.set_window_start_position(row, 0);
|
||||
true
|
||||
}
|
||||
|
||||
@ -152,7 +156,7 @@ fn set_status_end(view: &Preview, info: &mut ViewInfo) {
|
||||
}
|
||||
|
||||
fn set_status_top(view: &Preview, info: &mut ViewInfo) {
|
||||
if view.cursor.row_starts_at() == 0 {
|
||||
if view.cursor.window_origin().row == 0 {
|
||||
info.status = Some(Report::info("TOP"));
|
||||
} else {
|
||||
info.status = Some(Report::default());
|
||||
|
@ -2,7 +2,7 @@ mod table_widget;
|
||||
|
||||
use self::table_widget::{TableStyle, TableWidget, TableWidgetState};
|
||||
use super::{
|
||||
cursor::XYCursor,
|
||||
cursor::{Position, WindowCursor2D},
|
||||
util::{make_styled_string, nu_style_to_tui},
|
||||
Layout, View, ViewConfig,
|
||||
};
|
||||
@ -133,22 +133,22 @@ impl<'a> RecordView<'a> {
|
||||
layer.reset_cursor();
|
||||
}
|
||||
|
||||
pub fn get_current_position(&self) -> (usize, usize) {
|
||||
/// Get the current position of the cursor in the table as a whole
|
||||
pub fn get_cursor_position(&self) -> Position {
|
||||
let layer = self.get_layer_last();
|
||||
(layer.cursor.row(), layer.cursor.column())
|
||||
layer.cursor.position()
|
||||
}
|
||||
|
||||
pub fn get_current_window(&self) -> (usize, usize) {
|
||||
/// Get the current position of the cursor in the window being shown
|
||||
pub fn get_cursor_position_in_window(&self) -> Position {
|
||||
let layer = self.get_layer_last();
|
||||
(layer.cursor.row_window(), layer.cursor.column_window())
|
||||
layer.cursor.window_relative_position()
|
||||
}
|
||||
|
||||
pub fn get_current_offset(&self) -> (usize, usize) {
|
||||
/// Get the origin of the window being shown. (0,0), top left corner.
|
||||
pub fn get_window_origin(&self) -> Position {
|
||||
let layer = self.get_layer_last();
|
||||
(
|
||||
layer.cursor.row_starts_at(),
|
||||
layer.cursor.column_starts_at(),
|
||||
)
|
||||
layer.cursor.window_origin()
|
||||
}
|
||||
|
||||
pub fn set_cursor_mode(&mut self) {
|
||||
@ -160,7 +160,7 @@ impl<'a> RecordView<'a> {
|
||||
}
|
||||
|
||||
pub fn get_current_value(&self) -> Value {
|
||||
let (row, column) = self.get_current_position();
|
||||
let Position { row, column } = self.get_cursor_position();
|
||||
let layer = self.get_layer_last();
|
||||
|
||||
let (row, column) = match layer.orientation {
|
||||
@ -175,15 +175,17 @@ impl<'a> RecordView<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tablew(&'a self, cfg: ViewConfig<'a>) -> TableWidget<'a> {
|
||||
/// Create a table widget.
|
||||
/// WARNING: this is currently really slow on large data sets.
|
||||
/// It creates a string representation of every cell in the table and looks at every row for lscolorize.
|
||||
fn create_table_widget(&'a self, cfg: ViewConfig<'a>) -> TableWidget<'a> {
|
||||
let layer = self.get_layer_last();
|
||||
let mut data = convert_records_to_string(&layer.records, cfg.nu_config, cfg.style_computer);
|
||||
|
||||
lscolorize(&layer.columns, &mut data, cfg.lscolors);
|
||||
|
||||
let headers = layer.columns.as_ref();
|
||||
let style_computer = cfg.style_computer;
|
||||
let (row, column) = self.get_current_offset();
|
||||
let Position { row, column } = self.get_window_origin();
|
||||
|
||||
TableWidget::new(
|
||||
headers,
|
||||
@ -199,11 +201,17 @@ impl<'a> RecordView<'a> {
|
||||
fn update_cursors(&mut self, rows: usize, columns: usize) {
|
||||
match self.get_layer_last().orientation {
|
||||
Orientation::Top => {
|
||||
self.get_layer_last_mut().cursor.set_window(rows, columns);
|
||||
let _ = self
|
||||
.get_layer_last_mut()
|
||||
.cursor
|
||||
.set_window_size(rows, columns);
|
||||
}
|
||||
|
||||
Orientation::Left => {
|
||||
self.get_layer_last_mut().cursor.set_window(rows, columns);
|
||||
let _ = self
|
||||
.get_layer_last_mut()
|
||||
.cursor
|
||||
.set_window_size(rows, columns);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -226,7 +234,10 @@ impl<'a> RecordView<'a> {
|
||||
impl View for RecordView<'_> {
|
||||
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
|
||||
let mut table_layout = TableWidgetState::default();
|
||||
let table = self.create_tablew(cfg);
|
||||
// TODO: creating the table widget is O(N) where N is the number of cells in the grid.
|
||||
// Way too slow to do on every draw call!
|
||||
// To make explore work for larger data sets, this needs to be improved.
|
||||
let table = self.create_table_widget(cfg);
|
||||
f.render_stateful_widget(table, area, &mut table_layout);
|
||||
|
||||
*layout = table_layout.layout;
|
||||
@ -234,7 +245,7 @@ impl View for RecordView<'_> {
|
||||
self.update_cursors(table_layout.count_rows, table_layout.count_columns);
|
||||
|
||||
if self.mode == UIMode::Cursor {
|
||||
let (row, column) = self.get_current_window();
|
||||
let Position { row, column } = self.get_cursor_position_in_window();
|
||||
let info = get_element_info(
|
||||
layout,
|
||||
row,
|
||||
@ -308,7 +319,9 @@ impl View for RecordView<'_> {
|
||||
|
||||
for (column, _) in cells.iter().enumerate() {
|
||||
if i == pos {
|
||||
self.get_layer_last_mut().cursor.set_position(row, column);
|
||||
self.get_layer_last_mut()
|
||||
.cursor
|
||||
.set_window_start_position(row, column);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -374,7 +387,7 @@ pub struct RecordLayer<'a> {
|
||||
orientation: Orientation,
|
||||
name: Option<String>,
|
||||
was_transposed: bool,
|
||||
cursor: XYCursor,
|
||||
cursor: WindowCursor2D,
|
||||
}
|
||||
|
||||
impl<'a> RecordLayer<'a> {
|
||||
@ -384,7 +397,10 @@ impl<'a> RecordLayer<'a> {
|
||||
) -> Self {
|
||||
let columns = columns.into();
|
||||
let records = records.into();
|
||||
let cursor = XYCursor::new(records.len(), columns.len());
|
||||
|
||||
// TODO: refactor so this is fallible and returns a Result instead of panicking
|
||||
let cursor =
|
||||
WindowCursor2D::new(records.len(), columns.len()).expect("Failed to create cursor");
|
||||
|
||||
Self {
|
||||
columns,
|
||||
@ -420,7 +436,9 @@ impl<'a> RecordLayer<'a> {
|
||||
}
|
||||
|
||||
fn reset_cursor(&mut self) {
|
||||
self.cursor = XYCursor::new(self.count_rows(), self.count_columns());
|
||||
// TODO: refactor so this is fallible and returns a Result instead of panicking
|
||||
self.cursor = WindowCursor2D::new(self.count_rows(), self.count_columns())
|
||||
.expect("Failed to create cursor");
|
||||
}
|
||||
}
|
||||
|
||||
@ -634,7 +652,9 @@ fn tail_data(state: &mut RecordView<'_>, page_size: usize) {
|
||||
let layer = state.get_layer_last_mut();
|
||||
let count_rows = layer.records.len();
|
||||
if count_rows > page_size {
|
||||
layer.cursor.set_position(count_rows - page_size, 0);
|
||||
layer
|
||||
.cursor
|
||||
.set_window_start_position(count_rows - page_size, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -731,20 +751,18 @@ fn build_table_as_record(v: &RecordView) -> Value {
|
||||
Value::record(record, NuSpan::unknown())
|
||||
}
|
||||
|
||||
fn report_cursor_position(mode: UIMode, cursor: XYCursor) -> String {
|
||||
fn report_cursor_position(mode: UIMode, cursor: WindowCursor2D) -> String {
|
||||
if mode == UIMode::Cursor {
|
||||
let row = cursor.row();
|
||||
let column = cursor.column();
|
||||
let Position { row, column } = cursor.position();
|
||||
format!("{row},{column}")
|
||||
} else {
|
||||
let rows_seen = cursor.row_starts_at();
|
||||
let columns_seen = cursor.column_starts_at();
|
||||
format!("{rows_seen},{columns_seen}")
|
||||
let Position { row, column } = cursor.window_origin();
|
||||
format!("{row},{column}")
|
||||
}
|
||||
}
|
||||
|
||||
fn report_row_position(cursor: XYCursor) -> String {
|
||||
if cursor.row_starts_at() == 0 {
|
||||
fn report_row_position(cursor: WindowCursor2D) -> String {
|
||||
if cursor.window_origin().row == 0 {
|
||||
String::from("Top")
|
||||
} else {
|
||||
let percent_rows = get_percentage(cursor.row(), cursor.row_limit());
|
||||
|
Loading…
Reference in New Issue
Block a user