-
Notifications
You must be signed in to change notification settings - Fork 29
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
base: master
Are you sure you want to change the base?
Conversation
let page_rows = (rows - 4) as usize; // make room for the header and prompt | ||
|
||
loop { | ||
line_editor.clear_screen().expect("Should not fail normally"); |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"down" -> "next"?
header: String, | ||
body: String, |
There was a problem hiding this comment.
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.
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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
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 thereforeConsoleOutput::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 functionConsoleOutput::print
(so that the line separator can be chosen by the caller) and mention in comments that the existingprint_line
andprint_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).
-
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.
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 | ||
} | ||
} |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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"
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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
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.
-
-
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 { |
There was a problem hiding this comment.
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)
No description provided.