diff --git a/src/Common/LineReader.cpp b/src/Common/LineReader.cpp deleted file mode 100644 index ed7a53e2e83..00000000000 --- a/src/Common/LineReader.cpp +++ /dev/null @@ -1,185 +0,0 @@ -#include - -#include -#include - -#include -#include - -#ifdef OS_LINUX -/// We can detect if code is linked with one or another readline variants or open the library dynamically. -# include -extern "C" -{ - char * readline(const char *) __attribute__((__weak__)); - char * (*readline_ptr)(const char *) = readline; -} -#endif - -namespace -{ - -/// Trim ending whitespace inplace -void trim(String & s) -{ - s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end()); -} - -/// Check if multi-line query is inserted from the paste buffer. -/// Allows delaying the start of query execution until the entirety of query is inserted. -bool hasInputData() -{ - timeval timeout = {0, 0}; - fd_set fds{}; - FD_ZERO(&fds); - FD_SET(STDIN_FILENO, &fds); - return select(1, &fds, nullptr, nullptr, &timeout) == 1; -} - -} - -LineReader::Suggest::WordsRange LineReader::Suggest::getCompletions(const String & prefix, size_t prefix_length) const -{ - if (!ready) - return std::make_pair(words.end(), words.end()); - - std::string_view last_word; - - auto last_word_pos = prefix.find_last_of(word_break_characters); - if (std::string::npos == last_word_pos) - last_word = prefix; - else - last_word = std::string_view(prefix).substr(last_word_pos + 1, std::string::npos); - - /// last_word can be empty. - - /// Only perform case sensitive completion when the prefix string contains any uppercase characters - if (std::none_of(prefix.begin(), prefix.end(), [&](auto c) { return c >= 'A' && c <= 'Z'; })) - return std::equal_range( - words_no_case.begin(), words_no_case.end(), last_word, [prefix_length](std::string_view s, std::string_view prefix_searched) - { - return strncasecmp(s.data(), prefix_searched.data(), prefix_length) < 0; - }); - else - return std::equal_range(words.begin(), words.end(), last_word, [prefix_length](std::string_view s, std::string_view prefix_searched) - { - return strncmp(s.data(), prefix_searched.data(), prefix_length) < 0; - }); -} - -LineReader::LineReader(const String & history_file_path_, bool multiline_, Patterns extenders_, Patterns delimiters_) - : history_file_path(history_file_path_), multiline(multiline_), extenders(std::move(extenders_)), delimiters(std::move(delimiters_)) -{ - /// FIXME: check extender != delimiter -} - -String LineReader::readLine(const String & first_prompt, const String & second_prompt) -{ - String line; - bool need_next_line = false; - - while (auto status = readOneLine(need_next_line ? second_prompt : first_prompt)) - { - if (status == RESET_LINE) - { - line.clear(); - need_next_line = false; - continue; - } - - if (input.empty()) - { - if (!line.empty() && !multiline && !hasInputData()) - break; - else - continue; - } - -#if !defined(ARCADIA_BUILD) /// C++20 - const char * has_extender = nullptr; - for (const auto * extender : extenders) - { - if (input.ends_with(extender)) - { - has_extender = extender; - break; - } - } - - const char * has_delimiter = nullptr; - for (const auto * delimiter : delimiters) - { - if (input.ends_with(delimiter)) - { - has_delimiter = delimiter; - break; - } - } - - need_next_line = has_extender || (multiline && !has_delimiter) || hasInputData(); - - if (has_extender) - { - input.resize(input.size() - strlen(has_extender)); - trim(input); - if (input.empty()) - continue; - } -#endif - - line += (line.empty() ? "" : " ") + input; - - if (!need_next_line) - break; - } - - if (!line.empty() && line != prev_line) - { - addToHistory(line); - prev_line = line; - } - - return line; -} - -LineReader::InputStatus LineReader::readOneLine(const String & prompt) -{ - input.clear(); - -#ifdef OS_LINUX - if (!readline_ptr) - { - for (const auto * name : {"libreadline.so", "libreadline.so.0", "libeditline.so", "libeditline.so.0"}) - { - void * dl_handle = dlopen(name, RTLD_LAZY); - if (dl_handle) - { - readline_ptr = reinterpret_cast(dlsym(dl_handle, "readline")); - if (readline_ptr) - { - break; - } - } - } - } - - /// Minimal support for readline - if (readline_ptr) - { - char * line_read = (*readline_ptr)(prompt.c_str()); - if (!line_read) - return ABORT; - input = line_read; - } - else -#endif - { - std::cout << prompt; - std::getline(std::cin, input); - if (!std::cin.good()) - return ABORT; - } - - trim(input); - return INPUT_LINE; -} diff --git a/src/Common/LineReader.h b/src/Common/LineReader.h deleted file mode 100644 index f31459078ab..00000000000 --- a/src/Common/LineReader.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include - -#include -#include - -class LineReader -{ -public: - struct Suggest - { - using Words = std::vector; - using WordsRange = std::pair; - - Words words; - Words words_no_case; - std::atomic ready{false}; - - /// Get iterators for the matched range of words if any. - WordsRange getCompletions(const String & prefix, size_t prefix_length) const; - }; - - using Patterns = std::vector; - - LineReader(const String & history_file_path, bool multiline, Patterns extenders, Patterns delimiters); - virtual ~LineReader() {} - - /// Reads the whole line until delimiter (in multiline mode) or until the last line without extender. - /// If resulting line is empty, it means the user interrupted the input. - /// Non-empty line is appended to history - without duplication. - /// Typical delimiter is ';' (semicolon) and typical extender is '\' (backslash). - String readLine(const String & first_prompt, const String & second_prompt); - - /// When bracketed paste mode is set, pasted text is bracketed with control sequences so - /// that the program can differentiate pasted text from typed-in text. This helps - /// clickhouse-client so that without -m flag, one can still paste multiline queries, and - /// possibly get better pasting performance. See https://cirw.in/blog/bracketed-paste for - /// more details. - virtual void enableBracketedPaste() {} - -protected: - enum InputStatus - { - ABORT = 0, - RESET_LINE, - INPUT_LINE, - }; - - const String history_file_path; - static constexpr char word_break_characters[] = " \t\n\r\"\\'`@$><=;|&{(."; - - String input; - -private: - bool multiline; - - Patterns extenders; - Patterns delimiters; - - String prev_line; - - virtual InputStatus readOneLine(const String & prompt); - virtual void addToHistory(const String &) {} -}; diff --git a/src/Common/ReadlineLineReader.cpp b/src/Common/ReadlineLineReader.cpp deleted file mode 100644 index 9363dadd04f..00000000000 --- a/src/Common/ReadlineLineReader.cpp +++ /dev/null @@ -1,182 +0,0 @@ -#if defined(USE_READLINE) - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace -{ - -/// Trim ending whitespace inplace -void trim(String & s) -{ - s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end()); -} - -} - -static const LineReader::Suggest * suggest; - -/// Points to current word to suggest. -static LineReader::Suggest::Words::const_iterator pos; -/// Points after the last possible match. -static LineReader::Suggest::Words::const_iterator end; - -/// Set iterators to the matched range of words if any. -static void findRange(const char * prefix, size_t prefix_length) -{ - std::string prefix_str(prefix); - std::tie(pos, end) = suggest->getCompletions(prefix_str, prefix_length); -} - -/// Iterates through matched range. -static char * nextMatch() -{ - if (pos >= end) - return nullptr; - - /// readline will free memory by itself. - char * word = strdup(pos->c_str()); - ++pos; - return word; -} - -static char * generate(const char * text, int state) -{ - if (!suggest->ready) - return nullptr; - if (state == 0) - findRange(text, strlen(text)); - - /// Do not append whitespace after word. For unknown reason, rl_completion_append_character = '\0' does not work. - rl_completion_suppress_append = 1; - - return nextMatch(); -}; - -ReadlineLineReader::ReadlineLineReader( - const Suggest & suggest_, const String & history_file_path_, bool multiline_, Patterns extenders_, Patterns delimiters_) - : LineReader(history_file_path_, multiline_, std::move(extenders_), std::move(delimiters_)) -{ - suggest = &suggest_; - - if (!history_file_path.empty()) - { - int res = read_history(history_file_path.c_str()); - if (res) - std::cerr << "Cannot read history from file " + history_file_path + ": "+ strerror(errno) << std::endl; - } - - /// Added '.' to the default list. Because it is used to separate database and table. - rl_basic_word_break_characters = word_break_characters; - - /// Not append whitespace after single suggestion. Because whitespace after function name is meaningless. - rl_completion_append_character = '\0'; - - rl_completion_entry_function = generate; - - /// Install Ctrl+C signal handler that will be used in interactive mode. - - if (rl_initialize()) - throw std::runtime_error("Cannot initialize readline"); - - auto clear_prompt_or_exit = [](int) - { - /// This is signal safe. - ssize_t res = write(STDOUT_FILENO, "\n", 1); - - /// Allow to quit client while query is in progress by pressing Ctrl+C twice. - /// (First press to Ctrl+C will try to cancel query by InterruptListener). - if (res == 1 && rl_line_buffer[0] && !RL_ISSTATE(RL_STATE_DONE)) - { - rl_replace_line("", 0); - if (rl_forced_update_display()) - _exit(0); - } - else - { - /// A little dirty, but we struggle to find better way to correctly - /// force readline to exit after returning from the signal handler. - _exit(0); - } - }; - - if (signal(SIGINT, clear_prompt_or_exit) == SIG_ERR) - throw std::runtime_error(std::string("Cannot set signal handler for readline: ") + strerror(errno)); - - rl_variable_bind("completion-ignore-case", "on"); -} - -ReadlineLineReader::~ReadlineLineReader() -{ -} - -LineReader::InputStatus ReadlineLineReader::readOneLine(const String & prompt) -{ - input.clear(); - - const char* cinput = readline(prompt.c_str()); - if (cinput == nullptr) - return (errno != EAGAIN) ? ABORT : RESET_LINE; - input = cinput; - - trim(input); - return INPUT_LINE; -} - -void ReadlineLineReader::addToHistory(const String & line) -{ - add_history(line.c_str()); -} - -#if RL_VERSION_MAJOR >= 7 - -#define BRACK_PASTE_PREF "\033[200~" -#define BRACK_PASTE_SUFF "\033[201~" - -#define BRACK_PASTE_LAST '~' -#define BRACK_PASTE_SLEN 6 - -/// This handler bypasses some unused macro/event checkings and remove trailing newlines before insertion. -static int clickhouse_rl_bracketed_paste_begin(int /* count */, int /* key */) -{ - std::string buf; - buf.reserve(128); - - RL_SETSTATE(RL_STATE_MOREINPUT); - SCOPE_EXIT(RL_UNSETSTATE(RL_STATE_MOREINPUT)); - int c; - while ((c = rl_read_key()) >= 0) - { - if (c == '\r') - c = '\n'; - buf.push_back(c); - if (buf.size() >= BRACK_PASTE_SLEN && c == BRACK_PASTE_LAST && buf.substr(buf.size() - BRACK_PASTE_SLEN) == BRACK_PASTE_SUFF) - { - buf.resize(buf.size() - BRACK_PASTE_SLEN); - break; - } - } - trim(buf); - return static_cast(rl_insert_text(buf.c_str())) == buf.size() ? 0 : 1; -} - -#endif - -void ReadlineLineReader::enableBracketedPaste() -{ -#if RL_VERSION_MAJOR >= 7 - rl_variable_bind("enable-bracketed-paste", "on"); - - /// Use our bracketed paste handler to get better user experience. See comments above. - rl_bind_keyseq(BRACK_PASTE_PREF, clickhouse_rl_bracketed_paste_begin); -#endif -}; - -#endif diff --git a/src/Common/ReadlineLineReader.h b/src/Common/ReadlineLineReader.h deleted file mode 100644 index 95bd23b4634..00000000000 --- a/src/Common/ReadlineLineReader.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "LineReader.h" - -#include -#include - -class ReadlineLineReader : public LineReader -{ -public: - ReadlineLineReader(const Suggest & suggest, const String & history_file_path, bool multiline, Patterns extenders_, Patterns delimiters_); - ~ReadlineLineReader() override; - - void enableBracketedPaste() override; - -private: - InputStatus readOneLine(const String & prompt) override; - void addToHistory(const String & line) override; -};