Refactor ArgumentParser::printBashCompletion()
This commit is contained in:
parent
3c04514649
commit
872ee49979
|
@ -1015,57 +1015,80 @@ void insertSiblings(const ArgumentVector &siblings, list<const Argument *> &targ
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Prints the bash completion for the specified arguments and the specified \a lastPath.
|
||||
* \remarks Arguments must have been parsed before with readSpecifiedArgs(). When calling this method, completionMode must
|
||||
* be set to true.
|
||||
*/
|
||||
void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader)
|
||||
{
|
||||
// variables to store relevant completions (arguments, pre-defined values, files/dirs)
|
||||
list<const Argument *> relevantArgs, relevantPreDefinedValues;
|
||||
bool completeFiles = false, completeDirs = false, noWhitespace = false;
|
||||
struct ArgumentCompletionInfo {
|
||||
ArgumentCompletionInfo(const ArgumentReader &reader);
|
||||
|
||||
// get the last argument the argument parser was able to detect successfully
|
||||
const Argument *const lastDetectedArg = reader.lastArg;
|
||||
size_t lastDetectedArgIndex;
|
||||
const Argument *const lastDetectedArg;
|
||||
size_t lastDetectedArgIndex = 0;
|
||||
vector<Argument *> lastDetectedArgPath;
|
||||
if (lastDetectedArg) {
|
||||
lastDetectedArgIndex = reader.lastArgDenotation - argv;
|
||||
lastDetectedArgPath = lastDetectedArg->path(lastDetectedArg->occurrences() - 1);
|
||||
list<const Argument *> relevantArgs;
|
||||
list<const Argument *> relevantPreDefinedValues;
|
||||
const char *const *lastSpecifiedArg = nullptr;
|
||||
unsigned int lastSpecifiedArgIndex = 0;
|
||||
bool nextArgumentOrValue = false;
|
||||
bool completeFiles = false, completeDirs = false;
|
||||
};
|
||||
|
||||
ArgumentCompletionInfo::ArgumentCompletionInfo(const ArgumentReader &reader)
|
||||
: lastDetectedArg(reader.lastArg)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Determines arguments relevant for Bash completion or suggestions in case of typo.
|
||||
*/
|
||||
ArgumentCompletionInfo ArgumentParser::determineCompletionInfo(
|
||||
int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader)
|
||||
{
|
||||
ArgumentCompletionInfo completion(reader);
|
||||
|
||||
// determine last detected arg
|
||||
if (completion.lastDetectedArg) {
|
||||
completion.lastDetectedArgIndex = reader.lastArgDenotation - argv;
|
||||
completion.lastDetectedArgPath = completion.lastDetectedArg->path(completion.lastDetectedArg->occurrences() - 1);
|
||||
}
|
||||
|
||||
// determine last arg, omitting trailing empty args
|
||||
const char *const *lastSpecifiedArg;
|
||||
unsigned int lastSpecifiedArgIndex;
|
||||
if (argc) {
|
||||
lastSpecifiedArgIndex = static_cast<unsigned int>(argc) - 1;
|
||||
lastSpecifiedArg = argv + lastSpecifiedArgIndex;
|
||||
for (; lastSpecifiedArg >= argv && **lastSpecifiedArg == '\0'; --lastSpecifiedArg, --lastSpecifiedArgIndex)
|
||||
completion.lastSpecifiedArgIndex = static_cast<unsigned int>(argc) - 1;
|
||||
completion.lastSpecifiedArg = argv + completion.lastSpecifiedArgIndex;
|
||||
for (; completion.lastSpecifiedArg >= argv && **completion.lastSpecifiedArg == '\0';
|
||||
--completion.lastSpecifiedArg, --completion.lastSpecifiedArgIndex)
|
||||
;
|
||||
}
|
||||
|
||||
// determine arguments relevant for completion
|
||||
bool nextArgumentOrValue;
|
||||
if (lastDetectedArg && lastDetectedArg->isPresent()) {
|
||||
if ((nextArgumentOrValue = (currentWordIndex > lastDetectedArgIndex))) {
|
||||
// define function to add parameter values of argument as possible completions
|
||||
const auto addValueCompletionsForArg = [&relevantPreDefinedValues, &completeFiles, &completeDirs](const Argument *arg) {
|
||||
if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::PreDefinedValues) {
|
||||
relevantPreDefinedValues.push_back(arg);
|
||||
// just return main arguments if no args detected
|
||||
if (!completion.lastDetectedArg || !completion.lastDetectedArg->isPresent()) {
|
||||
completion.nextArgumentOrValue = true;
|
||||
insertSiblings(m_mainArgs, completion.relevantArgs);
|
||||
completion.relevantArgs.sort(compareArgs);
|
||||
return completion;
|
||||
}
|
||||
if (!(arg->valueCompletionBehaviour() & ValueCompletionBehavior::FileSystemIfNoPreDefinedValues)
|
||||
|| !arg->preDefinedCompletionValues()) {
|
||||
completeFiles = completeFiles || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Files;
|
||||
completeDirs = completeDirs || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories;
|
||||
|
||||
completion.nextArgumentOrValue = currentWordIndex > completion.lastDetectedArgIndex;
|
||||
if (!completion.nextArgumentOrValue) {
|
||||
// since the argument could be detected (hopefully unambiguously?) just return it for "final completion"
|
||||
completion.relevantArgs.push_back(completion.lastDetectedArg);
|
||||
completion.relevantArgs.sort(compareArgs);
|
||||
return completion;
|
||||
}
|
||||
|
||||
// define function to add parameter values of argument as possible completions
|
||||
const auto addValueCompletionsForArg = [&completion](const Argument *arg) {
|
||||
if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::PreDefinedValues) {
|
||||
completion.relevantPreDefinedValues.push_back(arg);
|
||||
}
|
||||
if (!(arg->valueCompletionBehaviour() & ValueCompletionBehavior::FileSystemIfNoPreDefinedValues) || !arg->preDefinedCompletionValues()) {
|
||||
completion.completeFiles = completion.completeFiles || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Files;
|
||||
completion.completeDirs = completion.completeDirs || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories;
|
||||
}
|
||||
};
|
||||
|
||||
// detect number of specified values
|
||||
auto currentValueCount = lastDetectedArg->values(lastDetectedArg->occurrences() - 1).size();
|
||||
auto currentValueCount = completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size();
|
||||
// ignore values which are specified after the current word
|
||||
if (currentValueCount) {
|
||||
const auto currentWordIndexRelativeToLastDetectedArg = currentWordIndex - lastDetectedArgIndex;
|
||||
const auto currentWordIndexRelativeToLastDetectedArg = currentWordIndex - completion.lastDetectedArgIndex;
|
||||
if (currentValueCount > currentWordIndexRelativeToLastDetectedArg) {
|
||||
currentValueCount -= currentWordIndexRelativeToLastDetectedArg;
|
||||
} else {
|
||||
|
@ -1075,8 +1098,8 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
|
|||
|
||||
// add value completions for implicit child if there are no value specified and there are no values required by the
|
||||
// last detected argument itself
|
||||
if (!currentValueCount && !lastDetectedArg->requiredValueCount()) {
|
||||
for (const Argument *child : lastDetectedArg->subArguments()) {
|
||||
if (!currentValueCount && !completion.lastDetectedArg->requiredValueCount()) {
|
||||
for (const Argument *child : completion.lastDetectedArg->subArguments()) {
|
||||
if (child->isImplicit() && child->requiredValueCount()) {
|
||||
addValueCompletionsForArg(child);
|
||||
break;
|
||||
|
@ -1085,44 +1108,50 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
|
|||
}
|
||||
|
||||
// add value completions for last argument if there are further values required
|
||||
if (lastDetectedArg->requiredValueCount() == Argument::varValueCount || (currentValueCount < lastDetectedArg->requiredValueCount())) {
|
||||
addValueCompletionsForArg(lastDetectedArg);
|
||||
if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
|
||||
|| (currentValueCount < completion.lastDetectedArg->requiredValueCount())) {
|
||||
addValueCompletionsForArg(completion.lastDetectedArg);
|
||||
}
|
||||
|
||||
if (lastDetectedArg->requiredValueCount() == Argument::varValueCount
|
||||
|| lastDetectedArg->values(lastDetectedArg->occurrences() - 1).size() >= lastDetectedArg->requiredValueCount()) {
|
||||
if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
|
||||
|| completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size()
|
||||
>= completion.lastDetectedArg->requiredValueCount()) {
|
||||
// sub arguments of the last arg are possible completions
|
||||
for (const Argument *subArg : lastDetectedArg->subArguments()) {
|
||||
for (const Argument *subArg : completion.lastDetectedArg->subArguments()) {
|
||||
if (subArg->occurrences() < subArg->maxOccurrences()) {
|
||||
relevantArgs.push_back(subArg);
|
||||
completion.relevantArgs.push_back(subArg);
|
||||
}
|
||||
}
|
||||
|
||||
// siblings of parents are possible completions as well
|
||||
for (auto parentArgument = lastDetectedArgPath.crbegin(), end = lastDetectedArgPath.crend();; ++parentArgument) {
|
||||
insertSiblings(parentArgument != end ? (*parentArgument)->subArguments() : m_mainArgs, relevantArgs);
|
||||
for (auto parentArgument = completion.lastDetectedArgPath.crbegin(), end = completion.lastDetectedArgPath.crend();; ++parentArgument) {
|
||||
insertSiblings(parentArgument != end ? (*parentArgument)->subArguments() : m_mainArgs, completion.relevantArgs);
|
||||
if (parentArgument == end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// since the argument could be detected (hopefully unambiguously?) just return it for "final completion"
|
||||
relevantArgs.push_back(lastDetectedArg);
|
||||
|
||||
completion.relevantArgs.sort(compareArgs);
|
||||
return completion;
|
||||
}
|
||||
|
||||
} else {
|
||||
// no arguments detected -> just use main arguments for completion
|
||||
nextArgumentOrValue = true;
|
||||
insertSiblings(m_mainArgs, relevantArgs);
|
||||
}
|
||||
/*!
|
||||
* \brief Prints the bash completion for the specified arguments and the specified \a lastPath.
|
||||
* \remarks Arguments must have been parsed before with readSpecifiedArgs(). When calling this method, completionMode must
|
||||
* be set to true.
|
||||
*/
|
||||
void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader)
|
||||
{
|
||||
// determine completion info
|
||||
const auto completionInfo(determineCompletionInfo(argc, argv, currentWordIndex, reader));
|
||||
|
||||
// read the "opening" (started but not finished argument denotation)
|
||||
const char *opening = nullptr;
|
||||
string compoundOpening;
|
||||
size_t openingLen, compoundOpeningStartLen = 0;
|
||||
unsigned char openingDenotationType = Value;
|
||||
if (argc && nextArgumentOrValue) {
|
||||
if (argc && completionInfo.nextArgumentOrValue) {
|
||||
if (currentWordIndex < static_cast<unsigned int>(argc)) {
|
||||
opening = argv[currentWordIndex];
|
||||
// For some reason completions for eg. "set --values disk=1 tag=a" are splitted so the
|
||||
|
@ -1130,7 +1159,7 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
|
|||
// This is not how values are treated by the argument parser. Hence the opening
|
||||
// must be joined again. In this case only the part after the equation sign needs to be
|
||||
// provided for completion so compoundOpeningStartLen is set to number of characters to skip.
|
||||
size_t minCurrentWordIndex = (lastDetectedArg ? lastDetectedArgIndex : 0);
|
||||
size_t minCurrentWordIndex = (completionInfo.lastDetectedArg ? completionInfo.lastDetectedArgIndex : 0);
|
||||
if (currentWordIndex > minCurrentWordIndex && !strcmp(opening, "=")) {
|
||||
compoundOpening.reserve(compoundOpeningStartLen = strlen(argv[--currentWordIndex]) + 1);
|
||||
compoundOpening = argv[currentWordIndex];
|
||||
|
@ -1145,24 +1174,23 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
|
|||
opening = compoundOpening.data();
|
||||
}
|
||||
} else {
|
||||
opening = *lastSpecifiedArg;
|
||||
opening = *completionInfo.lastSpecifiedArg;
|
||||
}
|
||||
*opening == '-' && (++opening, ++openingDenotationType) && *opening == '-' && (++opening, ++openingDenotationType);
|
||||
openingLen = strlen(opening);
|
||||
}
|
||||
|
||||
relevantArgs.sort(compareArgs);
|
||||
|
||||
// print "COMPREPLY" bash array
|
||||
cout << "COMPREPLY=(";
|
||||
// -> completions for parameter values
|
||||
for (const Argument *arg : relevantPreDefinedValues) {
|
||||
bool noWhitespace = false;
|
||||
for (const Argument *arg : completionInfo.relevantPreDefinedValues) {
|
||||
if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::InvokeCallback && arg->m_callbackFunction) {
|
||||
arg->m_callbackFunction(arg->isPresent() ? arg->m_occurrences.front() : ArgumentOccurrence(Argument::varValueCount));
|
||||
}
|
||||
if (arg->preDefinedCompletionValues()) {
|
||||
bool appendEquationSign = arg->valueCompletionBehaviour() & ValueCompletionBehavior::AppendEquationSign;
|
||||
if (argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
|
||||
if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
|
||||
if (openingDenotationType == Value) {
|
||||
bool wordStart = true, ok = false, equationSignAlreadyPresent = false;
|
||||
size_t wordIndex = 0;
|
||||
|
@ -1247,8 +1275,8 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
|
|||
}
|
||||
}
|
||||
// -> completions for further arguments
|
||||
for (const Argument *arg : relevantArgs) {
|
||||
if (argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
|
||||
for (const Argument *arg : completionInfo.relevantArgs) {
|
||||
if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
|
||||
switch (openingDenotationType) {
|
||||
case Value:
|
||||
if (!arg->denotesOperation() || strncmp(arg->name(), opening, openingLen)) {
|
||||
|
@ -1264,10 +1292,10 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
|
|||
}
|
||||
}
|
||||
|
||||
if (opening && openingDenotationType == Abbreviation && !nextArgumentOrValue) {
|
||||
if (opening && openingDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
|
||||
// TODO: add test for this case
|
||||
cout << '\'' << '-' << opening << arg->abbreviation() << '\'' << ' ';
|
||||
} else if (lastDetectedArg && reader.argDenotationType == Abbreviation && !nextArgumentOrValue) {
|
||||
} else if (completionInfo.lastDetectedArg && reader.argDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
|
||||
if (reader.argv == reader.end) {
|
||||
cout << '\'' << *(reader.argv - 1) << '\'' << ' ';
|
||||
}
|
||||
|
@ -1281,7 +1309,7 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
|
|||
// -> if there's already an "opening", determine the dir part and the file part
|
||||
string actualDir, actualFile;
|
||||
bool haveFileOrDirCompletions = false;
|
||||
if (argc && currentWordIndex == lastSpecifiedArgIndex && opening) {
|
||||
if (argc && currentWordIndex == completionInfo.lastSpecifiedArgIndex && opening) {
|
||||
// the "opening" might contain escaped characters which need to be unescaped first (let's hope this covers all possible escapings)
|
||||
string unescapedOpening(opening);
|
||||
findAndReplace<string>(unescapedOpening, "\\ ", " ");
|
||||
|
@ -1320,15 +1348,15 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
|
|||
|
||||
// -> completion for files and dirs
|
||||
DirectoryEntryType entryTypes = DirectoryEntryType::None;
|
||||
if (completeFiles) {
|
||||
if (completionInfo.completeFiles) {
|
||||
entryTypes |= DirectoryEntryType::File;
|
||||
}
|
||||
if (completeDirs) {
|
||||
if (completionInfo.completeDirs) {
|
||||
entryTypes |= DirectoryEntryType::Directory;
|
||||
}
|
||||
if (entryTypes != DirectoryEntryType::None) {
|
||||
const string replace("'"), with("'\"'\"'");
|
||||
if (argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
|
||||
if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
|
||||
list<string> entries = directoryEntries(actualDir.c_str(), entryTypes);
|
||||
findAndReplace(actualDir, replace, with);
|
||||
for (string &dirEntry : entries) {
|
||||
|
|
|
@ -365,6 +365,8 @@ template <typename... TargetType> std::vector<std::tuple<TargetType...>> Argumen
|
|||
return res;
|
||||
}
|
||||
|
||||
struct ArgumentCompletionInfo;
|
||||
|
||||
class CPP_UTILITIES_EXPORT ArgumentParser {
|
||||
friend ArgumentParserTests;
|
||||
friend ArgumentReader;
|
||||
|
@ -402,6 +404,7 @@ public:
|
|||
private:
|
||||
// declare internal operations
|
||||
IF_DEBUG_BUILD(void verifyArgs(const ArgumentVector &args);)
|
||||
ArgumentCompletionInfo determineCompletionInfo(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader);
|
||||
void printBashCompletion(int argc, const char *const *argv, unsigned int cursorPos, const ArgumentReader &reader);
|
||||
void checkConstraints(const ArgumentVector &args);
|
||||
static void invokeCallbacks(const ArgumentVector &args);
|
||||
|
|
Loading…
Reference in New Issue