From 97cf63b555f2ecd2fabee2d4124a5b394b60ee97 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 19 Sep 2025 14:16:17 +0100 Subject: [PATCH 1/7] Implement terminal_pager for log subcommand --- CMakeLists.txt | 2 + src/subcommand/log_subcommand.cpp | 5 + src/utils/terminal_pager.cpp | 238 ++++++++++++++++++++++++++++++ src/utils/terminal_pager.hpp | 63 ++++++++ 4 files changed, 308 insertions(+) create mode 100644 src/utils/terminal_pager.cpp create mode 100644 src/utils/terminal_pager.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index aa4a7ac..78c1bb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,8 @@ set(GIT2CPP_SRC ${GIT2CPP_SOURCE_DIR}/utils/git_exception.cpp ${GIT2CPP_SOURCE_DIR}/utils/git_exception.hpp ${GIT2CPP_SOURCE_DIR}/utils/output.hpp + ${GIT2CPP_SOURCE_DIR}/utils/terminal_pager.cpp + ${GIT2CPP_SOURCE_DIR}/utils/terminal_pager.hpp ${GIT2CPP_SOURCE_DIR}/wrapper/annotated_commit_wrapper.cpp ${GIT2CPP_SOURCE_DIR}/wrapper/annotated_commit_wrapper.hpp ${GIT2CPP_SOURCE_DIR}/wrapper/branch_wrapper.cpp diff --git a/src/subcommand/log_subcommand.cpp b/src/subcommand/log_subcommand.cpp index 3fa1746..c295c7f 100644 --- a/src/subcommand/log_subcommand.cpp +++ b/src/subcommand/log_subcommand.cpp @@ -7,6 +7,7 @@ #include #include "log_subcommand.hpp" +#include "../utils/terminal_pager.hpp" #include "../wrapper/repository_wrapper.hpp" #include "../wrapper/commit_wrapper.hpp" @@ -90,6 +91,8 @@ void log_subcommand::run() git_revwalk_new(&walker, repo); git_revwalk_push_head(walker); + terminal_pager pager; + std::size_t i=0; git_oid commit_oid; while (!git_revwalk_next(&commit_oid, walker) && i +#include +#include +#include + +// OS-specific libraries. +#include +#include + +#include + +#include "terminal_pager.hpp" + +terminal_pager::terminal_pager() + : m_grabbed(false), m_rows(0), m_columns(0), m_start_row_index(0) +{ + maybe_grab_cout(); +} + +terminal_pager::~terminal_pager() +{ + release_cout(); +} + +std::string terminal_pager::get_input() const +{ + // Blocks until input received. + std::string str; + char ch; + std::cin.get(ch); + str += ch; + + if (ch == '\e') // Start of ANSI escape sequence. + { + do + { + std::cin.get(ch); + str += ch; + } while (!std::isalpha(ch)); // ANSI escape sequence ends with a letter. + } + + return str; +} + +void terminal_pager::maybe_grab_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? + m_cout_rdbuf = std::cout.rdbuf(m_oss.rdbuf()); + m_grabbed = true; + } +} + +bool terminal_pager::process_input(const std::string& input) +{ + if (input.size() == 0) + { + return true; + } + + switch (input[0]) + { + case 'q': + case 'Q': + return true; // Exit pager. + case 'u': + case 'U': + scroll(true, true); // Up a page. + return false; + case 'd': + case 'D': + case ' ': + scroll(false, true); // Down a page. + return false; + case '\n': + scroll(false, false); // Down a line. + return false; + case '\e': // ANSI escape sequence. + // Cannot switch on a std::string. + if (input == "\e[A" || input == "\e[1A]") // Up arrow. + { + scroll(true, false); // Up a line. + return false; + } + else if (input == "\e[B" || input == "\e[1B]") // Down arrow. + { + scroll(false, false); // Down a line. + return false; + } + } + + std::cout << '\a'; // Emit BEL for visual feedback. + return false; +} + +void terminal_pager::release_cout() +{ + if (m_grabbed) + { + std::cout.rdbuf(m_cout_rdbuf); + m_grabbed = false; + } +} + +void terminal_pager::render_terminal() const +{ + auto end_row_index = m_start_row_index + m_rows - 1; + + std::cout << "\e[2J"; // Erase screen. + std::cout << "\e[H"; // Cursor to top. + + for (size_t i = m_start_row_index; i < end_row_index; i++) + { + if (i >= m_lines.size()) + { + break; + } + std::cout << m_lines[i] << std::endl; + } + + std::cout << "\e[" << m_rows << "H"; // Move cursor to bottom row of terminal. + std::cout << ":"; +} + +void terminal_pager::scroll(bool up, bool page) +{ + update_terminal_size(); + const auto old_start_row_index = m_start_row_index; + size_t offset = page ? m_rows - 1 : 1; + + if (up) + { + // Care needed to avoid underflow of unsigned size_t. + if (m_start_row_index >= offset) + { + m_start_row_index -= offset; + } + else + { + m_start_row_index = 0; + } + } + else + { + m_start_row_index += offset; + auto end_row_index = m_start_row_index + m_rows - 1; + if (end_row_index > m_lines.size()) + { + m_start_row_index = m_lines.size() - (m_rows - 1); + } + } + + if (m_start_row_index == old_start_row_index) + { + // No change, emit BEL for visual feedback. + std::cout << '\a'; + } + else + { + render_terminal(); + } +} + +void terminal_pager::show() +{ + if (!m_grabbed) + { + return; + } + + release_cout(); + + split_input_at_newlines(m_oss.view()); + + update_terminal_size(); + if (m_rows == 0 || m_lines.size() <= m_rows - 1) + { + // Don't need to use pager, can display directly. + for (auto line : m_lines) + { + std::cout << line << std::endl; + } + m_lines.clear(); + return; + } + + struct termios old_termios; + tcgetattr(fileno(stdin), &old_termios); + 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); + + std::cout << "\e[?1049h"; // Enable alternative buffer. + + m_start_row_index = 0; + render_terminal(); + + bool stop = false; + do + { + stop = process_input(get_input()); + } while (!stop); + + std::cout << "\e[?1049l"; // Disable alternative buffer. + + // Restore original termios settings. + tcsetattr(fileno(stdin), TCSANOW, &old_termios); + + m_lines.clear(); + m_start_row_index = 0; +} + +void terminal_pager::split_input_at_newlines(std::string_view str) +{ + auto split = str | std::ranges::views::split('\n') + | std::ranges::views::transform([](auto&& range) { + return std::string(range.begin(), std::ranges::distance(range)); + }); + m_lines = std::vector{split.begin(), split.end()}; +} + +void terminal_pager::update_terminal_size() +{ + struct winsize size; + int err = ioctl(fileno(stdout), TIOCGWINSZ, &size); + if (err == 0) + { + m_rows = size.ws_row; + m_columns = size.ws_col; + } + else + { + m_rows = m_columns = 0; + } +} diff --git a/src/utils/terminal_pager.hpp b/src/utils/terminal_pager.hpp new file mode 100644 index 0000000..9b35465 --- /dev/null +++ b/src/utils/terminal_pager.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include +#include + +/** + * Terminal pager that displays output written to stdout one page at a time, allowing the user to + * interactively scroll up and down. If cout is not a tty or the output is shorter than a single + * terminal page it does nothing. + * + * It expects all of cout to be written before the first page is displayed, so it does not pipe from + * cout which would be a more complicated implementation allowing the first page to be displayed + * before all of the output is written. This may need to be reconsidered if we need more performant + * handling of slow subcommands such as `git2cpp log` of repos with long histories. + * + * Keys handled: + * 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 + * + * Emits a BEL (ASCII 7) for unrecognised keys or attempts to scroll too far, which is used by some + * terminals for visual and/or audible feedback. + * + * Does not respond to a change of terminal size whilst it is waiting for input, but it will the + * next time the output is scrolled. + */ +class terminal_pager +{ +public: + terminal_pager(); + + ~terminal_pager(); + + void show(); + +private: + std::string get_input() const; + + void maybe_grab_cout(); + + // Return true if should stop pager. + bool process_input(const std::string& input); + + void release_cout(); + + void render_terminal() const; + + void scroll(bool up, bool page); + + void split_input_at_newlines(std::string_view str); + + void update_terminal_size(); + + + bool m_grabbed; + std::ostringstream m_oss; + std::streambuf* m_cout_rdbuf; + std::vector m_lines; + size_t m_rows, m_columns; + size_t m_start_row_index; +}; From e083e4875d2ce93c0d4a16ad58835f37f4d9f6f7 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Mon, 22 Sep 2025 08:43:33 +0100 Subject: [PATCH 2/7] Include cstdint --- src/utils/terminal_pager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/terminal_pager.cpp b/src/utils/terminal_pager.cpp index 1ee2405..6d0fb59 100644 --- a/src/utils/terminal_pager.cpp +++ b/src/utils/terminal_pager.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include From 44c04bea613db627816467e28e0060a082deb53a Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 10 Oct 2025 13:09:14 +0100 Subject: [PATCH 3/7] Use stringbuf instead of ostringstream --- src/utils/terminal_pager.cpp | 4 ++-- src/utils/terminal_pager.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/terminal_pager.cpp b/src/utils/terminal_pager.cpp index 6d0fb59..4df02c6 100644 --- a/src/utils/terminal_pager.cpp +++ b/src/utils/terminal_pager.cpp @@ -49,7 +49,7 @@ void terminal_pager::maybe_grab_cout() if (!m_grabbed && termcolor::_internal::is_atty(std::cout)) { // Should we do anything with cerr? - m_cout_rdbuf = std::cout.rdbuf(m_oss.rdbuf()); + m_cout_rdbuf = std::cout.rdbuf(&m_stringbuf); m_grabbed = true; } } @@ -173,7 +173,7 @@ void terminal_pager::show() release_cout(); - split_input_at_newlines(m_oss.view()); + split_input_at_newlines(m_stringbuf.view()); update_terminal_size(); if (m_rows == 0 || m_lines.size() <= m_rows - 1) diff --git a/src/utils/terminal_pager.hpp b/src/utils/terminal_pager.hpp index 9b35465..aa1795b 100644 --- a/src/utils/terminal_pager.hpp +++ b/src/utils/terminal_pager.hpp @@ -55,7 +55,7 @@ class terminal_pager bool m_grabbed; - std::ostringstream m_oss; + std::stringbuf m_stringbuf; std::streambuf* m_cout_rdbuf; std::vector m_lines; size_t m_rows, m_columns; From f7e4643b378d52b227321553dff01687c340462c Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 10 Oct 2025 13:22:28 +0100 Subject: [PATCH 4/7] Remove m_grabbed --- src/utils/terminal_pager.cpp | 20 +++++++------------- src/utils/terminal_pager.hpp | 1 - 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/utils/terminal_pager.cpp b/src/utils/terminal_pager.cpp index 4df02c6..576836e 100644 --- a/src/utils/terminal_pager.cpp +++ b/src/utils/terminal_pager.cpp @@ -13,7 +13,7 @@ #include "terminal_pager.hpp" terminal_pager::terminal_pager() - : m_grabbed(false), m_rows(0), m_columns(0), m_start_row_index(0) + : m_rows(0), m_columns(0), m_start_row_index(0) { maybe_grab_cout(); } @@ -46,11 +46,14 @@ std::string terminal_pager::get_input() const void terminal_pager::maybe_grab_cout() { // Unfortunately need to access _internal namespace of termcolor to check if a tty. - if (!m_grabbed && termcolor::_internal::is_atty(std::cout)) + if (termcolor::_internal::is_atty(std::cout)) { // Should we do anything with cerr? m_cout_rdbuf = std::cout.rdbuf(&m_stringbuf); - m_grabbed = true; + } + else + { + m_cout_rdbuf = std::cout.rdbuf(); } } @@ -98,11 +101,7 @@ bool terminal_pager::process_input(const std::string& input) void terminal_pager::release_cout() { - if (m_grabbed) - { - std::cout.rdbuf(m_cout_rdbuf); - m_grabbed = false; - } + std::cout.rdbuf(m_cout_rdbuf); } void terminal_pager::render_terminal() const @@ -166,11 +165,6 @@ void terminal_pager::scroll(bool up, bool page) void terminal_pager::show() { - if (!m_grabbed) - { - return; - } - release_cout(); split_input_at_newlines(m_stringbuf.view()); diff --git a/src/utils/terminal_pager.hpp b/src/utils/terminal_pager.hpp index aa1795b..9422f98 100644 --- a/src/utils/terminal_pager.hpp +++ b/src/utils/terminal_pager.hpp @@ -54,7 +54,6 @@ class terminal_pager void update_terminal_size(); - bool m_grabbed; std::stringbuf m_stringbuf; std::streambuf* m_cout_rdbuf; std::vector m_lines; From 0edd017015e2520ab92041b1e475a23ba7ed21f3 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 10 Oct 2025 13:49:41 +0100 Subject: [PATCH 5/7] Pass by value to process_input --- src/utils/terminal_pager.cpp | 2 +- src/utils/terminal_pager.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/terminal_pager.cpp b/src/utils/terminal_pager.cpp index 576836e..8b2b77a 100644 --- a/src/utils/terminal_pager.cpp +++ b/src/utils/terminal_pager.cpp @@ -57,7 +57,7 @@ void terminal_pager::maybe_grab_cout() } } -bool terminal_pager::process_input(const std::string& input) +bool terminal_pager::process_input(std::string input) { if (input.size() == 0) { diff --git a/src/utils/terminal_pager.hpp b/src/utils/terminal_pager.hpp index 9422f98..8c710a1 100644 --- a/src/utils/terminal_pager.hpp +++ b/src/utils/terminal_pager.hpp @@ -41,7 +41,7 @@ class terminal_pager void maybe_grab_cout(); // Return true if should stop pager. - bool process_input(const std::string& input); + bool process_input(std::string input); void release_cout(); From afd276a2ed07453069bec1c1d6329c3631a096b7 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 10 Oct 2025 14:19:54 +0100 Subject: [PATCH 6/7] Separate namespace for ANSI code sequences to avoid magic strings --- CMakeLists.txt | 2 ++ src/utils/ansi_code.cpp | 24 ++++++++++++++++++++++++ src/utils/ansi_code.hpp | 29 +++++++++++++++++++++++++++++ src/utils/output.hpp | 5 +++-- src/utils/terminal_pager.cpp | 22 +++++++++++----------- 5 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 src/utils/ansi_code.cpp create mode 100644 src/utils/ansi_code.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 78c1bb7..1e7f475 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,8 @@ set(GIT2CPP_SRC ${GIT2CPP_SOURCE_DIR}/subcommand/reset_subcommand.hpp ${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.cpp ${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.hpp + ${GIT2CPP_SOURCE_DIR}/utils/ansi_code.cpp + ${GIT2CPP_SOURCE_DIR}/utils/ansi_code.hpp ${GIT2CPP_SOURCE_DIR}/utils/common.cpp ${GIT2CPP_SOURCE_DIR}/utils/common.hpp ${GIT2CPP_SOURCE_DIR}/utils/git_exception.cpp diff --git a/src/utils/ansi_code.cpp b/src/utils/ansi_code.cpp new file mode 100644 index 0000000..44e4701 --- /dev/null +++ b/src/utils/ansi_code.cpp @@ -0,0 +1,24 @@ +#include "ansi_code.hpp" + +namespace ansi_code +{ + std::string cursor_to_row(size_t row) + { + return "\e[" + std::to_string(row) + "H"; + } + + bool is_down_arrow(std::string str) + { + return str == "\e[B" || str == "\e[1B]"; + } + + bool is_escape_char(char ch) + { + return ch == '\e'; + } + + bool is_up_arrow(std::string str) + { + return str == "\e[A" || str == "\e[1A]"; + } +} diff --git a/src/utils/ansi_code.hpp b/src/utils/ansi_code.hpp new file mode 100644 index 0000000..90b1e25 --- /dev/null +++ b/src/utils/ansi_code.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +/** + * ANSI escape codes. + * Use `termcolor` for colours. + */ +namespace ansi_code +{ + // Constants. + const std::string bel = "\a"; // ASCII 7, used for audio/visual feedback. + const std::string cursor_to_top = "\e[H"; + const std::string erase_screen = "\e[2J"; + + const std::string enable_alternative_buffer = "\e[?1049h"; + const std::string disable_alternative_buffer = "\e[?1049l"; + + const std::string hide_cursor = "\e[?25l"; + const std::string show_cursor = "\e[?25h"; + + // Functions. + std::string cursor_to_row(size_t row); + + bool is_escape_char(char ch); + + bool is_down_arrow(std::string str); + bool is_up_arrow(std::string str); +} diff --git a/src/utils/output.hpp b/src/utils/output.hpp index 173c2a5..cc33a79 100644 --- a/src/utils/output.hpp +++ b/src/utils/output.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include "ansi_code.hpp" #include "common.hpp" // Scope object to hide the cursor. This avoids @@ -10,11 +11,11 @@ struct cursor_hider : noncopyable_nonmovable { cursor_hider() { - std::cout << "\e[?25l"; + std::cout << ansi_code::hide_cursor; } ~cursor_hider() { - std::cout << "\e[?25h"; + std::cout << ansi_code::show_cursor; } }; diff --git a/src/utils/terminal_pager.cpp b/src/utils/terminal_pager.cpp index 8b2b77a..c99154e 100644 --- a/src/utils/terminal_pager.cpp +++ b/src/utils/terminal_pager.cpp @@ -10,6 +10,7 @@ #include +#include "ansi_code.hpp" #include "terminal_pager.hpp" terminal_pager::terminal_pager() @@ -31,7 +32,7 @@ std::string terminal_pager::get_input() const std::cin.get(ch); str += ch; - if (ch == '\e') // Start of ANSI escape sequence. + if (ansi_code::is_escape_char(ch)) // Start of ANSI escape sequence. { do { @@ -83,19 +84,19 @@ bool terminal_pager::process_input(std::string input) return false; case '\e': // ANSI escape sequence. // Cannot switch on a std::string. - if (input == "\e[A" || input == "\e[1A]") // Up arrow. + if (ansi_code::is_up_arrow(input)) { scroll(true, false); // Up a line. return false; } - else if (input == "\e[B" || input == "\e[1B]") // Down arrow. + else if (ansi_code::is_down_arrow(input)) { scroll(false, false); // Down a line. return false; } } - std::cout << '\a'; // Emit BEL for visual feedback. + std::cout << ansi_code::bel; return false; } @@ -108,8 +109,8 @@ void terminal_pager::render_terminal() const { auto end_row_index = m_start_row_index + m_rows - 1; - std::cout << "\e[2J"; // Erase screen. - std::cout << "\e[H"; // Cursor to top. + std::cout << ansi_code::erase_screen; + std::cout << ansi_code::cursor_to_top; for (size_t i = m_start_row_index; i < end_row_index; i++) { @@ -120,7 +121,7 @@ void terminal_pager::render_terminal() const std::cout << m_lines[i] << std::endl; } - std::cout << "\e[" << m_rows << "H"; // Move cursor to bottom row of terminal. + std::cout << ansi_code::cursor_to_row(m_rows); // Move cursor to bottom row of terminal. std::cout << ":"; } @@ -154,8 +155,7 @@ void terminal_pager::scroll(bool up, bool page) if (m_start_row_index == old_start_row_index) { - // No change, emit BEL for visual feedback. - std::cout << '\a'; + std::cout << ansi_code::bel; } else { @@ -188,7 +188,7 @@ void terminal_pager::show() new_termios.c_lflag &= (~ICANON & ~ECHO); tcsetattr(fileno(stdin), TCSANOW, &new_termios); - std::cout << "\e[?1049h"; // Enable alternative buffer. + std::cout << ansi_code::enable_alternative_buffer; m_start_row_index = 0; render_terminal(); @@ -199,7 +199,7 @@ void terminal_pager::show() stop = process_input(get_input()); } while (!stop); - std::cout << "\e[?1049l"; // Disable alternative buffer. + std::cout << ansi_code::disable_alternative_buffer; // Restore original termios settings. tcsetattr(fileno(stdin), TCSANOW, &old_termios); From b77eb871d13f0edaf86187d827151310839a2ce6 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 10 Oct 2025 14:42:46 +0100 Subject: [PATCH 7/7] alternative_buffer scope object --- CMakeLists.txt | 1 + src/utils/output.cpp | 23 +++++++++++++++++++++++ src/utils/output.hpp | 16 ++++++++++++++++ src/utils/terminal_pager.cpp | 16 ++-------------- 4 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 src/utils/output.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e7f475..ba71a22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ set(GIT2CPP_SRC ${GIT2CPP_SOURCE_DIR}/utils/common.hpp ${GIT2CPP_SOURCE_DIR}/utils/git_exception.cpp ${GIT2CPP_SOURCE_DIR}/utils/git_exception.hpp + ${GIT2CPP_SOURCE_DIR}/utils/output.cpp ${GIT2CPP_SOURCE_DIR}/utils/output.hpp ${GIT2CPP_SOURCE_DIR}/utils/terminal_pager.cpp ${GIT2CPP_SOURCE_DIR}/utils/terminal_pager.hpp diff --git a/src/utils/output.cpp b/src/utils/output.cpp new file mode 100644 index 0000000..71584b1 --- /dev/null +++ b/src/utils/output.cpp @@ -0,0 +1,23 @@ +#include "output.hpp" + +// OS-specific libraries. +#include + +alternative_buffer::alternative_buffer() +{ + tcgetattr(fileno(stdin), &m_previous_termios); + auto new_termios = m_previous_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); + + std::cout << ansi_code::enable_alternative_buffer; +} + +alternative_buffer::~alternative_buffer() +{ + std::cout << ansi_code::disable_alternative_buffer; + + // Restore previous termios settings. + tcsetattr(fileno(stdin), TCSANOW, &m_previous_termios); +} diff --git a/src/utils/output.hpp b/src/utils/output.hpp index cc33a79..803c20d 100644 --- a/src/utils/output.hpp +++ b/src/utils/output.hpp @@ -4,6 +4,9 @@ #include "ansi_code.hpp" #include "common.hpp" +// OS-specific libraries. +#include + // Scope object to hide the cursor. This avoids // cursor twinkling when rewritting the same line // too frequently. @@ -19,3 +22,16 @@ struct cursor_hider : noncopyable_nonmovable std::cout << ansi_code::show_cursor; } }; + +// Scope object to use alternative output buffer for +// fullscreen interactive terminal input/output. +class alternative_buffer : noncopyable_nonmovable +{ +public: + alternative_buffer(); + + ~alternative_buffer(); + +private: + struct termios m_previous_termios; +}; diff --git a/src/utils/terminal_pager.cpp b/src/utils/terminal_pager.cpp index c99154e..e7fe551 100644 --- a/src/utils/terminal_pager.cpp +++ b/src/utils/terminal_pager.cpp @@ -6,11 +6,11 @@ // OS-specific libraries. #include -#include #include #include "ansi_code.hpp" +#include "output.hpp" #include "terminal_pager.hpp" terminal_pager::terminal_pager() @@ -181,14 +181,7 @@ void terminal_pager::show() return; } - struct termios old_termios; - tcgetattr(fileno(stdin), &old_termios); - 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); - - std::cout << ansi_code::enable_alternative_buffer; + alternative_buffer alt_buffer; m_start_row_index = 0; render_terminal(); @@ -199,11 +192,6 @@ void terminal_pager::show() stop = process_input(get_input()); } while (!stop); - std::cout << ansi_code::disable_alternative_buffer; - - // Restore original termios settings. - tcsetattr(fileno(stdin), TCSANOW, &old_termios); - m_lines.clear(); m_start_row_index = 0; }