Suggest characters at ends of path fragments

The program now suggests a slash for containers, a space for leafs and a
left bracket for lists. The logic for suggesting the first the starting
bracket for lists got a little bit simplified, since it's now a part of
the list's identifier. The CLI now directly shows, if a
slash/bracket/space is going to be appended. The user can use this
information to deduce if a node is container/list/leaf.

Change-Id: Ib1a3cdd326a1451ba80f3bb48a9e8bdd13c102ae
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 5501247..75d8553 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -333,6 +333,7 @@
     {
         auto& parserContext = x3::get<parser_context_tag>(context);
         parserContext.m_suggestions.clear();
+        parserContext.m_completionSuffix.clear();
     }
 };
 
@@ -556,7 +557,32 @@
         const auto& schema = parserContext.m_schema;
 
         parserContext.m_completionIterator = begin;
-        parserContext.m_suggestions = schema.childNodes(parserContext.m_curPath, Recursion::NonRecursive);
+        auto suggestions = schema.childNodes(parserContext.m_curPath, Recursion::NonRecursive);
+        std::set<std::string> suffixesAdded;
+        std::transform(suggestions.begin(), suggestions.end(),
+            std::inserter(suffixesAdded, suffixesAdded.end()),
+            [&parserContext, &schema] (auto it) {
+                ModuleNodePair node;
+                if (auto colonPos = it.find(":"); colonPos != it.npos) {
+                    node.first = it.substr(0, colonPos);
+                    node.second = it.substr(colonPos + 1, node.second.npos);
+                } else {
+                    node.first = boost::none;
+                    node.second = it;
+                }
+
+                if (schema.isLeaf(parserContext.m_curPath, node)) {
+                    return it + " ";
+                }
+                if (schema.isContainer(parserContext.m_curPath, node)) {
+                    return it + "/";
+                }
+                if (schema.isList(parserContext.m_curPath, node)) {
+                    return it + "[";
+                }
+                return it;
+        });
+        parserContext.m_suggestions = suffixesAdded;
     }
 };
 
@@ -593,16 +619,6 @@
     }
 };
 
-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 4fd7b15..0a259b8 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -71,7 +71,6 @@
 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";
@@ -118,9 +117,6 @@
 auto const createKeySuggestions_def =
     x3::eps;
 
-auto const suggestKeysStart_def =
-    x3::eps;
-
 auto const suggestKeysEnd_def =
     x3::eps;
 
@@ -148,7 +144,7 @@
     *keyValueWrapper;
 
 auto const listElement_def =
-    listPrefix >> -(!char_('[') >> suggestKeysStart) >> &char_('[') > listSuffix;
+    listPrefix >> &char_('[') > listSuffix;
 
 auto const list_def =
     node_identifier >> !char_('[');
@@ -405,7 +401,6 @@
 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/static_schema.cpp b/src/static_schema.cpp
index a3f59aa..60cddf9 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -107,6 +107,8 @@
 void StaticSchema::addLeaf(const std::string& location, const std::string& name, const yang::LeafDataTypes& type)
 {
     m_nodes.at(location).emplace(name, yang::leaf{type, {}, {}, {}});
+    std::string key = joinPaths(location, name);
+    m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
 }
 
 void StaticSchema::addLeafEnum(const std::string& location, const std::string& name, std::set<std::string> enumValues)
@@ -115,6 +117,8 @@
     toAdd.m_type = yang::LeafDataTypes::Enum;
     toAdd.m_enumValues = enumValues;
     m_nodes.at(location).emplace(name, toAdd);
+    std::string key = joinPaths(location, name);
+    m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
 }
 
 void StaticSchema::addLeafIdentityRef(const std::string& location, const std::string& name, const ModuleValuePair& base)
@@ -124,6 +128,8 @@
     toAdd.m_type = yang::LeafDataTypes::IdentityRef;
     toAdd.m_identBase = base;
     m_nodes.at(location).emplace(name, toAdd);
+    std::string key = joinPaths(location, name);
+    m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
 }
 
 void StaticSchema::addLeafRef(const std::string& location, const std::string& name, const std::string& source)
@@ -132,6 +138,8 @@
     toAdd.m_type = yang::LeafDataTypes::LeafRef;
     toAdd.m_leafRefSource = source;
     m_nodes.at(location).emplace(name, toAdd);
+    std::string key = joinPaths(location, name);
+    m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
 }
 
 void StaticSchema::addModule(const std::string& name)
diff --git a/tests/path_completion.cpp b/tests/path_completion.cpp
index 5adb7ff..c68cca0 100644
--- a/tests/path_completion.cpp
+++ b/tests/path_completion.cpp
@@ -51,25 +51,25 @@
         SECTION("ls ")
         {
             input = "ls ";
-            expected = {"example:ano", "example:anoda", "example:bota", "example:leafInt", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList", "second:amelie"};
+            expected = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list[", "example:ovoce[", "example:ovocezelenina[", "example:twoKeyList[", "second:amelie/"};
         }
 
         SECTION("ls e")
         {
             input = "ls e";
-            expected = {"example:ano", "example:anoda", "example:bota", "example:leafInt", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList"};
+            expected = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list[", "example:ovoce[", "example:ovocezelenina[", "example:twoKeyList["};
         }
 
         SECTION("ls example:ano")
         {
             input = "ls example:ano";
-            expected = {"example:ano", "example:anoda"};
+            expected = {"example:ano/", "example:anoda/"};
         }
 
         SECTION("ls example:ano/example:a")
         {
             input = "ls example:ano/example:a";
-            expected = {"example:a2"};
+            expected = {"example:a2/"};
         }
 
         SECTION("ls x")
@@ -81,32 +81,44 @@
         SECTION("ls /")
         {
             input = "ls /";
-            expected = {"example:ano", "example:anoda", "example:bota", "example:leafInt", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList", "second:amelie"};
+            expected = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list[", "example:ovoce[", "example:ovocezelenina[", "example:twoKeyList[", "second:amelie/"};
         }
 
         SECTION("ls /e")
         {
             input = "ls /e";
-            expected = {"example:ano", "example:anoda", "example:bota", "example:leafInt", "example:list", "example:ovoce", "example:ovocezelenina", "example:twoKeyList"};
+            expected = {"example:ano/", "example:anoda/", "example:bota/", "example:leafInt ", "example:list[", "example:ovoce[", "example:ovocezelenina[", "example:twoKeyList["};
 
         }
 
+        SECTION("ls example:bota")
+        {
+            input = "ls example:bota";
+            expected = {"example:bota/"};
+        }
+
+        SECTION("ls /example:bota")
+        {
+            input = "ls /example:bota";
+            expected = {"example:bota/"};
+        }
+
         SECTION("ls /s")
         {
             input = "ls /s";
-            expected = {"second:amelie"};
+            expected = {"second:amelie/"};
         }
 
         SECTION("ls /example:list[number=3]/")
         {
             input = "ls /example:list[number=3]/";
-            expected = {"example:contInList"};
+            expected = {"example:contInList/"};
         }
 
         SECTION("ls /example:list[number=3]/c")
         {
             input = "ls /example:list[number=3]/e";
-            expected = {"example:contInList"};
+            expected = {"example:contInList/"};
         }
 
         SECTION("ls /example:list[number=3]/a")
@@ -121,7 +133,7 @@
         SECTION("cd example:lis")
         {
             input = "cd example:lis";
-            expected = {"example:list"};
+            expected = {"example:list["};
         }
 
         SECTION("set example:list")
@@ -211,7 +223,7 @@
         SECTION("cd example:ovoce")
         {
             input = "cd example:ovoce";
-            expected = {"example:ovoce", "example:ovocezelenina"};
+            expected = {"example:ovoce[", "example:ovocezelenina["};
         }
     }