diff --git a/src/builtin_read.cpp b/src/builtin_read.cpp index ed6853375..705b16077 100644 --- a/src/builtin_read.cpp +++ b/src/builtin_read.cpp @@ -30,6 +30,7 @@ #include "parser.h" #include "proc.h" #include "reader.h" +#include "termios.h" #include "wcstringutil.h" #include "wgetopt.h" #include "wutil.h" // IWYU pragma: keep @@ -198,7 +199,7 @@ static int parse_cmd_opts(read_cmd_opts_t &opts, int *optind, //!OCLINT(high nc /// we weren't asked to split on null characters. static int read_interactive(parser_t &parser, wcstring &buff, int nchars, bool shell, bool silent, const wchar_t *prompt, const wchar_t *right_prompt, - const wchar_t *commandline) { + const wchar_t *commandline, int stdin) { int exit_res = STATUS_CMD_OK; // Construct a configuration. @@ -217,6 +218,8 @@ static int read_interactive(parser_t &parser, wcstring &buff, int nchars, bool s conf.left_prompt_cmd = prompt; conf.right_prompt_cmd = right_prompt; + conf.stdin = stdin; + // Don't keep history. reader_push(parser, wcstring{}, std::move(conf)); reader_get_history()->resolve_pending(); @@ -487,7 +490,7 @@ maybe_t builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **arg if (stream_stdin_is_a_tty && !opts.split_null) { // Read interactively using reader_readline(). This does not support splitting on null. exit_res = read_interactive(parser, buff, opts.nchars, opts.shell, opts.silent, - opts.prompt, opts.right_prompt, opts.commandline); + opts.prompt, opts.right_prompt, opts.commandline, streams.stdin_fd); } else if (!opts.nchars && !stream_stdin_is_a_tty && lseek(streams.stdin_fd, 0, SEEK_CUR) != -1) { exit_res = read_in_chunks(streams.stdin_fd, buff, opts.split_null); diff --git a/src/input.cpp b/src/input.cpp index 910167a9e..e82a3e30f 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -318,7 +318,7 @@ void init_input() { } } -inputter_t::inputter_t(parser_t &parser) : parser_(parser.shared()) {} +inputter_t::inputter_t(parser_t &parser, int stdin) : event_queue_(stdin), parser_(parser.shared()) {} void inputter_t::function_push_arg(wchar_t arg) { input_function_args_.push_back(arg); } diff --git a/src/input.h b/src/input.h index 35ed33290..7341dc6c8 100644 --- a/src/input.h +++ b/src/input.h @@ -23,7 +23,7 @@ void init_input(); struct input_mapping_t; class inputter_t { - input_event_queue_t event_queue_{}; + input_event_queue_t event_queue_; std::vector input_function_args_{}; bool function_status_{false}; @@ -37,11 +37,12 @@ class inputter_t { bool mapping_is_match(const input_mapping_t &m); maybe_t find_mapping(); char_event_t read_characters_no_readline(); + int stdin{0}; public: - inputter_t(parser_t &parser); + inputter_t(parser_t &parser, int stdin = 0); - /// Read a character from fd 0. Try to convert some escape sequences into character constants, + /// Read a character from stdin. Try to convert some escape sequences into character constants, /// but do not permanently block the escape character. /// /// This is performed in the same way vim does it, i.e. if an escape character is read, wait for diff --git a/src/input_common.cpp b/src/input_common.cpp index a7897008f..b391f00e1 100644 --- a/src/input_common.cpp +++ b/src/input_common.cpp @@ -46,12 +46,12 @@ void input_common_init(interrupt_func_t func) { interrupt_handler = func; } char_event_t input_event_queue_t::readb() { for (;;) { fd_set fdset; - int fd_max = 0; + int fd_max = stdin; int ioport = iothread_port(); int res; FD_ZERO(&fdset); - FD_SET(0, &fdset); + FD_SET(stdin, &fdset); if (ioport > 0) { FD_SET(ioport, &fdset); fd_max = std::max(fd_max, ioport); @@ -106,9 +106,9 @@ char_event_t input_event_queue_t::readb() { } } - if (FD_ISSET(STDIN_FILENO, &fdset)) { + if (FD_ISSET(stdin, &fdset)) { unsigned char arr[1]; - if (read_blocked(0, arr, 1) != 1) { + if (read_blocked(stdin, arr, 1) != 1) { // The teminal has been closed. return char_event_type_t::eof; } @@ -212,7 +212,7 @@ char_event_t input_event_queue_t::readch_timed(bool dequeue_timeouts) { } else { fd_set fds; FD_ZERO(&fds); - FD_SET(STDIN_FILENO, &fds); + FD_SET(stdin, &fds); struct timeval tm = {wait_on_escape_ms / 1000, 1000 * (wait_on_escape_ms % 1000)}; if (select(1, &fds, nullptr, nullptr, &tm) > 0) { result = readch(); diff --git a/src/input_common.h b/src/input_common.h index d9e3f5f74..07327ae1e 100644 --- a/src/input_common.h +++ b/src/input_common.h @@ -193,7 +193,10 @@ class input_event_queue_t { char_event_t readb(); + int stdin{0}; public: + input_event_queue_t(int in = 0) : stdin(in) {}; + /// Function used by input_readch to read bytes from stdin until enough bytes have been read to /// convert them to a wchar_t. Conversion is done using mbrtowc. If a character has previously /// been read and then 'unread' using \c input_common_unreadch, that character is returned. This diff --git a/src/reader.cpp b/src/reader.cpp index b361cccfc..b3eb3b69c 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -609,7 +609,7 @@ class reader_data_t : public std::enable_shared_from_this { reader_data_t(std::shared_ptr parser, history_t *hist, reader_config_t &&conf) : conf(std::move(conf)), parser_ref(std::move(parser)), - inputter(*parser_ref), + inputter(*parser_ref, conf.stdin), history(hist) {} void update_buff_pos(editable_line_t *el, maybe_t new_pos = none_t()); @@ -2122,7 +2122,7 @@ static void reader_interactive_init(parser_t &parser) { } // Configure terminal attributes - if (tcsetattr(0, TCSANOW, &shell_modes) == -1) { + if (tcsetattr(STDIN_FILENO, TCSANOW, &shell_modes) == -1) { if (errno == EIO) { redirect_tty_output(); } @@ -2700,7 +2700,7 @@ maybe_t reader_data_t::read_normal_chars(readline_loop_state_t &rl while (accumulated_chars.size() < limit) { bool allow_commands = (accumulated_chars.empty()); auto evt = inputter.readch(allow_commands); - if (!event_is_normal_char(evt) || !can_read(STDIN_FILENO)) { + if (!event_is_normal_char(evt) || !can_read(conf.stdin)) { event_needing_handling = std::move(evt); break; } else if (evt.input_style == char_input_style_t::notfirst && accumulated_chars.empty() && @@ -3636,8 +3636,10 @@ maybe_t reader_data_t::readline(int nchars_or_0) { /// A helper that kicks off syntax highlighting, autosuggestion computing, and repaints. auto color_suggest_repaint_now = [this] { - this->update_autosuggestion(); - this->super_highlight_me_plenty(); + if (conf.stdin == STDIN_FILENO) { + this->update_autosuggestion(); + this->super_highlight_me_plenty(); + } if (this->is_repaint_needed()) this->layout_and_repaint(L"toplevel"); this->force_exec_prompt_and_repaint = false; }; @@ -3646,10 +3648,10 @@ maybe_t reader_data_t::readline(int nchars_or_0) { force_exec_prompt_and_repaint = true; // Get the current terminal modes. These will be restored when the function returns. - if (tcgetattr(STDIN_FILENO, &old_modes) == -1 && errno == EIO) redirect_tty_output(); + if (tcgetattr(conf.stdin, &old_modes) == -1 && errno == EIO) redirect_tty_output(); // Set the new modes. - if (tcsetattr(0, TCSANOW, &shell_modes) == -1) { + if (tcsetattr(conf.stdin, TCSANOW, &shell_modes) == -1) { int err = errno; if (err == EIO) { redirect_tty_output(); @@ -3756,7 +3758,7 @@ maybe_t reader_data_t::readline(int nchars_or_0) { // Redraw the command line. This is what ensures the autosuggestion is hidden, etc. after the // user presses enter. - if (this->is_repaint_needed()) this->layout_and_repaint(L"prepare to execute"); + if (this->is_repaint_needed() || conf.stdin != STDIN_FILENO) this->layout_and_repaint(L"prepare to execute"); // Emit a newline so that the output is on the line after the command. // But do not emit a newline if the cursor has wrapped onto a new line all its own - see #6826. @@ -3764,6 +3766,12 @@ maybe_t reader_data_t::readline(int nchars_or_0) { ignore_result(write(STDOUT_FILENO, "\n", 1)); } + // HACK: If stdin isn't the same terminal as stdout, we just moved the cursor. + // For now, just reset it to the beginning of the line. + if (conf.stdin != STDIN_FILENO) { + ignore_result(write(STDOUT_FILENO, "\r", 1)); + } + // Ensure we have no pager contents when we exit. if (!pager.empty()) { // Clear to end of screen to erase the pager contents. @@ -3775,7 +3783,7 @@ maybe_t reader_data_t::readline(int nchars_or_0) { if (s_exit_state != exit_state_t::finished_handlers) { // The order of the two conditions below is important. Try to restore the mode // in all cases, but only complain if interactive. - if (tcsetattr(0, TCSANOW, &old_modes) == -1 && + if (tcsetattr(conf.stdin, TCSANOW, &old_modes) == -1 && session_interactivity() != session_interactivity_t::not_interactive) { if (errno == EIO) redirect_tty_output(); wperror(L"tcsetattr"); // return to previous mode diff --git a/src/reader.h b/src/reader.h index 200cd9533..5d3bae30e 100644 --- a/src/reader.h +++ b/src/reader.h @@ -214,6 +214,9 @@ struct reader_config_t { /// If set, do not show what is typed. bool in_silent_mode{false}; + + /// The fd for stdin, default to actual stdin. + int stdin{0}; }; /// Push a new reader environment controlled by \p conf.