From 38418d6356111ca2e96e41f7946ff17ea27ff42e Mon Sep 17 00:00:00 2001 From: slama Date: Wed, 7 Feb 2018 23:36:15 +0900 Subject: [PATCH] Enable multi-line edit when the line ends with a pipe (#1285) --- src/reader.cpp | 20 ++++++++++++++++++++ src/tokenizer.cpp | 32 +++++++++++++++++++++++++++++++- src/tokenizer.h | 3 +++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/reader.cpp b/src/reader.cpp index 5c05d388e..bcb5a99e5 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -302,6 +302,7 @@ static volatile sig_atomic_t interrupted = 0; // Prototypes for a bunch of functions defined later on. static bool is_backslashed(const wcstring &str, size_t pos); static wchar_t unescaped_quote(const wcstring &str, size_t pos); +static bool ends_with_pipe(const wcstring &str, size_t pos); /// Mode on startup, which we restore on exit. static struct termios terminal_mode_on_startup; @@ -2302,6 +2303,19 @@ static bool is_backslashed(const wcstring &str, size_t pos) { return (count % 2) == 1; } +/// Test if the specified string ends with pipe. Whitespaces at the end are ignored. It returns +/// false if backslashed before the pipe because it should be treated as an escaped character. +static bool ends_with_pipe(const wcstring &str, size_t pos) { + if (pos > str.size()) return false; + + while (pos--) { + wchar_t c = str.at(pos); + if (c == L'|') return !is_backslashed(str, pos); + if (!iswspace(c)) break; + } + return false; +} + static wchar_t unescaped_quote(const wcstring &str, size_t pos) { wchar_t result = L'\0'; if (pos < str.size()) { @@ -2751,6 +2765,12 @@ const wchar_t *reader_readline(int nchars) { insert_char(el, '\n'); break; } + // A newline is inserted if the line ends with a pipe (issue #1285). + if (ends_with_pipe(el->text, el->size())) { + el->position = el->size(); + insert_char(el, '\n'); + break; + } // See if this command is valid. int command_test_result = data->test_func(el->text.c_str()); diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index cb99b98e7..bdbd4d2a8 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -102,7 +102,16 @@ bool tokenizer_t::next(struct tok_t *result) { } assert(this->buff >= this->orig_buff); - result->length = current_pos >= this->last_pos ? current_pos - this->last_pos : 0; + if (this->last_type == TOK_PIPE) { + // Ignore subsequent whitespaces or a newline after a pipe (#1285). + int pipe_pos = current_pos - 1; + while (this->orig_buff[pipe_pos] != L'|') { + pipe_pos--; + } + result->length = pipe_pos - this->last_pos + 1; + } else { + result->length = current_pos >= this->last_pos ? current_pos - this->last_pos : 0; + } this->tok_next(); return true; @@ -556,6 +565,7 @@ void tokenizer_t::tok_next() { this->last_token = L"1"; this->last_type = TOK_PIPE; this->buff++; + skip_newline_after_pipe(); break; } case L'>': @@ -570,6 +580,9 @@ void tokenizer_t::tok_next() { TOK_CALL_ERROR(this, TOK_OTHER, REDIRECT_ERROR, this->buff); } else { this->buff += consumed; + if (mode == TOK_PIPE) { + skip_newline_after_pipe(); + } this->last_type = mode; this->last_token = to_string(fd); } @@ -593,6 +606,9 @@ void tokenizer_t::tok_next() { TOK_CALL_ERROR(this, TOK_OTHER, PIPE_ERROR, error_location); } else { this->buff += consumed; + if (mode == TOK_PIPE) { + skip_newline_after_pipe(); + } this->last_type = mode; this->last_token = to_string(fd); } @@ -605,6 +621,20 @@ void tokenizer_t::tok_next() { } } +/// If the line ends with pipe, continue to the next line (#1285). +void tokenizer_t::skip_newline_after_pipe() { + while (1) { + if (this->buff[0] == L'\n') { + this->buff++; + break; + } else if (my_iswspace(this->buff[0])) { + this->buff++; + } else { + break; + } + } +} + wcstring tok_first(const wcstring &str) { wcstring result; tokenizer_t t(str.c_str(), TOK_SQUASH_ERRORS); diff --git a/src/tokenizer.h b/src/tokenizer.h index c87a696b2..aaada6e7e 100644 --- a/src/tokenizer.h +++ b/src/tokenizer.h @@ -107,6 +107,9 @@ class tokenizer_t { void read_comment(); void tok_next(); + /// Skip whitespaces and a newline after a pipe at the end of the line. + void skip_newline_after_pipe(); + public: /// Constructor for a tokenizer. b is the string that is to be tokenized. It is not copied, and /// should not be freed by the caller until after the tokenizer is destroyed.