#include "clicompleter.h" #include "completionhelper.h" #include "cli.h" #include "cli_config.h" #include "common/unused.h" #include "commands/clicommand.h" #include "parser/lexer.h" #include "commands/clicommandfactory.h" #include #include #if defined(Q_OS_WIN32) #include "readline.h" #elif defined(Q_OS_UNIX) #include #endif CliCompleter* CliCompleter::instance = nullptr; CliCompleter::CliCompleter() { } void CliCompleter::init(CLI* value) { rl_attempted_completion_function = CliCompleter::complete; cli = value; } CliCompleter* CliCompleter::getInstance() { if (!instance) instance = new CliCompleter(); return instance; } char** CliCompleter::complete(const char* text, int start, int end) { UNUSED(start); #ifdef Q_OS_UNIX // Unix readline needs this to disable the completion using rl_completion_entry_function rl_attempted_completion_over = 1; #endif return toCharArray(getInstance()->completeInternal(QString::fromLocal8Bit(text), QString::fromLocal8Bit(rl_line_buffer), end)); } QStringList CliCompleter::completeInternal(const QString& toBeReplaced, const QString& text, int curPos) { QString str; if (!cli->getLine().isEmpty()) { str += cli->getLine(); curPos += str.length(); } str += text; QStringList list; if (str.startsWith(CFG_CLI.Console.CommandPrefixChar.get())) list = completeCommand(str, curPos); else list = completeQuery(toBeReplaced, str, curPos); list.removeDuplicates(); #ifdef Q_OS_WIN if (list.size() == 1) list[0] += " "; #endif #ifdef Q_OS_UNIX // Unix readline treats first element in the list as a common value of all elements if (list.size() > 0) list.prepend(longestCommonPart(list)); #endif return list; } QStringList CliCompleter::completeCommand(const QString& str, int curPos) { QStringList results; QString text = str.mid(0, curPos); QStringList cmdWords = tokenizeArgs(text); if (cmdWords.size() == 0) return results; if (text[text.length()-1].isSpace()) cmdWords << ""; QString cmdStr = cmdWords[0].mid(1); if (cmdWords.size() > 1) { CliCommand* command = CliCommandFactory::getCommand(cmdStr); if (!command) return results; command->setup(cli); results = command->complete(cmdWords.mid(1)).filter(QRegExp("^"+cmdWords.last()+".*")); } else { QStringList cmdNames = CliCommandFactory::getCommandNames().filter(QRegExp("^"+cmdStr+".*")); cmdNames.sort(Qt::CaseInsensitive); foreach (const QString& cmdName, cmdNames) results << CFG_CLI.Console.CommandPrefixChar.get() + cmdName; } return results; } QStringList CliCompleter::completeQuery(const QString& toBeReplaced, const QString& str, int curPos) { QStringList list; if (!cli->getCurrentDb()) return list; bool keepOriginalStr = doKeepOriginalStr(str, curPos); CompletionHelper completer(str, curPos, cli->getCurrentDb()); QList expectedTokens = completer.getExpectedTokens().filtered(); foreach (const ExpectedTokenPtr& token, expectedTokens) list << token->value; list.removeAll(""); if (list.size() > 1) list.removeOne(";"); // we don't want it together with other proposals, cause it introduces problems when proposed by completer if (keepOriginalStr) { QMutableStringListIterator it(list); while (it.hasNext()) it.next().prepend(toBeReplaced); } return list; } bool CliCompleter::doKeepOriginalStr(const QString& str, int curPos) { Dialect dialect = Dialect::Sqlite3; if (cli->getCurrentDb()) dialect = cli->getCurrentDb()->getDialect(); TokenList tokens = Lexer::tokenize(str.mid(0, curPos), dialect); if (tokens.size() == 0) return false; return tokens.last()->isSeparating(); } char** CliCompleter::toCharArray(const QStringList& list) { if (list.size() == 0) return nullptr; char** array = (char**)malloc((list.size() + 1) * sizeof(char*)); array[list.size()] = nullptr; int i = 0; foreach (const QString& str, list) #if defined(Q_OS_WIN) array[i++] = _strdup(str.toLocal8Bit().data()); #elif defined(Q_OS_UNIX) array[i++] = strdup(str.toLocal8Bit().data()); #endif return array; }