Merge "Spell "XPath" in its usual spelling"
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index fede04c..dd80889 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -619,11 +619,21 @@
         if (generateMissingKeyCompletionSet(keysNeeded, parserContext.m_tmpListKeys).empty()) {
             parserContext.m_suggestions = {"]/"};
         } else {
-            parserContext.m_suggestions = {"]"};
+            parserContext.m_suggestions = {"]["};
         }
     }
 };
 
+struct suggestKeysStart_class {
+    template <typename T, typename Iterator, typename Context>
+    void on_success(Iterator const&, Iterator const&, T&, Context const& context)
+    {
+        auto& parserContext = x3::get<parser_context_tag>(context);
+
+        parserContext.m_completionSuffix = "[";
+    }
+};
+
 struct commandNamesVisitor {
     template <typename T>
     auto operator()(boost::type<T>)
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 9ba81c6..583a960 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -63,6 +63,7 @@
 x3::rule<initializePath_class, x3::unused_type> const initializePath = "initializePath";
 x3::rule<createPathSuggestions_class, x3::unused_type> const createPathSuggestions = "createPathSuggestions";
 x3::rule<createKeySuggestions_class, x3::unused_type> const createKeySuggestions = "createKeySuggestions";
+x3::rule<suggestKeysStart_class, x3::unused_type> const suggestKeysStart = "suggestKeysStart";
 x3::rule<suggestKeysEnd_class, x3::unused_type> const suggestKeysEnd = "suggestKeysEnd";
 x3::rule<createCommandSuggestions_class, x3::unused_type> const createCommandSuggestions = "createCommandSuggestions";
 x3::rule<completing_class, x3::unused_type> const completing = "completing";
@@ -89,6 +90,9 @@
 auto const createKeySuggestions_def =
     x3::eps;
 
+auto const suggestKeysStart_def =
+    x3::eps;
+
 auto const suggestKeysEnd_def =
     x3::eps;
 
@@ -109,14 +113,14 @@
     ];
 
 auto const listPrefix_def =
-    node_identifier >> &char_('[');
+    node_identifier;
 
 // even though we don't allow no keys to be supplied, the star allows me to check which keys are missing
 auto const listSuffix_def =
     *keyValueWrapper;
 
 auto const listElement_def =
-    listPrefix > listSuffix;
+    listPrefix >> -(!char_('[') >> suggestKeysStart) >> &char_('[') > listSuffix;
 
 auto const list_def =
     node_identifier >> !char_('[');
@@ -162,7 +166,7 @@
     initializePath >> -(absoluteStart >> createPathSuggestions) >> dataNode % '/' >> (trailingSlash >> createPathSuggestions >> (completing | x3::eoi) | (&space_separator | x3::eoi));
 
 auto const dataNodeList_def =
-    -(module) >> createPathSuggestions >> list;
+    createPathSuggestions >> -(module) >> list;
 
 // This intermediate rule is mandatory, because we need the first alternative
 // to be collapsed to a vector. If we didn't use the intermediate rule,
@@ -348,6 +352,7 @@
 BOOST_SPIRIT_DEFINE(command)
 BOOST_SPIRIT_DEFINE(createPathSuggestions)
 BOOST_SPIRIT_DEFINE(createKeySuggestions)
+BOOST_SPIRIT_DEFINE(suggestKeysStart)
 BOOST_SPIRIT_DEFINE(suggestKeysEnd)
 BOOST_SPIRIT_DEFINE(createCommandSuggestions)
 BOOST_SPIRIT_DEFINE(completing)
diff --git a/src/parser.cpp b/src/parser.cpp
index cf433e2..26e417f 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -54,7 +54,11 @@
     ];
     x3::phrase_parse(it, line.end(), grammar, space, parsedCommand);
 
-    return filterByPrefix(ctx.m_suggestions, std::string(ctx.m_completionIterator, line.end()));
+    auto set = filterByPrefix(ctx.m_suggestions, std::string(ctx.m_completionIterator, line.end()));
+    if (set.size() == 1) {
+        return {(*set.begin()) + ctx.m_completionSuffix};
+    }
+    return set;
 }
 
 void Parser::changeNode(const dataPath_& name)
diff --git a/src/parser_context.hpp b/src/parser_context.hpp
index 3871192..5bf1b26 100644
--- a/src/parser_context.hpp
+++ b/src/parser_context.hpp
@@ -22,4 +22,8 @@
     std::set<std::string> m_suggestions;
     // Iterator pointing to where suggestions were created
     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;
 };
diff --git a/src/static_schema.cpp b/src/static_schema.cpp
index a3649ae..6d28626 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -184,7 +184,7 @@
 
     auto topLevelModule = location.m_nodes.empty() ? node.first.get() : location.m_nodes.front().m_prefix.get().m_name;
     auto identModule = value.first ? value.first.value() : topLevelModule;
-    return std::any_of(identities.begin(), identities.end(), [identModule, value](const auto& x) { return x == identModule + ":" + value.second; });
+    return std::any_of(identities.begin(), identities.end(), [toFind = identModule + ":" + value.second](const auto& x) { return x == toFind; });
 }
 
 bool StaticSchema::isLeaf(const schemaPath_& location, const ModuleNodePair& node) const
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 3512baf..ca9e4ff 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -176,7 +176,7 @@
 
     auto topLevelModule = location.m_nodes.empty() ? node.first.get() : location.m_nodes.front().m_prefix.get().m_name;
     auto identModule = value.first ? value.first.value() : topLevelModule;
-    return std::any_of(identities.begin(), identities.end(), [identModule, value](const auto& x) { return x == identModule + ":" + value.second; });
+    return std::any_of(identities.begin(), identities.end(), [toFind = identModule + ":" + value.second](const auto& x) { return x == toFind; });
 }
 
 bool YangSchema::listHasKey(const schemaPath_& location, const ModuleNodePair& node, const std::string& key) const
diff --git a/tests/path_completion.cpp b/tests/path_completion.cpp
index 462a2e6..0e66595 100644
--- a/tests/path_completion.cpp
+++ b/tests/path_completion.cpp
@@ -26,6 +26,8 @@
     schema->addContainer("example:ano/example:a2", "example:a3");
     schema->addContainer("example:bota/example:b2", "example:b3");
     schema->addList("", "example:list", {"number"});
+    schema->addList("", "example:ovoce", {"name"});
+    schema->addList("", "example:ovocezelenina", {"name"});
     schema->addContainer("example:list", "example:contInList");
     schema->addList("", "example:twoKeyList", {"number", "name"});
     Parser parser(schema);
@@ -38,13 +40,13 @@
         SECTION("ls ")
         {
             input = "ls ";
-            expected = {"example:ano", "example:anoda", "example:bota", "second:amelie", "example:list", "example:twoKeyList"};
+            expected = {"example:ano", "example:anoda", "example:bota", "second:amelie", "example:list", "example:twoKeyList", "example:ovoce", "example:ovocezelenina"};
         }
 
         SECTION("ls e")
         {
             input = "ls e";
-            expected = {"example:ano", "example:anoda", "example:bota", "example:list", "example:twoKeyList"};
+            expected = {"example:ano", "example:anoda", "example:bota", "example:list", "example:twoKeyList", "example:ovoce", "example:ovocezelenina"};
         }
 
         SECTION("ls example:ano")
@@ -68,13 +70,13 @@
         SECTION("ls /")
         {
             input = "ls /";
-            expected = {"example:ano", "example:anoda", "example:bota", "second:amelie", "example:list", "example:twoKeyList"};
+            expected = {"example:ano", "example:anoda", "example:bota", "second:amelie", "example:list", "example:twoKeyList", "example:ovoce", "example:ovocezelenina"};
         }
 
         SECTION("ls /e")
         {
             input = "ls /e";
-            expected = {"example:ano", "example:anoda", "example:bota", "example:list", "example:twoKeyList"};
+            expected = {"example:ano", "example:anoda", "example:bota", "example:list", "example:twoKeyList", "example:ovoce", "example:ovocezelenina"};
         }
 
         SECTION("ls /s")
@@ -110,6 +112,18 @@
             expected = {"example:list"};
         }
 
+        SECTION("set example:list")
+        {
+            input = "set example:list";
+            expected = {"example:list["};
+        }
+
+        SECTION("cd example:list")
+        {
+            input = "cd example:list";
+            expected = {"example:list["};
+        }
+
         SECTION("cd example:list[")
         {
             input = "cd example:list[";
@@ -146,6 +160,18 @@
             expected = {"name=", "number="};
         }
 
+        SECTION("cd example:twoKeyList[name=\"AHOJ\"")
+        {
+            input = "cd example:twoKeyList[name=\"AHOJ\"";
+            expected = {"]["};
+        }
+
+        SECTION("cd example:twoKeyList[name=\"AHOJ\"]")
+        {
+            input = "cd example:twoKeyList[name=\"AHOJ\"]";
+            expected = {"]["};
+        }
+
         SECTION("cd example:twoKeyList[name=\"AHOJ\"][")
         {
             input = "cd example:twoKeyList[name=\"AHOJ\"][";
@@ -169,6 +195,12 @@
             input = "cd example:twoKeyList[name=\"AHOJ\"][number=123]";
             expected = {"]/"};
         }
+
+        SECTION("cd example:ovoce")
+        {
+            input = "cd example:ovoce";
+            expected = {"example:ovoce", "example:ovocezelenina"};
+        }
     }
 
     REQUIRE(parser.completeCommand(input, errorStream) == expected);