Tab completion for list keys

Change-Id: Ifbebd0e27d3a26237c12e5492a2737612fc7a644
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 05283b8..b1d0ccb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -91,6 +91,7 @@
     src/utils.cpp
     src/parser_context.cpp
     src/interpreter.cpp
+    src/ast_handlers.cpp
     )
 target_link_libraries(parser schemas)
 
diff --git a/src/ast_handlers.cpp b/src/ast_handlers.cpp
new file mode 100644
index 0000000..ca5fe25
--- /dev/null
+++ b/src/ast_handlers.cpp
@@ -0,0 +1,15 @@
+#include "ast_handlers.hpp"
+std::set<std::string> generateMissingKeyCompletionSet(std::set<std::string> keysNeeded, std::set<std::string> currentSet)
+{
+    std::set<std::string> missingKeys;
+    std::set_difference(keysNeeded.begin(), keysNeeded.end(),
+            currentSet.begin(), currentSet.end(),
+            std::inserter(missingKeys, missingKeys.end()));
+
+    std::set<std::string> res;
+
+    std::transform(missingKeys.begin(), missingKeys.end(),
+                   std::inserter(res, res.end()),
+                   [] (auto it) { return it + "="; });
+    return res;
+}
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index b9b9101..5a55f36 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -11,6 +11,9 @@
 #include "parser_context.hpp"
 #include "schema.hpp"
 #include "utils.hpp"
+namespace x3 = boost::spirit::x3;
+
+struct parser_context_tag;
 
 struct keyValue_class {
     template <typename T, typename Iterator, typename Context>
@@ -498,3 +501,36 @@
         parserContext.m_suggestions = schema.childNodes(parserContext.m_curPath, Recursion::NonRecursive);
     }
 };
+
+std::set<std::string> generateMissingKeyCompletionSet(std::set<std::string> keysNeeded, std::set<std::string> currentSet);
+
+struct createKeySuggestions_class {
+    template <typename T, typename Iterator, typename Context>
+    void on_success(Iterator const& begin, Iterator const&, T&, Context const& context)
+    {
+        auto& parserContext = x3::get<parser_context_tag>(context);
+        const auto& schema = parserContext.m_schema;
+
+        parserContext.m_completionIterator = begin;
+
+        const auto& keysNeeded = schema.listKeys(parserContext.m_curPath, {parserContext.m_curModule, parserContext.m_tmpListName});
+        parserContext.m_suggestions = generateMissingKeyCompletionSet(keysNeeded, parserContext.m_tmpListKeys);
+    }
+};
+
+struct suggestKeysEnd_class {
+    template <typename T, typename Iterator, typename Context>
+    void on_success(Iterator const& begin, Iterator const&, T&, Context const& context)
+    {
+        auto& parserContext = x3::get<parser_context_tag>(context);
+        const auto& schema = parserContext.m_schema;
+
+        parserContext.m_completionIterator = begin;
+        const auto& keysNeeded = schema.listKeys(parserContext.m_curPath, {parserContext.m_curModule, parserContext.m_tmpListName});
+        if (generateMissingKeyCompletionSet(keysNeeded, parserContext.m_tmpListKeys).empty()) {
+            parserContext.m_suggestions = {"]/"};
+        } else {
+            parserContext.m_suggestions = {"]"};
+        }
+    }
+};
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 79cd17a..2bf7be4 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -55,6 +55,8 @@
 
 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<suggestKeysEnd_class, x3::unused_type> const suggestKeysEnd = "suggestKeysEnd";
 
 #if __clang__
 #pragma GCC diagnostic push
@@ -73,8 +75,17 @@
 auto const number =
         +x3::digit;
 
+auto const createKeySuggestions_def =
+        x3::eps;
+
+auto const suggestKeysEnd_def =
+        x3::eps;
+
 auto const keyValue_def =
-        lexeme['[' > key_identifier > '=' > (quotedValue | number) > ']'];
+        key_identifier > '=' > (quotedValue | number);
+
+auto const keyValueWrapper =
+        lexeme['[' > createKeySuggestions > keyValue > suggestKeysEnd > ']'];
 
 auto const module_identifier_def =
         lexeme[
@@ -91,7 +102,7 @@
 
 // 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 =
-        *keyValue;
+        *keyValueWrapper;
 
 auto const listElement_def =
         listPrefix > listSuffix;
@@ -272,3 +283,5 @@
 BOOST_SPIRIT_DEFINE(delete_rule)
 BOOST_SPIRIT_DEFINE(command)
 BOOST_SPIRIT_DEFINE(createPathSuggestions)
+BOOST_SPIRIT_DEFINE(createKeySuggestions)
+BOOST_SPIRIT_DEFINE(suggestKeysEnd)
diff --git a/tests/path_completion.cpp b/tests/path_completion.cpp
index cd1a2c6..7a9090b 100644
--- a/tests/path_completion.cpp
+++ b/tests/path_completion.cpp
@@ -33,70 +33,142 @@
     std::ostringstream errorStream;
     std::set<std::string> expected;
 
-    SECTION("ls ")
+    SECTION("node name completion")
     {
-        input = "ls ";
-        expected = {"example:ano", "example:anoda", "example:bota", "second:amelie", "example:list", "example:twoKeyList"};
+        SECTION("ls ")
+        {
+            input = "ls ";
+            expected = {"example:ano", "example:anoda", "example:bota", "second:amelie", "example:list", "example:twoKeyList"};
+        }
+
+        SECTION("ls e")
+        {
+            input = "ls e";
+            expected = {"xample:ano", "xample:anoda", "xample:bota", "xample:list", "xample:twoKeyList"};
+        }
+
+        SECTION("ls example:ano")
+        {
+            input = "ls example:ano";
+            expected = {"", "da"};
+        }
+
+        SECTION("ls example:ano/example:a")
+        {
+            input = "ls example:ano/example:a";
+            expected = {"2"};
+        }
+
+        SECTION("ls x")
+        {
+            input = "ls x";
+            expected = {};
+        }
+
+        SECTION("ls /")
+        {
+            input = "ls /";
+            expected = {"example:ano", "example:anoda", "example:bota", "second:amelie", "example:list", "example:twoKeyList"};
+        }
+
+        SECTION("ls /e")
+        {
+            input = "ls /e";
+            expected = {"xample:ano", "xample:anoda", "xample:bota", "xample:list", "xample:twoKeyList"};
+        }
+
+        SECTION("ls /s")
+        {
+            input = "ls /s";
+            expected = {"econd:amelie"};
+        }
+
+        SECTION("ls /example:list[number=3]/")
+        {
+            input = "ls /example:list[number=3]/";
+            expected = {"example:contInList"};
+        }
+
+        SECTION("ls /example:list[number=3]/c")
+        {
+            input = "ls /example:list[number=3]/e";
+            expected = {"xample:contInList"};
+        }
+
+        SECTION("ls /example:list[number=3]/a")
+        {
+            input = "ls /example:list[number=3]/a";
+            expected = {};
+        }
     }
 
-    SECTION("ls e")
+    SECTION("list keys completion")
     {
-        input = "ls e";
-        expected = {"xample:ano", "xample:anoda", "xample:bota", "xample:list", "xample:twoKeyList"};
-    }
+        SECTION("cd example:lis")
+        {
+            input = "cd example:lis";
+            expected = {"t"};
+        }
 
-    SECTION("ls example:ano")
-    {
-        input = "ls example:ano";
-        expected = {"", "da"};
-    }
+        SECTION("cd example:list[")
+        {
+            input = "cd example:list[";
+            expected = {"number="};
+        }
 
-    SECTION("ls example:ano/example:a")
-    {
-        input = "ls example:ano/example:a";
-        expected = {"2"};
-    }
+        SECTION("cd example:list[numb")
+        {
+            input = "cd example:list[numb";
+            expected = {"er="};
+        }
 
-    SECTION("ls x")
-    {
-        input = "ls x";
-        expected = {};
-    }
+        SECTION("cd example:list[number")
+        {
+            input = "cd example:list[number";
+            expected = {"="};
+        }
 
-    SECTION("ls /")
-    {
-        input = "ls /";
-        expected = {"example:ano", "example:anoda", "example:bota", "second:amelie", "example:list", "example:twoKeyList"};
-    }
+        SECTION("cd example:list[number=12")
+        {
+            input = "cd example:list[number=12";
+            expected = {"]/"};
+        }
 
-    SECTION("ls /e")
-    {
-        input = "ls /e";
-        expected = {"xample:ano", "xample:anoda", "xample:bota", "xample:list", "xample:twoKeyList"};
-    }
+        SECTION("cd example:list[number=12]")
+        {
+            input = "cd example:list[number=12]";
+            expected = {"/"};
+        }
 
-    SECTION("ls /s")
-    {
-        input = "ls /s";
-        expected = {"econd:amelie"};
-    }
+        SECTION("cd example:twoKeyList[")
+        {
+            input = "cd example:twoKeyList[";
+            expected = {"name=", "number="};
+        }
 
-    SECTION("ls /example:list[number=3]/")
-    {
-        input = "ls /example:list[number=3]/";
-        expected = {"example:contInList"};
-    }
+        SECTION("cd example:twoKeyList[name=\"AHOJ\"][")
+        {
+            input = "cd example:twoKeyList[name=\"AHOJ\"][";
+            expected = {"number="};
+        }
 
-    SECTION("ls /example:list[number=3]/c")
-    {
-        input = "ls /example:list[number=3]/e";
-        expected = {"xample:contInList"};
-    }
+        SECTION("cd example:twoKeyList[number=42][")
+        {
+            input = "cd example:twoKeyList[number=42][";
+            expected = {"name="};
+        }
 
-    SECTION("ls /example:list[number=3]/a")
-    {
-        input = "ls /example:list[number=3]/a";
-        expected = {};
+        SECTION("cd example:twoKeyList[name=\"AHOJ\"][number=123")
+        {
+            input = "cd example:twoKeyList[name=\"AHOJ\"][number=123";
+            expected = {"]/"};
+        }
+
+        SECTION("cd example:twoKeyList[name=\"AHOJ\"][number=123]")
+        {
+            input = "cd example:twoKeyList[name=\"AHOJ\"][number=123]";
+            expected = {"/"};
+        }
     }
 
     REQUIRE(parser.completeCommand(input, errorStream) == expected);