Separate tab completion of the initial [ for lists
US: https://tree.taiga.io/project/jktjkt-netconf-cli/us/144
Change-Id: I03ffc2830c62bbf833fb1075e2dbead55b0ca838
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9e78846..d15c36a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -124,6 +124,7 @@
src/parser_context.cpp
src/interpreter.cpp
src/ast_handlers.cpp
+ src/completion.cpp
)
target_link_libraries(parser schemas utils ast_values)
diff --git a/src/ast_handlers.cpp b/src/ast_handlers.cpp
index 7738085..7def513 100644
--- a/src/ast_handlers.cpp
+++ b/src/ast_handlers.cpp
@@ -1,5 +1,5 @@
#include "ast_handlers.hpp"
-std::set<std::string> generateMissingKeyCompletionSet(std::set<std::string> keysNeeded, std::map<std::string, leaf_data_> currentKeys)
+std::set<Completion> generateMissingKeyCompletionSet(std::set<std::string> keysNeeded, std::map<std::string, leaf_data_> currentKeys)
{
std::set<std::string> missingKeys;
@@ -9,11 +9,9 @@
}
}
- std::set<std::string> res;
+ std::set<Completion> res;
- std::transform(missingKeys.begin(), missingKeys.end(),
- std::inserter(res, res.end()),
- [] (auto it) { return it + "="; });
+ std::transform(missingKeys.begin(), missingKeys.end(), std::inserter(res, res.end()), [](auto it) { return Completion{it + "="}; });
return res;
}
@@ -26,14 +24,14 @@
return leafDataToString(value);
}
-template<>
-decltype(ParserContext::m_suggestions) createSetSuggestions_class<yang::LeafDataTypes::Enum>::getSuggestions(const ParserContext& parserContext, const Schema& schema) const
+template <>
+std::set<std::string> createSetSuggestions_class<yang::LeafDataTypes::Enum>::getSuggestions(const ParserContext& parserContext, const Schema& schema) const
{
return schema.enumValues(parserContext.m_tmpListKeyLeafPath.m_location, parserContext.m_tmpListKeyLeafPath.m_node);
}
-template<>
-decltype(ParserContext::m_suggestions) createSetSuggestions_class<yang::LeafDataTypes::IdentityRef>::getSuggestions(const ParserContext& parserContext, const Schema& schema) const
+template <>
+std::set<std::string> createSetSuggestions_class<yang::LeafDataTypes::IdentityRef>::getSuggestions(const ParserContext& parserContext, const Schema& schema) const
{
return schema.validIdentities(parserContext.m_tmpListKeyLeafPath.m_location, parserContext.m_tmpListKeyLeafPath.m_node, Prefixes::WhenNeeded);
}
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index d5f972a..c046040 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -339,7 +339,6 @@
auto& parserContext = x3::get<parser_context_tag>(context);
parserContext.m_suggestions.clear();
parserContext.m_completionIterator = boost::none;
- parserContext.m_completionSuffix.clear();
}
};
@@ -539,7 +538,7 @@
parserContext.m_completionIterator = begin;
auto suggestions = schema.childNodes(parserContext.currentSchemaPath(), Recursion::NonRecursive);
- std::set<std::string> suffixesAdded;
+ std::set<Completion> suffixesAdded;
std::transform(suggestions.begin(), suggestions.end(),
std::inserter(suffixesAdded, suffixesAdded.end()),
[&parserContext, &schema] (auto it) {
@@ -553,21 +552,21 @@
}
if (schema.isLeaf(parserContext.currentSchemaPath(), node)) {
- return it + " ";
+ return Completion{it + " "};
}
if (schema.isContainer(parserContext.currentSchemaPath(), node)) {
- return it + "/";
+ return Completion{it + "/"};
}
if (schema.isList(parserContext.currentSchemaPath(), node)) {
- return it + "[";
+ return Completion{it, "[", Completion::WhenToAdd::IfFullMatch};
}
- return it;
+ return Completion{it};
});
parserContext.m_suggestions = suffixesAdded;
}
};
-std::set<std::string> generateMissingKeyCompletionSet(std::set<std::string> keysNeeded, std::map<std::string, leaf_data_> currentSet);
+std::set<Completion> generateMissingKeyCompletionSet(std::set<std::string> keysNeeded, std::map<std::string, leaf_data_> currentSet);
struct createKeySuggestions_class {
template <typename T, typename Iterator, typename Context>
@@ -609,10 +608,10 @@
};
std::copy_if(listInstances.begin(), listInstances.end(), std::inserter(filteredInstances, filteredInstances.end()), partialFitsComplete);
- std::set<std::string> validValues;
+ std::set<Completion> validValues;
- std::transform(filteredInstances.begin(), filteredInstances.end(), std::inserter(validValues, validValues.end()), [&parserContext] (const auto& instance) {
- return leafDataToCompletion(instance.at(parserContext.m_tmpListKeyLeafPath.m_node.second));
+ std::transform(filteredInstances.begin(), filteredInstances.end(), std::inserter(validValues, validValues.end()), [&parserContext](const auto& instance) {
+ return Completion{leafDataToCompletion(instance.at(parserContext.m_tmpListKeyLeafPath.m_node.second))};
});
parserContext.m_suggestions = validValues;
@@ -629,16 +628,16 @@
parserContext.m_completionIterator = begin;
const auto& keysNeeded = schema.listKeys(parserContext.currentSchemaPath(), {parserContext.m_curModule, parserContext.m_tmpListName});
if (generateMissingKeyCompletionSet(keysNeeded, parserContext.m_tmpListKeys).empty()) {
- parserContext.m_suggestions = {"]/"};
+ parserContext.m_suggestions = {Completion{"]/"}};
} else {
- parserContext.m_suggestions = {"]["};
+ parserContext.m_suggestions = {Completion{"]["}};
}
}
};
struct commandNamesVisitor {
template <typename T>
- auto operator()(boost::type<T>)
+ std::string operator()(boost::type<T>)
{
return T::name;
}
@@ -653,7 +652,7 @@
parserContext.m_suggestions.clear();
boost::mpl::for_each<CommandTypes, boost::type<boost::mpl::_>>([&parserContext](auto cmd) {
- parserContext.m_suggestions.emplace(commandNamesVisitor()(cmd));
+ parserContext.m_suggestions.insert({commandNamesVisitor()(cmd)});
});
}
};
@@ -671,7 +670,7 @@
template<yang::LeafDataTypes TYPE>
struct createSetSuggestions_class {
- decltype(ParserContext::m_suggestions) getSuggestions(const ParserContext& ctx, const Schema& schema) const;
+ std::set<std::string> getSuggestions(const ParserContext& ctx, const Schema& schema) const;
template <typename T, typename Iterator, typename Context>
void on_success(Iterator const& begin, Iterator const&, T&, Context const& context)
@@ -683,7 +682,10 @@
// overwrite some other completions.
if (schema.leafType(parserContext.m_tmpListKeyLeafPath.m_location, parserContext.m_tmpListKeyLeafPath.m_node) == TYPE) {
parserContext.m_completionIterator = begin;
- parserContext.m_suggestions = getSuggestions(parserContext, schema);
+ auto suggestions = getSuggestions(parserContext, schema);
+ std::set<Completion> res;
+ std::transform(suggestions.begin(), suggestions.end(), std::inserter(res, res.end()), [](auto it) { return Completion{it}; });
+ parserContext.m_suggestions = res;
}
}
};
diff --git a/src/completion.cpp b/src/completion.cpp
new file mode 100644
index 0000000..44286e9
--- /dev/null
+++ b/src/completion.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubernat@cesnet.cz>
+ *
+*/
+#include <boost/algorithm/string/predicate.hpp>
+#include <tuple>
+#include "completion.hpp"
+
+bool Completion::operator<(const Completion& b) const
+{
+ return std::tie(this->m_value, this->m_suffix, this->m_whenToAdd) < std::tie(b.m_value, b.m_suffix, b.m_whenToAdd);
+}
+
+bool Completion::operator==(const Completion& b) const
+{
+ return std::tie(this->m_value, this->m_suffix, this->m_whenToAdd) == std::tie(b.m_value, b.m_suffix, b.m_whenToAdd);
+}
+
+std::set<Completion> filterByPrefix(const std::set<Completion>& set, const std::string_view prefix)
+{
+ std::set<Completion> filtered;
+ std::copy_if(set.begin(), set.end(), std::inserter(filtered, filtered.end()), [prefix](Completion it) { return boost::starts_with(it.m_value, prefix); });
+ return filtered;
+}
diff --git a/src/completion.hpp b/src/completion.hpp
new file mode 100644
index 0000000..a95dafc
--- /dev/null
+++ b/src/completion.hpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubernat@cesnet.cz>
+ *
+*/
+#include <set>
+#include <string>
+struct Completion {
+ enum class WhenToAdd {
+ Always,
+ IfFullMatch
+ };
+ bool operator<(const Completion& b) const;
+ bool operator==(const Completion& b) const;
+ std::string m_value;
+
+ /** A completion can have a suffix specified. This suffix is appended to the completion, if it's the only valid completion present in
+ * the ParserContext. For example, let's say there are two valid schema paths: "user/name", "user/nationality" and "user/city" and the
+ * user tries to complete this path: "user/n". The parser determines these completions: "name", "nationality", "city". The parser
+ * filters out "city", because it doesn't start with an "n", so valid completions are "name" and "nationality". Their common prefix is
+ * "na", so the input becomes "user/na". Next, the user changes the output to "user/natio", the completions will again be "name",
+ * "nationality", "city", but now, after filtering, only one single completion remains - "nationality". This is where m_suffix gets in
+ * play: since there is only one completion remaining, m_suffix can get added depending on the value of m_whenToAdd. It it's set to
+ * Always, the suffix gets appended always. That means, the actual completion would be "nationality" plus whatever m_suffix is set to.
+ * Otherwise (if m_whenToAdd is set to IfFullMatch), the suffix will only get added after the user input fully matches the completion.
+ * For example, if the user input is "user/natio", the completion becomes just "nationality", but if the user input is "user/nationality",
+ * the completion becomes "nationality" plus the suffix.
+ */
+ std::string m_suffix = "";
+ WhenToAdd m_whenToAdd = WhenToAdd::Always;
+};
+
+/** Returns a subset of the original set with only the strings starting with prefix */
+std::set<Completion> filterByPrefix(const std::set<Completion>& set, const std::string_view prefix);
diff --git a/src/parser.cpp b/src/parser.cpp
index ff262fc..85793a0 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -67,11 +67,18 @@
int completionContext = line.end() - completionIterator;
- auto set = filterByPrefix(ctx.m_suggestions, std::string(completionIterator, line.end()));
- if (set.size() == 1) {
- return {{(*set.begin()) + ctx.m_completionSuffix}, completionContext};
+ auto filtered = filterByPrefix(ctx.m_suggestions, std::string(completionIterator, line.end()));
+ if (filtered.size() == 1) {
+ auto suffix = filtered.begin()->m_whenToAdd == Completion::WhenToAdd::IfFullMatch
+ && filtered.begin()->m_value == std::string{completionIterator, line.end()}
+ ? filtered.begin()->m_suffix
+ : "";
+ return {{filtered.begin()->m_value + suffix}, completionContext};
}
- return {set, completionContext};
+
+ std::set<std::string> res;
+ std::transform(filtered.begin(), filtered.end(), std::inserter(res, res.end()), [](auto it) { return it.m_value; });
+ return {res, completionContext};
}
void Parser::changeNode(const dataPath_& name)
diff --git a/src/parser_context.hpp b/src/parser_context.hpp
index b9798fc..3ad0179 100644
--- a/src/parser_context.hpp
+++ b/src/parser_context.hpp
@@ -7,6 +7,7 @@
*/
#pragma once
+#include "completion.hpp"
#include "data_query.hpp"
#include "schema.hpp"
struct ParserContext {
@@ -33,13 +34,10 @@
std::map<std::string, leaf_data_> m_tmpListKeys;
bool m_errorHandled = false;
bool m_completing = false;
- std::set<std::string> m_suggestions;
+
+ std::set<Completion> m_suggestions;
// Iterator pointing to where suggestions were created
boost::optional<std::string::const_iterator> m_completionIterator;
- // If the parser determines that suggestions are unambiguous (after
- // filtering by prefix), this suffix gets added to the completion (for
- // example a left bracket after a list)
- std::string m_completionSuffix;
private:
boost::variant<dataPath_, schemaPath_> m_curPath;
diff --git a/src/utils.cpp b/src/utils.cpp
index 978e0a9..fcbe0d0 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -5,8 +5,8 @@
* Written by Václav Kubernát <kubervac@fit.cvut.cz>
*
*/
-#include <boost/algorithm/string/predicate.hpp>
#include <sstream>
+#include "completion.hpp"
#include "utils.hpp"
std::string joinPaths(const std::string& prefix, const std::string& suffix)
@@ -104,15 +104,6 @@
return fullNodeName(dataPathToSchemaPath(location), pair);
}
-std::set<std::string> filterByPrefix(const std::set<std::string>& set, const std::string_view prefix)
-{
- std::set<std::string> filtered;
- std::copy_if(set.begin(), set.end(),
- std::inserter(filtered, filtered.end()),
- [prefix] (auto it) { return boost::starts_with(it, prefix); });
- return filtered;
-}
-
struct leafDataToStringVisitor : boost::static_visitor<std::string> {
std::string operator()(const enum_& data) const
{
diff --git a/src/utils.hpp b/src/utils.hpp
index bd959e3..f0f7ae5 100644
--- a/src/utils.hpp
+++ b/src/utils.hpp
@@ -20,6 +20,4 @@
std::string leafDataTypeToString(yang::LeafDataTypes type);
std::string fullNodeName(const schemaPath_& location, const ModuleNodePair& pair);
std::string fullNodeName(const dataPath_& location, const ModuleNodePair& pair);
-/** Returns a subset of the original set with only the strings starting with prefix */
-std::set<std::string> filterByPrefix(const std::set<std::string>& set, const std::string_view prefix);
std::string leafDataToString(const leaf_data_ value);
diff --git a/tests/path_completion.cpp b/tests/path_completion.cpp
index 1550326..0715639 100644
--- a/tests/path_completion.cpp
+++ b/tests/path_completion.cpp
@@ -66,14 +66,14 @@
SECTION("ls ")
{
input = "ls ";
- expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list[", "example:ovoce[", "example:ovocezelenina[", "example:twoKeyList[", "second:amelie/"};
+ expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList", "second:amelie/"};
expectedContextLength = 0;
}
SECTION("ls e")
{
input = "ls e";
- expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list[", "example:ovoce[", "example:ovocezelenina[", "example:twoKeyList["};
+ expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList"};
expectedContextLength = 1;
}
@@ -101,14 +101,14 @@
SECTION("ls /")
{
input = "ls /";
- expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list[", "example:ovoce[", "example:ovocezelenina[", "example:twoKeyList[", "second:amelie/"};
+ expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList", "second:amelie/"};
expectedContextLength = 0;
}
SECTION("ls /e")
{
input = "ls /e";
- expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list[", "example:ovoce[", "example:ovocezelenina[", "example:twoKeyList["};
+ expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList"};
expectedContextLength = 1;
}
@@ -160,7 +160,7 @@
SECTION("cd example:lis")
{
input = "cd example:lis";
- expectedCompletions = {"example:list["};
+ expectedCompletions = {"example:list"};
expectedContextLength = 11;
}
@@ -272,9 +272,23 @@
SECTION("cd example:ovoce")
{
input = "cd example:ovoce";
- expectedCompletions = {"example:ovoce[", "example:ovocezelenina["};
+ expectedCompletions = {"example:ovoce", "example:ovocezelenina"};
expectedContextLength = 13;
}
+
+ SECTION("cd example:ovoceze")
+ {
+ input = "cd example:ovoceze";
+ expectedCompletions = {"example:ovocezelenina"};
+ expectedContextLength = 15;
+ }
+
+ SECTION("cd example:ovocezelenina")
+ {
+ input = "cd example:ovocezelenina";
+ expectedCompletions = {"example:ovocezelenina["};
+ expectedContextLength = 21;
+ }
}
SECTION("clear completions when no longer inputting path")
diff --git a/tests/utils.cpp b/tests/utils.cpp
index c862980..91c61cb 100644
--- a/tests/utils.cpp
+++ b/tests/utils.cpp
@@ -7,20 +7,21 @@
*/
#include "trompeloeil_doctest.hpp"
+#include "completion.hpp"
#include "utils.hpp"
TEST_CASE("utils")
{
SECTION("filterByPrefix")
{
- std::set<std::string> set{"ahoj", "coze", "copak", "aha", "polivka"};
+ std::set<Completion> set{{"ahoj"}, {"coze"}, {"copak"}, {"aha"}, {"polivka"}};
- REQUIRE((filterByPrefix(set, "a") == std::set<std::string>{"ahoj", "aha"}));
- REQUIRE((filterByPrefix(set, "ah") == std::set<std::string>{"ahoj", "aha"}));
- REQUIRE((filterByPrefix(set, "aho") == std::set<std::string>{"ahoj"}));
- REQUIRE((filterByPrefix(set, "polivka") == std::set<std::string>{"polivka"}));
- REQUIRE((filterByPrefix(set, "polivkax") == std::set<std::string>{}));
- REQUIRE((filterByPrefix(set, "co") == std::set<std::string>{"copak", "coze"}));
+ REQUIRE((filterByPrefix(set, "a") == std::set<Completion>{{"ahoj"}, {"aha"}}));
+ REQUIRE((filterByPrefix(set, "ah") == std::set<Completion>{{"ahoj"}, {"aha"}}));
+ REQUIRE((filterByPrefix(set, "aho") == std::set<Completion>{{"ahoj"}}));
+ REQUIRE((filterByPrefix(set, "polivka") == std::set<Completion>{{"polivka"}}));
+ REQUIRE((filterByPrefix(set, "polivkax") == std::set<Completion>{}));
+ REQUIRE((filterByPrefix(set, "co") == std::set<Completion>{{"copak"}, {"coze"}}));
}
SECTION("joinPaths") {