Skip to content

Commit 6184588

Browse files
committed
Add support for pagination in the CLI wallet
1 parent c5e7f97 commit 6184588

File tree

5 files changed

+145
-2
lines changed

5 files changed

+145
-2
lines changed

node-gui/src/main_window/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,16 @@ impl MainWindow {
645645
backend_sender,
646646
)
647647
.map(MainWindowMessage::MainWidgetMessage),
648+
ConsoleCommand::PaginatedPrint { header, body } => self
649+
.main_widget
650+
.update(
651+
MainWidgetMessage::TabsMessage(TabsMessage::WalletMessage(
652+
wallet_id,
653+
WalletMessage::ConsoleOutput(header + &body),
654+
)),
655+
backend_sender,
656+
)
657+
.map(MainWindowMessage::MainWidgetMessage),
648658
ConsoleCommand::ClearScreen
649659
| ConsoleCommand::ClearHistory
650660
| ConsoleCommand::PrintHistory

wallet/wallet-cli-commands/src/command_handler/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,7 +1234,10 @@ where
12341234
WalletCommand::ListPendingTransactions => {
12351235
let (wallet, selected_account) = wallet_and_selected_acc(&mut self.wallet).await?;
12361236
let utxos = wallet.list_pending_transactions(selected_account).await?;
1237-
Ok(ConsoleCommand::Print(format!("{utxos:#?}")))
1237+
Ok(ConsoleCommand::PaginatedPrint {
1238+
header: "Pending Transactions".to_owned(),
1239+
body: format!("{utxos:#?}"),
1240+
})
12381241
}
12391242

12401243
WalletCommand::ListMainchainTransactions { address, limit } => {
@@ -1257,7 +1260,10 @@ where
12571260
table
12581261
};
12591262

1260-
Ok(ConsoleCommand::Print(table.to_string()))
1263+
Ok(ConsoleCommand::PaginatedPrint {
1264+
header: String::new(),
1265+
body: table.to_string(),
1266+
})
12611267
}
12621268

12631269
WalletCommand::GetTransaction { transaction_id } => {

wallet/wallet-cli-commands/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,11 @@ pub enum ManageableWalletCommand {
878878
#[derive(Debug, Clone, serde::Serialize)]
879879
pub enum ConsoleCommand {
880880
Print(String),
881+
PaginatedPrint {
882+
// TODO: add support for more structured data like table pagination
883+
header: String,
884+
body: String,
885+
},
881886
ClearScreen,
882887
PrintHistory,
883888
ClearHistory,

wallet/wallet-cli-lib/src/repl/interactive/mod.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@ mod wallet_prompt;
2121
use std::path::PathBuf;
2222

2323
use clap::Command;
24+
use itertools::Itertools;
2425
use reedline::{
2526
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
2627
ColumnarMenu, DefaultValidator, EditMode, Emacs, FileBackedHistory, ListMenu, MenuBuilder,
2728
Reedline, ReedlineMenu, Signal, Vi,
2829
};
30+
2931
use tokio::sync::{mpsc, oneshot};
32+
use utils::once_destructor::OnceDestructor;
3033
use wallet_cli_commands::{get_repl_command, parse_input, ConsoleCommand};
3134
use wallet_rpc_lib::types::NodeInterface;
3235

@@ -197,6 +200,9 @@ fn handle_response<N: NodeInterface>(
197200
Ok(Some(ConsoleCommand::Print(text))) => {
198201
console.print_line(&text);
199202
}
203+
Ok(Some(ConsoleCommand::PaginatedPrint { header, body })) => {
204+
paginate_output(header, body, line_editor, console).expect("Should not fail normally");
205+
}
200206
Ok(Some(ConsoleCommand::SetStatus {
201207
status,
202208
print_message,
@@ -226,3 +232,118 @@ fn handle_response<N: NodeInterface>(
226232
}
227233
None
228234
}
235+
236+
fn paginate_output(
237+
header: String,
238+
body: String,
239+
line_editor: &mut Reedline,
240+
console: &mut impl ConsoleOutput,
241+
) -> std::io::Result<()> {
242+
let mut current_index = 0;
243+
244+
let (mut page_rows, mut offsets) = compute_page_line_offsets(&body);
245+
let mut last_batch = offsets.len() - 1;
246+
247+
loop {
248+
line_editor.clear_screen()?;
249+
250+
let end_batch = std::cmp::min(current_index + page_rows, last_batch);
251+
let start = offsets[current_index];
252+
let end = offsets[end_batch];
253+
console.print_line(&header);
254+
console.print_line(body.get(start..end).expect("safe point"));
255+
256+
let commands = match (current_index, end_batch) {
257+
(0, end) if end == last_batch => "Press 'q' to quit",
258+
(0, _) => "Press 'j' for next, 'q' to quit",
259+
(_, end) if end == last_batch => "Press 'k' for previous, 'q' to quit",
260+
_ => "Press 'j' for next, 'k' for previous, 'q' to quit",
261+
};
262+
console.print_line(commands);
263+
264+
match read_command(current_index, last_batch, end_batch)? {
265+
PagginationCommand::Exit => break,
266+
PagginationCommand::Next => {
267+
current_index += 1;
268+
}
269+
PagginationCommand::Previous => {
270+
current_index -= 1;
271+
}
272+
PagginationCommand::TerminalResize => {
273+
(page_rows, offsets) = compute_page_line_offsets(&body);
274+
last_batch = offsets.len() - 1;
275+
current_index = std::cmp::min(current_index, last_batch - page_rows);
276+
}
277+
}
278+
}
279+
280+
line_editor.clear_screen()
281+
}
282+
283+
fn compute_page_line_offsets(body: &str) -> (usize, Vec<usize>) {
284+
let (cols, rows) = crossterm::terminal::size().unwrap_or((80, 24));
285+
let cols = cols as usize;
286+
287+
// make room for the header and prompt
288+
let page_rows = (rows - 4) as usize;
289+
290+
let position_offsets = (0..1) // prepend 0 as starting position
291+
.chain(body.char_indices().peekable().batching(|it| {
292+
// if no more characters exit
293+
it.peek()?;
294+
295+
// advance the iterator to the next new line or at least cols characters
296+
let _skip = it.take(cols).find(|(_, c)| *c == '\n');
297+
298+
match it.peek() {
299+
Some((idx, _)) => Some(*idx),
300+
None => Some(body.len()),
301+
}
302+
}))
303+
.collect_vec();
304+
(page_rows, position_offsets)
305+
}
306+
307+
enum PagginationCommand {
308+
Exit,
309+
Next,
310+
Previous,
311+
TerminalResize,
312+
}
313+
314+
fn read_command(
315+
current_index: usize,
316+
last_batch: usize,
317+
end_batch: usize,
318+
) -> Result<PagginationCommand, std::io::Error> {
319+
// TODO: maybe enable raw mode only once per paggination
320+
crossterm::terminal::enable_raw_mode()?;
321+
let _cleanup = OnceDestructor::new(|| {
322+
crossterm::terminal::disable_raw_mode().expect("Should not fail normally")
323+
});
324+
325+
loop {
326+
let event = crossterm::event::read()?;
327+
328+
match event {
329+
crossterm::event::Event::Key(key_event) => {
330+
match key_event.code {
331+
reedline::KeyCode::Char('j') | reedline::KeyCode::Down
332+
if end_batch < last_batch =>
333+
{
334+
return Ok(PagginationCommand::Next)
335+
}
336+
reedline::KeyCode::Char('k') | reedline::KeyCode::Up if current_index > 0 => {
337+
return Ok(PagginationCommand::Previous)
338+
}
339+
reedline::KeyCode::Char('q') | reedline::KeyCode::Esc => {
340+
return Ok(PagginationCommand::Exit);
341+
}
342+
_ => {} // Ignore other keys
343+
}
344+
}
345+
crossterm::event::Event::Resize(_, _) => return Ok(PagginationCommand::TerminalResize),
346+
_ => {}
347+
}
348+
}
349+
}

wallet/wallet-cli-lib/src/repl/non_interactive/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ fn to_line_output<N: NodeInterface>(
5252
) -> Result<LineOutput, WalletCliError<N>> {
5353
match command_output {
5454
ConsoleCommand::Print(text) => Ok(LineOutput::Print(text)),
55+
ConsoleCommand::PaginatedPrint { header, body } => Ok(LineOutput::Print(header + &body)),
5556
ConsoleCommand::SetStatus {
5657
status: _,
5758
print_message,

0 commit comments

Comments
 (0)