2019-12-26 15:30:25 +00:00
|
|
|
#include <common/LineReader.h>
|
|
|
|
|
2020-01-23 08:18:19 +00:00
|
|
|
#include <iostream>
|
|
|
|
#include <string_view>
|
|
|
|
|
|
|
|
#include <string.h>
|
2020-02-17 14:27:09 +00:00
|
|
|
#include <unistd.h>
|
2020-01-19 18:41:03 +00:00
|
|
|
|
2020-01-23 08:18:19 +00:00
|
|
|
#ifdef OS_LINUX
|
2020-01-19 18:41:03 +00:00
|
|
|
/// We can detect if code is linked with one or another readline variants or open the library dynamically.
|
2020-01-23 08:18:19 +00:00
|
|
|
# include <dlfcn.h>
|
2020-01-19 19:03:12 +00:00
|
|
|
extern "C"
|
|
|
|
{
|
|
|
|
char * readline(const char *) __attribute__((__weak__));
|
|
|
|
char * (*readline_ptr)(const char *) = readline;
|
|
|
|
}
|
2020-01-14 14:53:53 +00:00
|
|
|
#endif
|
|
|
|
|
2019-12-26 15:30:25 +00:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2020-01-09 16:21:04 +00:00
|
|
|
/// 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;
|
|
|
|
}
|
|
|
|
|
2019-12-26 15:30:25 +00:00
|
|
|
}
|
|
|
|
|
2020-01-01 19:22:57 +00:00
|
|
|
LineReader::Suggest::WordsRange LineReader::Suggest::getCompletions(const String & prefix, size_t prefix_length) const
|
|
|
|
{
|
|
|
|
if (!ready)
|
|
|
|
return std::make_pair(words.end(), words.end());
|
|
|
|
|
2020-01-18 15:23:59 +00:00
|
|
|
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.
|
|
|
|
|
2020-01-01 19:22:57 +00:00
|
|
|
return std::equal_range(
|
2020-01-18 15:23:59 +00:00
|
|
|
words.begin(), words.end(), last_word, [prefix_length](std::string_view s, std::string_view prefix_searched)
|
2020-01-01 19:22:57 +00:00
|
|
|
{
|
2020-01-18 15:23:59 +00:00
|
|
|
return strncmp(s.data(), prefix_searched.data(), prefix_length) < 0;
|
2020-01-01 19:22:57 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-01-23 08:18:19 +00:00
|
|
|
LineReader::LineReader(const String & history_file_path_, char extender_, char delimiter_)
|
2019-12-26 15:30:25 +00:00
|
|
|
: history_file_path(history_file_path_), extender(extender_), delimiter(delimiter_)
|
|
|
|
{
|
|
|
|
/// FIXME: check extender != delimiter
|
|
|
|
}
|
|
|
|
|
|
|
|
String LineReader::readLine(const String & first_prompt, const String & second_prompt)
|
|
|
|
{
|
|
|
|
String line;
|
|
|
|
bool is_multiline = false;
|
|
|
|
|
2019-12-27 13:11:29 +00:00
|
|
|
while (auto status = readOneLine(is_multiline ? second_prompt : first_prompt))
|
2019-12-26 15:30:25 +00:00
|
|
|
{
|
2019-12-27 13:11:29 +00:00
|
|
|
if (status == RESET_LINE)
|
|
|
|
{
|
|
|
|
line.clear();
|
|
|
|
is_multiline = false;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-12-26 15:30:25 +00:00
|
|
|
if (input.empty())
|
2020-02-06 10:11:14 +00:00
|
|
|
{
|
|
|
|
if (!line.empty() && !delimiter && !hasInputData())
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
continue;
|
|
|
|
}
|
2019-12-26 15:30:25 +00:00
|
|
|
|
2020-01-09 16:21:04 +00:00
|
|
|
is_multiline = (input.back() == extender) || (delimiter && input.back() != delimiter) || hasInputData();
|
2019-12-26 15:30:25 +00:00
|
|
|
|
|
|
|
if (input.back() == extender)
|
|
|
|
{
|
|
|
|
input = input.substr(0, input.size() - 1);
|
|
|
|
trim(input);
|
|
|
|
if (input.empty())
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
line += (line.empty() ? "" : " ") + input;
|
|
|
|
|
|
|
|
if (!is_multiline)
|
2020-02-06 10:11:14 +00:00
|
|
|
break;
|
|
|
|
}
|
2019-12-26 15:30:25 +00:00
|
|
|
|
2020-02-06 10:11:14 +00:00
|
|
|
if (!line.empty() && line != prev_line)
|
|
|
|
{
|
|
|
|
addToHistory(line);
|
|
|
|
prev_line = line;
|
2019-12-26 15:30:25 +00:00
|
|
|
}
|
|
|
|
|
2020-02-06 10:11:14 +00:00
|
|
|
return line;
|
2019-12-26 15:30:25 +00:00
|
|
|
}
|
|
|
|
|
2019-12-27 13:11:29 +00:00
|
|
|
LineReader::InputStatus LineReader::readOneLine(const String & prompt)
|
2019-12-26 15:30:25 +00:00
|
|
|
{
|
2019-12-27 13:11:29 +00:00
|
|
|
input.clear();
|
|
|
|
|
2020-01-23 08:18:19 +00:00
|
|
|
#ifdef OS_LINUX
|
2020-01-19 18:41:03 +00:00
|
|
|
if (!readline_ptr)
|
|
|
|
{
|
2020-01-19 19:03:12 +00:00
|
|
|
for (auto name : {"libreadline.so", "libreadline.so.0", "libeditline.so", "libeditline.so.0"})
|
2020-01-19 18:41:03 +00:00
|
|
|
{
|
2020-01-19 19:03:12 +00:00
|
|
|
void * dl_handle = dlopen(name, RTLD_LAZY);
|
|
|
|
if (dl_handle)
|
2020-01-19 18:48:04 +00:00
|
|
|
{
|
2020-01-19 19:03:12 +00:00
|
|
|
readline_ptr = reinterpret_cast<char * (*)(const char *)>(dlsym(dl_handle, "readline"));
|
|
|
|
if (readline_ptr)
|
2020-01-19 18:48:04 +00:00
|
|
|
{
|
2020-01-19 19:03:12 +00:00
|
|
|
break;
|
2020-01-19 18:48:04 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-19 18:41:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Minimal support for readline
|
|
|
|
if (readline_ptr)
|
|
|
|
{
|
|
|
|
char * line_read = (*readline_ptr)(prompt.c_str());
|
|
|
|
if (!line_read)
|
|
|
|
return ABORT;
|
|
|
|
input = line_read;
|
|
|
|
}
|
|
|
|
else
|
2020-01-23 08:18:19 +00:00
|
|
|
#endif
|
2020-01-19 18:41:03 +00:00
|
|
|
{
|
|
|
|
std::cout << prompt;
|
|
|
|
std::getline(std::cin, input);
|
|
|
|
if (!std::cin.good())
|
|
|
|
return ABORT;
|
|
|
|
}
|
2019-12-26 15:30:25 +00:00
|
|
|
|
2019-12-27 13:11:29 +00:00
|
|
|
trim(input);
|
|
|
|
return INPUT_LINE;
|
2019-12-26 15:30:25 +00:00
|
|
|
}
|