From abbfcc352a9506c9798631ece7b40b8ea3485aed Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Thu, 3 Sep 2026 09:52:13 +0100 Subject: [PATCH] Support piped input. --- include/internal/io/io.h | 15 +++++++++++++-- include/internal/ui/command.h | 2 ++ include/internal/ui/text_mode.h | 2 ++ src/internal/io/input.cc | 25 +++++++++++++++++++++++++ src/internal/io/io.cc | 20 ++++++++++++++++++-- src/internal/ui/command/command.cc | 11 +++++++++++ src/internal/ui/text_mode/text_mode.cc | 17 +++++++++++++++++ 7 files changed, 88 insertions(+), 4 deletions(-) diff --git a/include/internal/io/io.h b/include/internal/io/io.h index bdaf923..93dfd21 100644 --- a/include/internal/io/io.h +++ b/include/internal/io/io.h @@ -58,12 +58,23 @@ struct IO { static volatile std::atomic_bool resized; static void handle_sigwinch(int); + static enum struct Mode { + PIPE, + TERMINAL + } mode; + + std::string pipe_input; + std::deque input_queue; + IO(); ~IO(); IO(const IO &) = delete; IO &operator=(const IO &) = delete; + bool interactive() const { + return mode == Mode::TERMINAL; + } void enable_mouse(); void disable_mouse(); @@ -71,14 +82,14 @@ struct IO { std::pair cursor_position(); void move_cursor(uint16_t row, uint16_t col); + std::pair read_pipe(); + KeyEvent read_key(); void write(const char *, uint64_t); void write(std::string_view); void write_line(std::string_view); void run_pty(const std::string &); - std::deque input_queue; - KeyEvent::ReadResult get_next_byte(char &out); void enqueue_bytes(const std::string &bytes); static int utf8_seq_len(uint8_t byte); diff --git a/include/internal/ui/command.h b/include/internal/ui/command.h index e036828..704dac8 100644 --- a/include/internal/ui/command.h +++ b/include/internal/ui/command.h @@ -41,6 +41,8 @@ struct CommandIO { CommandIO(BEd &); std::pair run(); + std::pair run_pipe(); + std::pair run_terminal(); void redraw(); }; } // namespace bed::internal::ui diff --git a/include/internal/ui/text_mode.h b/include/internal/ui/text_mode.h index 23245c7..097e970 100644 --- a/include/internal/ui/text_mode.h +++ b/include/internal/ui/text_mode.h @@ -15,6 +15,8 @@ struct TextMode { TextMode(BEd &); std::pair run(); + std::pair run_pipe(); + std::pair run_terminal(); void grow(); void redraw(); }; diff --git a/src/internal/io/input.cc b/src/internal/io/input.cc index e83e3a4..7c8193f 100644 --- a/src/internal/io/input.cc +++ b/src/internal/io/input.cc @@ -1,6 +1,31 @@ #include "internal/io/io.h" namespace bed::internal::io { +std::pair IO::read_pipe() { + char buf[4096]; + while (true) { + auto pos = pipe_input.find('\n'); + if (pos != std::string::npos) { + std::string line = pipe_input.substr(0, pos); + pipe_input.erase(0, pos + 1); + return {line, false}; + } + ssize_t n = read(STDIN_FILENO, buf, sizeof(buf)); + if (n > 0) { + pipe_input.append(buf, n); + continue; + } + if (n == 0) { + std::string line = std::move(pipe_input); + pipe_input.clear(); + return {line, true}; + } + if (errno == EINTR) + continue; + throw fatal_error("Can't read stdin.", 1); + } +} + KeyEvent::ReadResult IO::get_next_byte(char &out) { if (!input_queue.empty()) { out = input_queue.front(); diff --git a/src/internal/io/io.cc b/src/internal/io/io.cc index 14723bb..0e2a384 100644 --- a/src/internal/io/io.cc +++ b/src/internal/io/io.cc @@ -5,8 +5,12 @@ termios IO::orig_termios{}; termios IO::raw_termios{}; bool IO::cleaned = true; volatile std::atomic_bool IO::resized(false); +IO::Mode IO::mode = IO::Mode::PIPE; IO::IO() { + if (!isatty(STDIN_FILENO)) + return; + mode = Mode::TERMINAL; if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) throw fatal_error("Can't get terminal state.", 1); struct sigaction sa{}; @@ -30,16 +34,22 @@ IO::~IO() { } void IO::enable_mouse() { + if (mode == Mode::PIPE) + throw fatal_error("no mouse in pipe mode.", 1); const char *seq = "\x1b[?1000h"; write_all(STDOUT_FILENO, seq, 8); } void IO::disable_mouse() { + if (mode == Mode::PIPE) + throw fatal_error("no mouse in pipe mode.", 1); const char *seq = "\x1b[?1000l"; write_all(STDOUT_FILENO, seq, 8); } std::pair IO::terminal_size() { + if (mode == Mode::PIPE) + throw fatal_error("no terminal size in pipe mode.", 1); struct winsize ws{}; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) throw fatal_error("Can't get terminal size.", 1); @@ -47,6 +57,8 @@ std::pair IO::terminal_size() { } std::pair IO::cursor_position() { + if (mode == Mode::PIPE) + throw fatal_error("no cursor in pipe mode.", 1); write_all(STDOUT_FILENO, "\x1b[6n", 4); std::string response; char c; @@ -69,7 +81,7 @@ std::pair IO::cursor_position() { } void IO::enable_raw() { - if (!cleaned) + if (!cleaned || mode == Mode::PIPE) return; std::string os = "\x1b[?2004h"; write_all(STDOUT_FILENO, os.c_str(), os.size()); @@ -79,7 +91,7 @@ void IO::enable_raw() { } void IO::cleanup() { - if (cleaned) + if (cleaned || mode == Mode::PIPE) return; std::string os = "\x1b[?1000l\x1b[?2004l"; write_all(STDOUT_FILENO, os.c_str(), os.size()); @@ -93,6 +105,8 @@ void IO::handle_sigwinch(int) { } void IO::move_cursor(uint16_t row, uint16_t col) { + if (mode == Mode::PIPE) + return; char buf[32]; int n = snprintf(buf, sizeof(buf), "\x1b[%u;%uH", row, col); write_all(STDOUT_FILENO, buf, n); @@ -112,6 +126,8 @@ void IO::write_line(std::string_view s) { } void IO::run_pty(const std::string &cmd) { + if (mode == Mode::PIPE) + throw ed_error("Shell running not allowed in pipe mode."); int master_fd = -1; struct winsize ws{}; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) diff --git a/src/internal/ui/command/command.cc b/src/internal/ui/command/command.cc index 402679b..bc90624 100644 --- a/src/internal/ui/command/command.cc +++ b/src/internal/ui/command/command.cc @@ -69,6 +69,17 @@ CommandIO::CommandIO(BEd &bed) : bed(bed) { } std::pair CommandIO::run() { + if (!bed.io.interactive()) + return run_pipe(); + return run_terminal(); +} + +std::pair CommandIO::run_pipe() { + bed.io.write(prompt); + return bed.io.read_pipe(); +} + +std::pair CommandIO::run_terminal() { auto [row, col] = bed.io.cursor_position(); auto [rows, cols] = bed.io.terminal_size(); if (row > rows) diff --git a/src/internal/ui/text_mode/text_mode.cc b/src/internal/ui/text_mode/text_mode.cc index eca9ded..b4b53de 100644 --- a/src/internal/ui/text_mode/text_mode.cc +++ b/src/internal/ui/text_mode/text_mode.cc @@ -7,6 +7,23 @@ TextMode::TextMode(BEd &bed) : bed(bed) { } std::pair TextMode::run() { + if (!bed.io.interactive()) + return run_pipe(); + return run_terminal(); +} + +std::pair TextMode::run_pipe() { + cmd.clear(); + while (true) { + auto [str, eof] = bed.io.read_pipe(); + if (str == "." || eof) + break; + cmd += str + "\n"; + } + return {vase::Shard::from_string(cmd.data(), cmd.length(), true), false}; +} + +std::pair TextMode::run_terminal() { auto [row, col] = bed.io.cursor_position(); auto [rows, cols] = bed.io.terminal_size(); if (row > rows)