From 1af88c964e0de0905317703135d8bbeed37177f2 Mon Sep 17 00:00:00 2001 From: Martchus Date: Fri, 28 Jul 2017 17:32:07 +0200 Subject: [PATCH] bash completion: Show values for implicit args So eg. `tageditor get [tab][tab]` also suggests specifying field names directly instead of only via --fields. --- CMakeLists.txt | 2 +- application/argumentparser.cpp | 50 +++++++++++++++++++++++++--------- tests/argumentparsertests.cpp | 11 +++++++- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d445e48..5717511 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,7 +127,7 @@ set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}") set(META_APP_DESCRIPTION "Common C++ classes and routines used by my applications such as argument parser, IO and conversion utilities") set(META_VERSION_MAJOR 4) set(META_VERSION_MINOR 9) -set(META_VERSION_PATCH 1) +set(META_VERSION_PATCH 2) # find required 3rd party libraries include(3rdParty) diff --git a/application/argumentparser.cpp b/application/argumentparser.cpp index 40aa722..fa72023 100644 --- a/application/argumentparser.cpp +++ b/application/argumentparser.cpp @@ -858,23 +858,47 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi bool nextArgumentOrValue; if (lastDetectedArg && lastDetectedArg->isPresent()) { if ((nextArgumentOrValue = (currentWordIndex > lastDetectedArgIndex))) { - // parameter values of the last arg are possible completions - auto currentValueCount = lastDetectedArg->values(lastDetectedArg->occurrences() - 1).size(); - if (currentValueCount) { - currentValueCount -= (currentWordIndex - lastDetectedArgIndex); - } - if (lastDetectedArg->requiredValueCount() == static_cast(-1) || (currentValueCount < lastDetectedArg->requiredValueCount())) { - if (lastDetectedArg->valueCompletionBehaviour() & ValueCompletionBehavior::PreDefinedValues) { - relevantPreDefinedValues.push_back(lastDetectedArg); + // 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); } - if (!(lastDetectedArg->valueCompletionBehaviour() & ValueCompletionBehavior::FileSystemIfNoPreDefinedValues) - || !lastDetectedArg->preDefinedCompletionValues()) { - completeFiles = completeFiles || lastDetectedArg->valueCompletionBehaviour() & ValueCompletionBehavior::Files; - completeDirs = completeDirs || lastDetectedArg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories; + if (!(arg->valueCompletionBehaviour() & ValueCompletionBehavior::FileSystemIfNoPreDefinedValues) + || !arg->preDefinedCompletionValues()) { + completeFiles = completeFiles || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Files; + completeDirs = completeDirs || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories; + } + }; + + // detect number of specified values + auto currentValueCount = lastDetectedArg->values(lastDetectedArg->occurrences() - 1).size(); + // ignore values which are specified after the current word + if (currentValueCount) { + const auto currentWordIndexRelativeToLastDetectedArg = currentWordIndex - lastDetectedArgIndex; + if (currentValueCount > currentWordIndexRelativeToLastDetectedArg) { + currentValueCount -= currentWordIndexRelativeToLastDetectedArg; + } else { + currentValueCount = 0; } } - if (lastDetectedArg->requiredValueCount() == static_cast(-1) + // 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 (child->isImplicit() && child->requiredValueCount()) { + addValueCompletionsForArg(child); + break; + } + } + } + + // add value completions for last argument if there are further values required + if (lastDetectedArg->requiredValueCount() == Argument::varValueCount || (currentValueCount < lastDetectedArg->requiredValueCount())) { + addValueCompletionsForArg(lastDetectedArg); + } + + if (lastDetectedArg->requiredValueCount() == Argument::varValueCount || lastDetectedArg->values(lastDetectedArg->occurrences() - 1).size() >= lastDetectedArg->requiredValueCount()) { // sub arguments of the last arg are possible completions for (const Argument *subArg : lastDetectedArg->subArguments()) { diff --git a/tests/argumentparsertests.cpp b/tests/argumentparsertests.cpp index 7702f0e..7beb63e 100644 --- a/tests/argumentparsertests.cpp +++ b/tests/argumentparsertests.cpp @@ -429,7 +429,7 @@ void ArgumentParserTests::testBashCompletion() Argument valuesArg("values", '\0', "specifies the fields"); valuesArg.setRequiredValueCount(-1); valuesArg.setPreDefinedCompletionValues("title album artist trackpos"); - valuesArg.setImplicit(true); + valuesArg.setImplicit(false); valuesArg.setValueCompletionBehavior(ValueCompletionBehavior::PreDefinedValues | ValueCompletionBehavior::AppendEquationSign); Argument getArg("get", 'g', "gets tag values"); getArg.setSubArguments({ &fieldsArg, &filesArg }); @@ -519,6 +519,15 @@ void ArgumentParserTests::testBashCompletion() cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL("COMPREPLY=('album=' 'artist=' ); compopt -o nospace\n"s, buffer.str()); + // pre-defined values for implicit argument + buffer.str(string()); + cout.rdbuf(buffer.rdbuf()); + parser.resetArgs(); + reader.reset(argv3, argv3 + 1).read(); + parser.printBashCompletion(1, argv3, 2, reader); + cout.rdbuf(regularCoutBuffer); + CPPUNIT_ASSERT_EQUAL("COMPREPLY=('title' 'album' 'artist' 'trackpos' '--fields' '--files' )\n"s, buffer.str()); + // file names string iniFilePath = TestUtilities::testFilePath("test.ini"); iniFilePath.resize(iniFilePath.size() - 4);