Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for pagination in the CLI wallet #1894

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

OBorce
Copy link
Contributor

@OBorce OBorce commented Mar 18, 2025

No description provided.

let page_rows = (rows - 4) as usize; // make room for the header and prompt

loop {
line_editor.clear_screen().expect("Should not fail normally");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make paginate_output return a Result and propagate all errors properly.


let commands = match (current_index, end_index) {
(0, end) if end == body.len() => "Press 'q' to quit",
(0, _) => "Press 'j' for down, 'q' to quit",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"down" -> "next"?

Comment on lines +236 to +237
header: String,
body: String,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO "paginate" should accept a more structured data, where "header" is a list of column names and "body" is a table.

A TODO would be fine too for now.

Comment on lines +269 to +271
crossterm::terminal::enable_raw_mode().expect("Should not fail normally");
let event = crossterm::event::read().expect("Should not fail normally");
crossterm::terminal::disable_raw_mode().expect("Should not fail normally");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Probably the more correct way is to switch to the raw mode once at the beginning and switch back via OnceDestructor.
    (and probably we should first check that the terminal is not in the raw mode already).

    The problem is that println! (and therefore ConsoleOutput::print_line) won't work properly, because "\n" won't return the cursor to the beginning of the line ("\r" is needed for that).
    But we can just add a separate function ConsoleOutput::print (so that the line separator can be chosen by the caller) and mention in comments that the existing print_line and print_error are not suitable for a terminal in a raw mode.

    (though I'm not sure if the current implementation has any drawbacks, besides not looking nice).

  2. We probably need a way of pausing logging while in the pagination mode, because when the loop is waiting for a key press, the terminal is in the raw mode either way. So if a logging event happens, it won't be printed correctly (due to "\n" not returning the cursor).

Let's maybe at least add some TODOs for this.

Comment on lines +273 to +292
if let crossterm::event::Event::Key(key_event) = event {
match key_event.code {
reedline::KeyCode::Char('j') | reedline::KeyCode::Down
if end_index < body.len() =>
{
let next_text = body.get(current_index..).expect("safe point").lines();
let limit = compute_visible_text_limit(next_text, cols, 1);
current_index = std::cmp::min(body.len(), current_index + limit);
}
reedline::KeyCode::Char('k') | reedline::KeyCode::Up if current_index > 0 => {
let prev_text = body.get(..current_index).expect("safe point").lines().rev();
let limit = compute_visible_text_limit(prev_text, cols, 1);
current_index = current_index.saturating_sub(limit);
}
reedline::KeyCode::Char('q') | reedline::KeyCode::Esc => {
break; // Exit pagination
}
_ => {} // Ignore other keys
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's avoid calling clear_screen if a wrong key was pressed (including "j" and "k" when they are not applicable), to avoid screen flickering.

FoldWhile::Continue((new_total_rows, new_end_index))
} else {
let rows_available = page_rows - current_rows;
// make sure we cut it on the start of a utf-8 char boundary
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's either "on the start of a utf-8 char" or "at a utf-8 char boundary"

Comment on lines +312 to +318
let new_end_index = current_index
+ (1..=rows_available * cols)
.rev()
.find(|&i| line.is_char_boundary(i))
// 0 is always a safe char boundary
.unwrap_or(0);
FoldWhile::Done((page_rows, new_end_index))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Regaring unicode handling:

    • This won't work correctly in the "k" case, because here you calculate the number of bytes from the beginning of the line to some char boundary and the caller will subtract this value from current_index, treating it as the number of bytes from the end of the line to a char boundary.

    • rows_available * cols should be the number of characters and not bytes.

  2. Even in the ascii case this doesn't work well in the "k" case, because the partial row shown at the top will always have "cols" characters.
    E.g. here is how I see a top row when the size of the terminal is small.
    Initially:

    | db76bb89b8e14daeed2caba4aeb28493c95eef39cd1ea4dc71c53d77c913d0d5 | 297962
     | 1726820532     |
    

    After presing "j":

    | 1726820532     |
    

    And after pressing "j" again and then "k":

    2caba4aeb28493c95eef39cd1ea4dc71c53d77c913d0d5 | 297962      | 1726820532     |
    

I think you should build some kind of "layout" object once (well, maybe rebuilding it on Resize), and the "k"/"j" should only scroll over the lines of that "layout" and not do any text parsing.
(In the current implementation the "layout" can be a Vec<&str>, where each element represents a separate line of the output)

let event = crossterm::event::read().expect("Should not fail normally");
crossterm::terminal::disable_raw_mode().expect("Should not fail normally");

if let crossterm::event::Event::Key(key_event) = event {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to handle the Resize event as well (a TODO is fine for me)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants