Fix ScreenInteractive::FixedSize screen stomps on the history terminal output (#1064)
Some checks failed
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (cl, cl, windows-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (clang, clang++, ubuntu-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, macos-latest) (push) Has been cancelled
Build / Bazel, ${{ matrix.cxx }}, ${{ matrix.os }} (gcc, g++, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (cl, Windows MSVC, windows-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (gcc, Linux GCC, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, Linux Clang, ubuntu-latest) (push) Has been cancelled
Build / CMake, ${{ matrix.compiler }}, ${{ matrix.os }} (llvm, llvm-cov gcov, MacOS clang, macos-latest) (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled

Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
This commit is contained in:
Zane Zhou 2025-06-20 21:59:36 +08:00 committed by GitHub
parent 6440a88dc6
commit 68fc9b1212
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 148 additions and 22 deletions

View File

@ -25,6 +25,10 @@ Next
```
Thanks @mikomikotaishi for PR #1015.
### Component
- Fix ScreenInteractive::FixedSize screen stomps on the preceding terminal
output. Thanks @zozowell in #1064.
6.1.9 (2025-05-07)
------------

View File

@ -10,7 +10,6 @@
#include <memory> // for shared_ptr
#include <string> // for string
#include <thread> // for thread
#include <variant> // for variant
#include "ftxui/component/animation.hpp" // for TimePoint
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
@ -105,12 +104,12 @@ class ScreenInteractive : public Screen {
Fullscreen,
TerminalOutput,
};
Dimension dimension_ = Dimension::Fixed;
bool use_alternative_screen_ = false;
ScreenInteractive(int dimx,
ScreenInteractive(Dimension dimension,
int dimx,
int dimy,
Dimension dimension,
bool use_alternative_screen);
const Dimension dimension_;
const bool use_alternative_screen_;
bool track_mouse_ = true;
@ -129,6 +128,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;

View File

@ -346,9 +346,9 @@ void AnimationListener(std::atomic<bool>* quit, Sender<Task> out) {
} // namespace
ScreenInteractive::ScreenInteractive(int dimx,
ScreenInteractive::ScreenInteractive(Dimension dimension,
int dimx,
int dimy,
Dimension dimension,
bool use_alternative_screen)
: Screen(dimx, dimy),
dimension_(dimension),
@ -359,10 +359,10 @@ ScreenInteractive::ScreenInteractive(int dimx,
// static
ScreenInteractive ScreenInteractive::FixedSize(int dimx, int dimy) {
return {
Dimension::Fixed,
dimx,
dimy,
Dimension::Fixed,
false,
/*use_alternative_screen=*/false,
};
}
@ -379,11 +379,12 @@ ScreenInteractive ScreenInteractive::Fullscreen() {
/// content might mess up with the terminal content.
// static
ScreenInteractive ScreenInteractive::FullscreenPrimaryScreen() {
auto terminal = Terminal::Size();
return {
0,
0,
Dimension::Fullscreen,
false,
Dimension::Fullscreen,
terminal.dimx,
terminal.dimy,
/*use_alternative_screen=*/false,
};
}
@ -391,30 +392,37 @@ ScreenInteractive ScreenInteractive::FullscreenPrimaryScreen() {
/// alternate screen buffer to avoid messing with the terminal content.
// static
ScreenInteractive ScreenInteractive::FullscreenAlternateScreen() {
auto terminal = Terminal::Size();
return {
0,
0,
Dimension::Fullscreen,
true,
terminal.dimx,
terminal.dimy,
/*use_alternative_screen=*/true,
};
}
/// Create a ScreenInteractive whose width match the terminal output width and
/// the height matches the component being drawn.
// static
ScreenInteractive ScreenInteractive::TerminalOutput() {
auto terminal = Terminal::Size();
return {
0,
0,
Dimension::TerminalOutput,
false,
terminal.dimx,
terminal.dimy, // Best guess.
/*use_alternative_screen=*/false,
};
}
/// Create a ScreenInteractive whose width and height match the component being
/// drawn.
// static
ScreenInteractive ScreenInteractive::FitComponent() {
auto terminal = Terminal::Size();
return {
0,
0,
Dimension::FitComponent,
terminal.dimx, // Best guess.
terminal.dimy, // Best guess.
false,
};
}
@ -921,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);
@ -1004,6 +1012,7 @@ void ScreenInteractive::Draw(Component component) {
Flush();
Clear();
frame_valid_ = true;
frame_count_++;
}
// private

View File

@ -10,9 +10,57 @@
#include "ftxui/component/screen_interactive.hpp"
#include "ftxui/dom/elements.hpp" // for text, Element
#if defined(__unix__)
#include <fcntl.h>
#include <unistd.h>
#include <array>
#include <cstdio>
#include <ftxui/component/loop.hpp>
#include <string>
#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,69 @@ 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();
}
using namespace std::string_view_literals;
auto expected =
// 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"sv;
ASSERT_EQ(expected, output);
#endif
}
} // namespace ftxui