Complete slash after lists when inputting "any" path

Choosing what to complete based on what kind of node we're parsing is no
longer suitable. Example is a path ending with list. That kind of path
is by itself a data path, so it gets completed by a left bracket,
however, if we're using the `ls` command, we would like it to complete a
slash. That's why there is now a separate template argument for
NodeParser (and PathParser), which controls the completion mode.

Change-Id: Ib8c9e502f7d57aa88fe0087e7a940370b422ad40
diff --git a/src/path_parser.hpp b/src/path_parser.hpp
index ecd3bd7..6cac725 100644
--- a/src/path_parser.hpp
+++ b/src/path_parser.hpp
@@ -58,9 +58,13 @@
     using type = dataNode_;
 };
 
+enum class CompletionMode {
+    Schema,
+    Data
+};
 
-template <NodeParserMode PARSER_MODE>
-struct NodeParser : x3::parser<NodeParser<PARSER_MODE>> {
+template <NodeParserMode PARSER_MODE, CompletionMode COMPLETION_MODE>
+struct NodeParser : x3::parser<NodeParser<PARSER_MODE, COMPLETION_MODE>> {
     using attribute_type = typename ModeToAttribute<PARSER_MODE>::type;
 
     std::function<bool(const Schema&, const std::string& path)> m_filterFunction;
@@ -114,7 +118,12 @@
                     } else {
                         out.m_suffix = listElement_{child.second, {}};
                     }
-                    parserContext.m_suggestions.emplace(Completion{parseString, "[", Completion::WhenToAdd::IfFullMatch});
+
+                    if constexpr (COMPLETION_MODE == CompletionMode::Schema) {
+                        parserContext.m_suggestions.emplace(Completion{parseString + "/"});
+                    } else {
+                        parserContext.m_suggestions.emplace(Completion{parseString, "[", Completion::WhenToAdd::IfFullMatch});
+                    }
                     break;
                 case yang::NodeTypes::LeafList:
                     if constexpr (std::is_same<attribute_type, schemaNode_>()) {
@@ -122,7 +131,12 @@
                     } else {
                         out.m_suffix = leafListElement_{child.second, {}};
                     }
-                    parserContext.m_suggestions.emplace(Completion{parseString, "[", Completion::WhenToAdd::IfFullMatch});
+
+                    if constexpr (COMPLETION_MODE == CompletionMode::Schema) {
+                        parserContext.m_suggestions.emplace(Completion{parseString + "/"});
+                    } else {
+                        parserContext.m_suggestions.emplace(Completion{parseString, "[", Completion::WhenToAdd::IfFullMatch});
+                    }
                     break;
                 case yang::NodeTypes::Action:
                 case yang::NodeTypes::AnyXml:
@@ -216,10 +230,10 @@
     }
 };
 
-using schemaNode = NodeParser<NodeParserMode::SchemaNode>;
-using dataNode = NodeParser<NodeParserMode::CompleteDataNode>;
-using incompleteDataNode = NodeParser<NodeParserMode::IncompleteDataNode>;
-using pathCompletions = NodeParser<NodeParserMode::CompletionsOnly>;
+template <CompletionMode COMPLETION_MODE> using schemaNode = NodeParser<NodeParserMode::SchemaNode, COMPLETION_MODE>;
+template <CompletionMode COMPLETION_MODE> using dataNode = NodeParser<NodeParserMode::CompleteDataNode, COMPLETION_MODE>;
+template <CompletionMode COMPLETION_MODE> using incompleteDataNode = NodeParser<NodeParserMode::IncompleteDataNode, COMPLETION_MODE>;
+template <CompletionMode COMPLETION_MODE> using pathCompletions = NodeParser<NodeParserMode::CompletionsOnly, COMPLETION_MODE>;
 
 using AnyPath = boost::variant<schemaPath_, dataPath_>;
 
@@ -244,8 +258,8 @@
     using type = dataPath_;
 };
 
-template <PathParserMode PARSER_MODE>
-struct PathParser : x3::parser<PathParser<PARSER_MODE>> {
+template <PathParserMode PARSER_MODE, CompletionMode COMPLETION_MODE>
+struct PathParser : x3::parser<PathParser<PARSER_MODE, COMPLETION_MODE>> {
     using attribute_type = ModeToAttribute<PARSER_MODE>;
     std::function<bool(const Schema&, const std::string& path)> m_filterFunction;
 
@@ -266,7 +280,7 @@
         // gets reverted to before the starting slash.
         auto res = (-absoluteStart).parse(begin, end, ctx, rctx, attrData.m_scope);
         auto dataPath = x3::attr(attrData.m_scope)
-            >> (dataNode{m_filterFunction} % '/' | pathEnd >> x3::attr(std::vector<dataNode_>{}))
+            >> (dataNode<COMPLETION_MODE>{m_filterFunction} % '/' | pathEnd >> x3::attr(std::vector<dataNode_>{}))
             >> -trailingSlash;
         res = dataPath.parse(begin, end, ctx, rctx, attrData);
 
@@ -274,13 +288,13 @@
         if constexpr (PARSER_MODE == PathParserMode::DataPathListEnd || PARSER_MODE == PathParserMode::AnyPath) {
             if (!res || !pathEnd.parse(begin, end, ctx, rctx, x3::unused)) {
                 dataNode_ attrNodeList;
-                res = incompleteDataNode{m_filterFunction}.parse(begin, end, ctx, rctx, attrNodeList);
+                res = incompleteDataNode<COMPLETION_MODE>{m_filterFunction}.parse(begin, end, ctx, rctx, attrNodeList);
                 if (res) {
                     attrData.m_nodes.push_back(attrNodeList);
                     // If the trailing slash matches, no more nodes are parsed.
                     // That means no more completion. So, I generate them
                     // manually.
-                    res = (-(trailingSlash >> x3::omit[pathCompletions{m_filterFunction}])).parse(begin, end, ctx, rctx, attrData.m_trailingSlash);
+                    res = (-(trailingSlash >> x3::omit[pathCompletions<COMPLETION_MODE>{m_filterFunction}])).parse(begin, end, ctx, rctx, attrData.m_trailingSlash);
                 }
             }
         }
@@ -295,7 +309,7 @@
                 if (!res || !pathEnd.parse(begin, end, ctx, rctx, x3::unused)) {
                     // If dataPath parsed some nodes, they will be saved in `attrData`. We have to keep these.
                     schemaPath_ attrSchema = dataPathToSchemaPath(attrData);
-                    auto schemaPath = schemaNode{m_filterFunction} % '/';
+                    auto schemaPath = schemaNode<COMPLETION_MODE>{m_filterFunction} % '/';
                     // The schemaPath parser continues where the dataPath parser ended.
                     res = schemaPath.parse(begin, end, ctx, rctx, attrSchema.m_nodes);
                     auto trailing = -trailingSlash >> pathEnd;
@@ -315,9 +329,9 @@
 // The PathParser class would get a boost::variant as the attribute, but I
 // don't want to deal with that, so I use these wrappers to ensure the
 // attribute I want (and let Spirit deal with boost::variant).
-auto const anyPath = x3::rule<class anyPath_class, AnyPath>{"anyPath"} = PathParser<PathParserMode::AnyPath>{};
-auto const dataPath = x3::rule<class dataPath_class, dataPath_>{"dataPath"} = PathParser<PathParserMode::DataPath>{};
-auto const dataPathListEnd = x3::rule<class dataPath_class, dataPath_>{"dataPath"} = PathParser<PathParserMode::DataPathListEnd>{};
+auto const anyPath = x3::rule<class anyPath_class, AnyPath>{"anyPath"} = PathParser<PathParserMode::AnyPath, CompletionMode::Schema>{};
+auto const dataPath = x3::rule<class dataPath_class, dataPath_>{"dataPath"} = PathParser<PathParserMode::DataPath, CompletionMode::Data>{};
+auto const dataPathListEnd = x3::rule<class dataPath_class, dataPath_>{"dataPath"} = PathParser<PathParserMode::DataPathListEnd, CompletionMode::Data>{};
 
 #if __clang__
 #pragma GCC diagnostic push
@@ -381,7 +395,7 @@
 };
 
 auto const writableLeafPath_def =
-    PathParser<PathParserMode::DataPath>{filterConfigFalse};
+    PathParser<PathParserMode::DataPath, CompletionMode::Data>{filterConfigFalse};
 
 auto const presenceContainerPath_def =
     dataPath;
diff --git a/tests/path_completion.cpp b/tests/path_completion.cpp
index 71fc917..3002ed7 100644
--- a/tests/path_completion.cpp
+++ b/tests/path_completion.cpp
@@ -38,6 +38,7 @@
     schema->addLeaf("/example:twoKeyList", "example:number", yang::Int32{});
     schema->addLeaf("/", "example:leafInt", yang::Int32{});
     schema->addLeaf("/", "example:readonly", yang::Int32{}, yang::AccessType::ReadOnly);
+    schema->addLeafList("/", "example:addresses", yang::String{});
     auto mockDatastore = std::make_shared<MockDatastoreAccess>();
 
     // The parser will use DataQuery for key value completion, but I'm not testing that here, so I don't return anything.
@@ -67,14 +68,14 @@
         SECTION("ls ")
         {
             input = "ls ";
-            expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list", "example:ovoce", "example:readonly ", "example:ovocezelenina", "example:twoKeyList", "second:amelie/"};
+            expectedCompletions = {"example:addresses/", "example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list/", "example:ovoce/", "example:readonly ", "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:readonly ", "example:ovocezelenina", "example:twoKeyList"};
+            expectedCompletions = {"example:addresses/", "example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list/", "example:ovoce/", "example:readonly ", "example:ovocezelenina/", "example:twoKeyList/"};
             expectedContextLength = 1;
         }
 
@@ -102,14 +103,14 @@
         SECTION("ls /")
         {
             input = "ls /";
-            expectedCompletions = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list", "example:ovoce", "example:readonly ", "example:ovocezelenina", "example:twoKeyList", "second:amelie/"};
+            expectedCompletions = {"example:addresses/", "example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list/", "example:ovoce/", "example:readonly ", "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:readonly ", "example:ovocezelenina", "example:twoKeyList"};
+            expectedCompletions = {"example:addresses/", "example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list/", "example:ovoce/", "example:readonly ", "example:ovocezelenina/", "example:twoKeyList/"};
             expectedContextLength = 1;
         }
 
@@ -134,6 +135,13 @@
             expectedContextLength = 1;
         }
 
+        SECTION("ls /example:list")
+        {
+            input = "ls /example:list";
+            expectedCompletions = {"example:list/"};
+            expectedContextLength = 12;
+        }
+
         SECTION("ls /example:list/")
         {
             input = "ls /example:list/";