diff --git a/Userland/Utilities/less.cpp b/Userland/Utilities/less.cpp index f785dae5c8..37672ccc51 100644 --- a/Userland/Utilities/less.cpp +++ b/Userland/Utilities/less.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -19,7 +20,9 @@ #include static struct termios g_save; -static struct winsize g_wsize; + +// Flag set by a SIGWINCH signal handler to notify the main loop that the window has been resized. +static Atomic g_resized { false }; static void setup_tty(bool switch_buffer) { @@ -28,11 +31,6 @@ static void setup_tty(bool switch_buffer) perror("tcgetattr(3)"); } - // Get the window size. - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &g_wsize) == -1) { - perror("ioctl(2)"); - } - struct termios raw = g_save; raw.c_lflag &= ~(ECHO | ICANON); @@ -95,11 +93,11 @@ static Vector wrap_line(Utf8View const& string, size_t width) class Pager { public: - Pager(FILE* file, FILE* tty, size_t width, size_t height) + Pager(StringView const& filename, FILE* file, FILE* tty, StringView const& prompt) : m_file(file) , m_tty(tty) - , m_width(width) - , m_height(height) + , m_filename(filename) + , m_prompt(prompt) { } @@ -184,13 +182,44 @@ public: void init() { - while (m_lines.size() < m_height) { - if (!read_line()) - break; + resize(); + } + + void resize() + { + // First, we get the current size of the window. + struct winsize window; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &window) == -1) { + perror("ioctl(2)"); + return; } - write_range(0, m_height - 1); + + auto original_height = m_height; + + m_width = window.ws_col; + m_height = window.ws_row; + + // If the window is now larger than it was before, read more lines of + // the file so that there is enough data to fill the whole screen. + // + // m_height is initialized to 0, so if the terminal was 80x25 when + // this is called for the first time, then additional_lines will be 80 + // and 80 lines of text will be buffered. + auto additional_lines = m_height - original_height; + while (additional_lines > 0) { + if (!read_line()) { + // End of file has been reached. + break; + } + --additional_lines; + } + + // Next, we repaint the whole screen. We need to figure out what line was at the top + // of the screen, and seek there and re-display everything again. + clear_status(); + out("\e[2J\e[0G\e[0d"); + write_range(m_line, m_height - 1); status_line(); - m_line = 0; fflush(m_tty); } @@ -215,16 +244,6 @@ public: out(m_tty, " \e[27m"); } - void set_filename(StringView const& filename) - { - m_filename = filename; - } - - void set_prompt(StringView const& prompt) - { - m_prompt = prompt; - } - bool read_line() { char* line = nullptr; @@ -302,19 +321,26 @@ private: FILE* m_file; FILE* m_tty; - size_t m_width; - size_t m_height; + size_t m_width { 0 }; + size_t m_height { 0 }; String m_filename; String m_prompt; }; -static String get_key_sequence() +/// Return the next key sequence, or nothing if a signal is received while waiting +/// to read the next sequence. +static Optional get_key_sequence() { // We need a buffer to handle ansi sequences. char buff[8]; + ssize_t n = read(STDOUT_FILENO, buff, sizeof(buff)); - return String(buff, n); + if (n > 0) { + return String(buff, n); + } else { + return {}; + } } static void cat_file(FILE* file) @@ -337,7 +363,7 @@ static void cat_file(FILE* file) int main(int argc, char** argv) { - if (pledge("stdio rpath tty", nullptr) < 0) { + if (pledge("stdio rpath tty sigaction", nullptr) < 0) { perror("pledge"); return 1; } @@ -367,6 +393,12 @@ int main(int argc, char** argv) exit(1); } + // On SIGWINCH set this flag so that the main-loop knows when the terminal + // has been resized. + signal(SIGWINCH, [](auto) { + g_resized = true; + }); + if (pledge("stdio tty", nullptr) < 0) { perror("pledge"); return 1; @@ -386,14 +418,22 @@ int main(int argc, char** argv) setup_tty(!dont_switch_buffer); - Pager pager(file, stdout, g_wsize.ws_col, g_wsize.ws_row); - pager.set_filename(filename); - pager.set_prompt(prompt); - + Pager pager(filename, file, stdout, prompt); pager.init(); StringBuilder modifier_buffer = StringBuilder(10); - for (String sequence;; sequence = get_key_sequence()) { + for (Optional sequence_value;; sequence_value = get_key_sequence()) { + if (g_resized) { + g_resized = false; + pager.resize(); + } + + if (!sequence_value.has_value()) { + continue; + } + + const auto& sequence = sequence_value.value(); + if (sequence.to_uint().has_value()) { modifier_buffer.append(sequence); } else {