From 67bf515553ae26f3acd22db515629e0ca00e0e61 Mon Sep 17 00:00:00 2001 From: Zane Zhou Date: Thu, 19 Jun 2025 17:22:07 +0800 Subject: [PATCH 1/4] fix: ScreenInteractive::FixedSize screen stomps on the history terminal output --- .../ftxui/component/screen_interactive.hpp | 9 ++++-- src/ftxui/component/screen_interactive.cpp | 31 ++++++++++--------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index f3460917..8b90a2be 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -107,9 +107,12 @@ class ScreenInteractive : public Screen { }; Dimension dimension_ = Dimension::Fixed; bool use_alternative_screen_ = false; - ScreenInteractive(int dimx, + + ScreenInteractive(Dimension dimension, + bool use_alternative_screen); + ScreenInteractive(Dimension dimension, + int dimx, int dimy, - Dimension dimension, bool use_alternative_screen); bool track_mouse_ = true; @@ -126,6 +129,8 @@ class ScreenInteractive : public Screen { bool animation_requested_ = false; animation::TimePoint previous_animation_time_; + int fixed_dimx_ = 0; + int fixed_dimy_ = 0; int cursor_x_ = 1; int cursor_y_ = 1; diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 83ff4049..bce10aeb 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -346,22 +346,31 @@ void AnimationListener(std::atomic* quit, Sender out) { } // namespace -ScreenInteractive::ScreenInteractive(int dimx, - int dimy, - Dimension dimension, +ScreenInteractive::ScreenInteractive(Dimension dimension, bool use_alternative_screen) - : Screen(dimx, dimy), + : Screen(0, 0), dimension_(dimension), use_alternative_screen_(use_alternative_screen) { task_receiver_ = MakeReceiver(); } +ScreenInteractive::ScreenInteractive(Dimension dimension, + int dimx, + int dimy, + bool use_alternative_screen) + : Screen(0, 0), + dimension_(dimension), + fixed_dimx_(dimx), fixed_dimy_(dimy), + use_alternative_screen_(use_alternative_screen) { + task_receiver_ = MakeReceiver(); +} + // static ScreenInteractive ScreenInteractive::FixedSize(int dimx, int dimy) { return { + Dimension::Fixed, dimx, dimy, - Dimension::Fixed, false, }; } @@ -380,8 +389,6 @@ ScreenInteractive ScreenInteractive::Fullscreen() { // static ScreenInteractive ScreenInteractive::FullscreenPrimaryScreen() { return { - 0, - 0, Dimension::Fullscreen, false, }; @@ -392,8 +399,6 @@ ScreenInteractive ScreenInteractive::FullscreenPrimaryScreen() { // static ScreenInteractive ScreenInteractive::FullscreenAlternateScreen() { return { - 0, - 0, Dimension::Fullscreen, true, }; @@ -402,8 +407,6 @@ ScreenInteractive ScreenInteractive::FullscreenAlternateScreen() { // static ScreenInteractive ScreenInteractive::TerminalOutput() { return { - 0, - 0, Dimension::TerminalOutput, false, }; @@ -412,8 +415,6 @@ ScreenInteractive ScreenInteractive::TerminalOutput() { // static ScreenInteractive ScreenInteractive::FitComponent() { return { - 0, - 0, Dimension::FitComponent, false, }; @@ -904,8 +905,8 @@ void ScreenInteractive::Draw(Component component) { document->ComputeRequirement(); switch (dimension_) { case Dimension::Fixed: - dimx = dimx_; - dimy = dimy_; + dimx = fixed_dimx_; + dimy = fixed_dimy_; break; case Dimension::TerminalOutput: dimx = terminal.dimx; From d86a08b8c9103ac8cd5ee7d462df15a0a96f27a7 Mon Sep 17 00:00:00 2001 From: ArthurSonzogni Date: Thu, 19 Jun 2025 12:41:23 +0200 Subject: [PATCH 2/4] Refactor --- .../ftxui/component/screen_interactive.hpp | 13 ++++------ src/ftxui/component/screen_interactive.cpp | 25 ++++++++----------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index 8b90a2be..950b1931 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -105,15 +105,14 @@ class ScreenInteractive : public Screen { Fullscreen, TerminalOutput, }; - Dimension dimension_ = Dimension::Fixed; - bool use_alternative_screen_ = false; + const Dimension dimension_ = Dimension::Fixed; + const int dimension_fixed_x_ = 0; + const int dimension_fixed_y_ = 0; + const bool use_alternative_screen_ = false; ScreenInteractive(Dimension dimension, bool use_alternative_screen); - ScreenInteractive(Dimension dimension, - int dimx, - int dimy, - bool use_alternative_screen); + ScreenInteractive(int dimx, int dimy); // Dimension::Fixed constructor. bool track_mouse_ = true; @@ -129,8 +128,6 @@ class ScreenInteractive : public Screen { bool animation_requested_ = false; animation::TimePoint previous_animation_time_; - int fixed_dimx_ = 0; - int fixed_dimy_ = 0; int cursor_x_ = 1; int cursor_y_ = 1; diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index bce10aeb..deddc0c5 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -354,25 +354,16 @@ ScreenInteractive::ScreenInteractive(Dimension dimension, task_receiver_ = MakeReceiver(); } -ScreenInteractive::ScreenInteractive(Dimension dimension, - int dimx, - int dimy, - bool use_alternative_screen) +ScreenInteractive::ScreenInteractive(int dimx, int dimy) : Screen(0, 0), - dimension_(dimension), - fixed_dimx_(dimx), fixed_dimy_(dimy), - use_alternative_screen_(use_alternative_screen) { + dimension_fixed_x_(dimx), + dimension_fixed_y_(dimy) { task_receiver_ = MakeReceiver(); } // static ScreenInteractive ScreenInteractive::FixedSize(int dimx, int dimy) { - return { - Dimension::Fixed, - dimx, - dimy, - false, - }; + return { dimx, dimy }; } /// Create a ScreenInteractive taking the full terminal size. This is using the @@ -404,6 +395,8 @@ ScreenInteractive ScreenInteractive::FullscreenAlternateScreen() { }; } +/// Create a ScreenInteractive whose width match the terminal output width and +/// the height matches the component being drawn. // static ScreenInteractive ScreenInteractive::TerminalOutput() { return { @@ -412,6 +405,8 @@ ScreenInteractive ScreenInteractive::TerminalOutput() { }; } +/// Create a ScreenInteractive whose width and height match the component being +/// drawn. // static ScreenInteractive ScreenInteractive::FitComponent() { return { @@ -905,8 +900,8 @@ void ScreenInteractive::Draw(Component component) { document->ComputeRequirement(); switch (dimension_) { case Dimension::Fixed: - dimx = fixed_dimx_; - dimy = fixed_dimy_; + dimx = dimension_fixed_x_; + dimy = dimension_fixed_y_; break; case Dimension::TerminalOutput: dimx = terminal.dimx; From 6dbe1843a7b2fc06f90df849531a549fecfd9a80 Mon Sep 17 00:00:00 2001 From: ArthurSonzogni Date: Thu, 19 Jun 2025 14:52:39 +0200 Subject: [PATCH 3/4] Fix tests. --- .../ftxui/component/screen_interactive.hpp | 6 +-- src/ftxui/component/screen_interactive.cpp | 45 ++++++++++++------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index 950b1931..6c938f2c 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -106,13 +106,12 @@ class ScreenInteractive : public Screen { TerminalOutput, }; const Dimension dimension_ = Dimension::Fixed; - const int dimension_fixed_x_ = 0; - const int dimension_fixed_y_ = 0; const bool use_alternative_screen_ = false; ScreenInteractive(Dimension dimension, + int dimx, + int dimy, bool use_alternative_screen); - ScreenInteractive(int dimx, int dimy); // Dimension::Fixed constructor. bool track_mouse_ = true; @@ -131,6 +130,7 @@ class ScreenInteractive : public Screen { int cursor_x_ = 1; int cursor_y_ = 1; + std::uint64_t frame_count_ = 0; bool mouse_captured = false; bool previous_frame_resized_ = false; diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index deddc0c5..dadeff7e 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -347,23 +347,23 @@ void AnimationListener(std::atomic* quit, Sender out) { } // namespace ScreenInteractive::ScreenInteractive(Dimension dimension, + int dimx, + int dimy, bool use_alternative_screen) - : Screen(0, 0), + : Screen(dimx, dimy), dimension_(dimension), use_alternative_screen_(use_alternative_screen) { task_receiver_ = MakeReceiver(); } -ScreenInteractive::ScreenInteractive(int dimx, int dimy) - : Screen(0, 0), - dimension_fixed_x_(dimx), - dimension_fixed_y_(dimy) { - task_receiver_ = MakeReceiver(); -} - // static ScreenInteractive ScreenInteractive::FixedSize(int dimx, int dimy) { - return { dimx, dimy }; + return { + Dimension::Fixed, + dimx, + dimy, + /*use_alternative_screen=*/false, + }; } /// Create a ScreenInteractive taking the full terminal size. This is using the @@ -379,9 +379,12 @@ ScreenInteractive ScreenInteractive::Fullscreen() { /// content might mess up with the terminal content. // static ScreenInteractive ScreenInteractive::FullscreenPrimaryScreen() { + auto terminal = Terminal::Size(); return { - Dimension::Fullscreen, - false, + Dimension::Fullscreen, + terminal.dimx, + terminal.dimy, + /*use_alternative_screen=*/false, }; } @@ -389,9 +392,12 @@ ScreenInteractive ScreenInteractive::FullscreenPrimaryScreen() { /// alternate screen buffer to avoid messing with the terminal content. // static ScreenInteractive ScreenInteractive::FullscreenAlternateScreen() { + auto terminal = Terminal::Size(); return { Dimension::Fullscreen, - true, + terminal.dimx, + terminal.dimy, + /*use_alternative_screen=*/true, }; } @@ -399,9 +405,12 @@ ScreenInteractive ScreenInteractive::FullscreenAlternateScreen() { /// the height matches the component being drawn. // static ScreenInteractive ScreenInteractive::TerminalOutput() { + auto terminal = Terminal::Size(); return { Dimension::TerminalOutput, - false, + terminal.dimx, + terminal.dimy, // Best guess. + /*use_alternative_screen=*/false, }; } @@ -409,8 +418,11 @@ ScreenInteractive ScreenInteractive::TerminalOutput() { /// drawn. // static ScreenInteractive ScreenInteractive::FitComponent() { + auto terminal = Terminal::Size(); return { Dimension::FitComponent, + terminal.dimx, // Best guess. + terminal.dimy, // Best guess. false, }; } @@ -900,8 +912,8 @@ void ScreenInteractive::Draw(Component component) { document->ComputeRequirement(); switch (dimension_) { case Dimension::Fixed: - dimx = dimension_fixed_x_; - dimy = dimension_fixed_y_; + dimx = dimx_; + dimy = dimy_; break; case Dimension::TerminalOutput: dimx = terminal.dimx; @@ -917,7 +929,7 @@ void ScreenInteractive::Draw(Component component) { break; } - const bool resized = (dimx != dimx_) || (dimy != dimy_); + const bool resized = frame_count_ == 0 || (dimx != dimx_) || (dimy != dimy_); ResetCursorPosition(); std::cout << ResetPosition(/*clear=*/resized); @@ -1000,6 +1012,7 @@ void ScreenInteractive::Draw(Component component) { Flush(); Clear(); frame_valid_ = true; + frame_count_++; } // private From 4f1a694e81af030f0c34e459e74b07a78503427f Mon Sep 17 00:00:00 2001 From: ArthurSonzogni Date: Fri, 20 Jun 2025 11:09:59 +0200 Subject: [PATCH 4/4] Add test --- .../ftxui/component/screen_interactive.hpp | 6 +- .../component/screen_interactive_test.cpp | 111 ++++++++++++++++++ 2 files changed, 113 insertions(+), 4 deletions(-) diff --git a/include/ftxui/component/screen_interactive.hpp b/include/ftxui/component/screen_interactive.hpp index 6c938f2c..f9220ff8 100644 --- a/include/ftxui/component/screen_interactive.hpp +++ b/include/ftxui/component/screen_interactive.hpp @@ -10,7 +10,6 @@ #include // for shared_ptr #include // for string #include // for thread -#include // for variant #include "ftxui/component/animation.hpp" // for TimePoint #include "ftxui/component/captured_mouse.hpp" // for CapturedMouse @@ -105,13 +104,12 @@ class ScreenInteractive : public Screen { Fullscreen, TerminalOutput, }; - const Dimension dimension_ = Dimension::Fixed; - const bool use_alternative_screen_ = false; - ScreenInteractive(Dimension dimension, int dimx, int dimy, bool use_alternative_screen); + const Dimension dimension_; + const bool use_alternative_screen_; bool track_mouse_ = true; diff --git a/src/ftxui/component/screen_interactive_test.cpp b/src/ftxui/component/screen_interactive_test.cpp index 73562fe1..299d34f7 100644 --- a/src/ftxui/component/screen_interactive_test.cpp +++ b/src/ftxui/component/screen_interactive_test.cpp @@ -10,9 +10,57 @@ #include "ftxui/component/screen_interactive.hpp" #include "ftxui/dom/elements.hpp" // for text, Element +#if defined(__unix__) +#include +#include +#include +#include +#include +#include +#endif + namespace ftxui { namespace { +#if defined(__unix__) + +// Capture the standard output (stdout) to a string. +class StdCapture { + public: + explicit StdCapture(std::string* captured) + : captured_(captured) { + if (pipe(pipefd_) != 0) return; + old_stdout_ = dup(fileno(stdout)); + fflush(stdout); + dup2(pipefd_[1], fileno(stdout)); + close(pipefd_[1]); // Close the write end in the parent + } + + ~StdCapture() { + fflush(stdout); + dup2(old_stdout_, fileno(stdout)); + close(old_stdout_); + + char buffer[1024]; + ssize_t count; + while ((count = read(pipefd_[0], buffer, sizeof(buffer))) > 0) { + captured_->append(buffer, count); + } + + close(pipefd_[0]); + } + + StdCapture(const StdCapture&) = delete; + StdCapture& operator=(const StdCapture&) = delete; + + private: + int pipefd_[2]{-1, -1}; + int old_stdout_{-1}; + std::string* const captured_; +}; + +#endif + bool TestSignal(int signal) { int called = 0; // The tree of components. This defines how to navigate using the keyboard. @@ -131,4 +179,67 @@ TEST(ScreenInteractive, CtrlC_NotForced) { ASSERT_GE(ctrl_c_count, 50); } +// Regression test for: +// https://github.com/ArthurSonzogni/FTXUI/pull/1064/files +TEST(ScreenInteractive, FixedSizeInitialFrame) { +#if defined(__unix__) + std::string output; + { + auto capture = StdCapture(&output); + + auto screen = ScreenInteractive::FixedSize(2, 2); + auto component = Renderer([&] { + return text("AB"); + }); + + Loop loop(&screen, component); + loop.RunOnce(); + } + ASSERT_EQ( + // Install the ScreenInteractive. + "\0" // Flush stdout. + "\x1BP$q q" // Set cursor shape to 1 (block). + "\x1B\\" // Reset cursor position. + "\x1B[?7l" // Disable line wrapping. + "\x1B[?1000h" // Enable mouse tracking. + "\x1B[?1003h" // Enable mouse motion tracking. + "\x1B[?1015h" // Enable mouse wheel tracking. + "\x1B[?1006h" // Enable SGR mouse tracking. + "\0" // Flush stdout. + + // Reset the screen. + "\r" // Reset cursor position. + "\x1B[2K" // Clear the line. + "\x1B[1A" // Move cursor up one line. + "\x1B[2K" // Clear the line. + + // Print the document. + "AB\r\n" // Print "AB" and move to the next line. + " " // Print two spaces to fill the line. + + // Set cursor position. + "\x1B[1D" // Move cursor left one character. + "\x1B[?25l" // Hide cursor. + + // Flush + "\0" // Flush stdout. + + // Uninstall the ScreenInteractive. + "\x1B[1C" // Move cursor right one character. + "\x1B[?1006l" // Disable SGR mouse tracking. + "\x1B[?1015l" // Disable mouse wheel tracking. + "\x1B[?1003l" // Disable mouse motion tracking. + "\x1B[?1000l" // Disable mouse tracking. + "\x1B[?7h" // Enable line wrapping. + "\x1B[?25h" // Show cursor. + "\x1B[1 q" // Set cursor shape to 1 (block). + "\0" // Flush stdout. + + // Skip one line to avoid the prompt to be printed over the last drawing. + "\r\n", + output); +#endif + +} + } // namespace ftxui