Skip to content

Conversation

ianthomas23
Copy link
Member

The start of a terminal pager for various git2cpp commands, initially enabled for log subcommand. Fixes #45.

pager.mp4

Supports the following keys to navigate:

d, space                   scroll down a page
u                          scroll up a page
q                          quit pager
down arrow, enter, return  scroll down a line
up arrow                   scroll up a line

If cout is not to a tty or the output is short enough to fit within a single page the pager is not used.

Still to do, probably in separate PRs:

  • Gracefully deal with lines that are longer than the terminal width.
  • Searching by regex.

To add to other subcommands, see how it is used in log_subcommand.cpp. The terminal_pager constructor does the required initialisation, and it displays on the show call. I suppose I could have done the show automatically in the destructor but that didn't seem a good idea.

Awaiting a new release of JupyterLite before we can easily try it in the terminal.

@ianthomas23 ianthomas23 marked this pull request as ready for review September 22, 2025 07:55


bool m_grabbed;
std::ostringstream m_oss;
Copy link
Member

Choose a reason for hiding this comment

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

It seems that m_oss is used only to get its internal buffer. Why not directly store a std::stringbuf instead?

#include "terminal_pager.hpp"

terminal_pager::terminal_pager()
: m_grabbed(false), m_rows(0), m_columns(0), m_start_row_index(0)
Copy link
Member

Choose a reason for hiding this comment

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

std::cout.rdbuf(std::cout.rdbuf()) is a noop, so I think we could always capture std::cout buffer, and always restore it in release_cout, even if it was not redirected. This way we could drop m_grabbed and the condition in the implementaion of release_cout.

// Unfortunately need to access _internal namespace of termcolor to check if a tty.
if (!m_grabbed && termcolor::_internal::is_atty(std::cout))
{
// Should we do anything with cerr?
Copy link
Member

Choose a reason for hiding this comment

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

We should probably capture std::cerr too as by default (at least on some platforms) it outputs to the same place as std::cout

void maybe_grab_cout();

// Return true if should stop pager.
bool process_input(const std::string& input);
Copy link
Member

Choose a reason for hiding this comment

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

I think it's fine to take the argument by value; calling it with an rvalue will trigger the string's move constructor.

auto end_row_index = m_start_row_index + m_rows - 1;

std::cout << "\e[2J"; // Erase screen.
std::cout << "\e[H"; // Cursor to top.
Copy link
Member

@JohanMabille JohanMabille Sep 24, 2025

Choose a reason for hiding this comment

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

Should we capture these "magic strings" into constants with expressive names?

return false;
case '\e': // ANSI escape sequence.
// Cannot switch on a std::string.
if (input == "\e[A" || input == "\e[1A]") // Up arrow.
Copy link
Member

@JohanMabille JohanMabille Sep 24, 2025

Choose a reason for hiding this comment

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

Same question here.

auto new_termios = old_termios;
// Disable canonical mode (buffered I/O) and echo from stdin to stdout.
new_termios.c_lflag &= (~ICANON & ~ECHO);
tcsetattr(fileno(stdin), TCSANOW, &new_termios);
Copy link
Member

Choose a reason for hiding this comment

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

I think this and the call to tcsetattr to restore the initial state line 211 should probably be in a scope object to guarantee the initial state is restored even if an excpetion is thrown.

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.

Implement pager for log and status commands
2 participants