Switch from linenoise to replxx

Change-Id: Id3e04eca8dbd7e68cef080713296fef3fdc683c5
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5656ee8..b8ee606 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -50,6 +50,11 @@
 find_package(docopt REQUIRED)
 find_package(spdlog REQUIRED)
 find_package(Boost REQUIRED)
+find_library(REPLXX_LIBRARY replxx REQUIRED)
+find_path(REPLXX_PATH replxx.hxx)
+if("${REPLXX_PATH}" STREQUAL REPLXX_PATH-NOTFOUND)
+    message(FATAL_ERROR "Cannot find the \"replxx.hxx\" include file for the replxx library.")
+endif()
 
 find_package(PkgConfig)
 pkg_check_modules(LIBYANG REQUIRED libyang-cpp>=0.15.111)
@@ -108,7 +113,8 @@
 add_executable(netconf-cli
     src/main.cpp
     )
-target_link_libraries(netconf-cli sysrepoaccess yangschema docopt parser)
+target_link_libraries(netconf-cli sysrepoaccess yangschema docopt parser ${REPLXX_LIBRARY})
+target_include_directories(netconf-cli PRIVATE ${REPLXX_PATH})
 if(CMAKE_CXX_FLAGS MATCHES "-stdlib=libc\\+\\+")
     target_link_libraries(netconf-cli c++experimental)
 else()
diff --git a/src/main.cpp b/src/main.cpp
index 3c973dd..4fe798f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -5,11 +5,10 @@
  * Written by Václav Kubernát <kubervac@fit.cvut.cz>
  *
 */
-
 #include <docopt.h>
 #include <experimental/filesystem>
 #include <iostream>
-#include <linenoise.hpp>
+#include <replxx.hxx>
 #include "NETCONF_CLI_VERSION.h"
 #include "interpreter.hpp"
 #include "sysrepo_access.hpp"
@@ -37,24 +36,27 @@
 
     SysrepoAccess datastore("netconf-cli");
     Parser parser(datastore.schema());
+    replxx::Replxx lineEditor;
+    lineEditor.set_completion_callback([&parser](const std::string& input, int&) {
+        std::stringstream stream;
+        auto completionsSet = parser.completeCommand(input, stream);
+
+        std::vector<std::string> res;
+        std::transform(completionsSet.begin(), completionsSet.end(), std::back_inserter(res),
+                [input](auto it) { return it; });
+        return res;
+    });
+    lineEditor.set_word_break_characters(" '/[");
 
     while (true) {
-        linenoise::SetCompletionCallback([&parser](const char* editBuffer, std::vector<std::string>& completions) {
-            std::stringstream stream;
-            auto completionsSet = parser.completeCommand(editBuffer, stream);
-            std::transform(completionsSet.begin(), completionsSet.end(), std::back_inserter(completions),
-                           [editBuffer](auto it) { return std::string(editBuffer) + it; });
-        });
-        linenoise::SetHistoryMaxLen(4);
-        linenoise::SetMultiLine(true);
-        std::string line;
-        auto quit = linenoise::Readline((parser.currentNode() + "> ").c_str(), line);
-        if (quit) {
+        auto line = lineEditor.input(parser.currentNode() + "> ");
+        if (!line) {
             break;
         }
 
         std::locale C_locale("C");
-        if (std::all_of(line.begin(), line.end(),
+        std::string_view view{line};
+        if (std::all_of(view.begin(), view.end(),
                         [C_locale](const auto c) { return std::isspace(c, C_locale);})) {
             continue;
         }
@@ -66,7 +68,7 @@
             std::cerr << ex.what() << std::endl;
         }
 
-        linenoise::AddHistory(line.c_str());
+        lineEditor.history_add(line);
     }
 
     return 0;
diff --git a/src/parser.cpp b/src/parser.cpp
index 2dbea6b..ba6690d 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -53,7 +53,7 @@
     ];
     x3::phrase_parse(it, line.end(), grammar, space, parsedCommand);
 
-    return filterAndErasePrefix(ctx.m_suggestions, std::string(ctx.m_completionIterator, line.end()));
+    return filterByPrefix(ctx.m_suggestions, std::string(ctx.m_completionIterator, line.end()));
 }
 
 void Parser::changeNode(const dataPath_& name)
diff --git a/src/utils.cpp b/src/utils.cpp
index 358bc05..595dc7a 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -67,17 +67,11 @@
     return fullNodeName(dataPathToSchemaPath(location), pair);
 }
 
-std::set<std::string> filterAndErasePrefix(const std::set<std::string>& set, const std::string_view prefix)
+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); });
-
-    std::set<std::string> withoutPrefix;
-    std::transform(filtered.begin(), filtered.end(),
-            std::inserter(withoutPrefix, withoutPrefix.end()),
-            [prefix] (auto it) { boost::erase_first(it, prefix); return it; });
-    return withoutPrefix;
+    return filtered;
 }
-
diff --git a/src/utils.hpp b/src/utils.hpp
index 38db0af..dcaefb3 100644
--- a/src/utils.hpp
+++ b/src/utils.hpp
@@ -19,7 +19,5 @@
 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
- * and with the actual prefix deleted from the string
- */
-std::set<std::string> filterAndErasePrefix(const std::set<std::string>& set, const std::string_view prefix);
+/** 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);
diff --git a/tests/command_completion.cpp b/tests/command_completion.cpp
index f3cc868..21410c8 100644
--- a/tests/command_completion.cpp
+++ b/tests/command_completion.cpp
@@ -32,13 +32,13 @@
     SECTION("c")
     {
         input = "c";
-        expected = {"d", "ommit", "reate"};
+        expected = {"cd", "commit", "create"};
     }
 
     SECTION("d")
     {
         input = "d";
-        expected = {"elete", "iscard"};
+        expected = {"delete", "discard"};
     }
 
     SECTION("x")
@@ -51,13 +51,13 @@
     {
         input = "cd";
         // TODO: depending on how Readline works, this will have to be changed to include a space
-        expected = {""};
+        expected = {"cd"};
     }
 
     SECTION("create")
     {
         input = "create";
-        expected = {""};
+        expected = {"create"};
     }
 
     REQUIRE(parser.completeCommand(input, errorStream) == expected);
diff --git a/tests/path_completion.cpp b/tests/path_completion.cpp
index 7a9090b..e0faf15 100644
--- a/tests/path_completion.cpp
+++ b/tests/path_completion.cpp
@@ -44,19 +44,19 @@
         SECTION("ls e")
         {
             input = "ls e";
-            expected = {"xample:ano", "xample:anoda", "xample:bota", "xample:list", "xample:twoKeyList"};
+            expected = {"example:ano", "example:anoda", "example:bota", "example:list", "example:twoKeyList"};
         }
 
         SECTION("ls example:ano")
         {
             input = "ls example:ano";
-            expected = {"", "da"};
+            expected = {"example:ano", "example:anoda"};
         }
 
         SECTION("ls example:ano/example:a")
         {
             input = "ls example:ano/example:a";
-            expected = {"2"};
+            expected = {"example:a2"};
         }
 
         SECTION("ls x")
@@ -74,13 +74,13 @@
         SECTION("ls /e")
         {
             input = "ls /e";
-            expected = {"xample:ano", "xample:anoda", "xample:bota", "xample:list", "xample:twoKeyList"};
+            expected = {"example:ano", "example:anoda", "example:bota", "example:list", "example:twoKeyList"};
         }
 
         SECTION("ls /s")
         {
             input = "ls /s";
-            expected = {"econd:amelie"};
+            expected = {"second:amelie"};
         }
 
         SECTION("ls /example:list[number=3]/")
@@ -92,7 +92,7 @@
         SECTION("ls /example:list[number=3]/c")
         {
             input = "ls /example:list[number=3]/e";
-            expected = {"xample:contInList"};
+            expected = {"example:contInList"};
         }
 
         SECTION("ls /example:list[number=3]/a")
@@ -107,7 +107,7 @@
         SECTION("cd example:lis")
         {
             input = "cd example:lis";
-            expected = {"t"};
+            expected = {"example:list"};
         }
 
         SECTION("cd example:list[")
@@ -119,13 +119,13 @@
         SECTION("cd example:list[numb")
         {
             input = "cd example:list[numb";
-            expected = {"er="};
+            expected = {"number="};
         }
 
         SECTION("cd example:list[number")
         {
             input = "cd example:list[number";
-            expected = {"="};
+            expected = {"number="};
         }
 
         SECTION("cd example:list[number=12")
@@ -137,7 +137,7 @@
         SECTION("cd example:list[number=12]")
         {
             input = "cd example:list[number=12]";
-            expected = {"/"};
+            expected = {"]/"};
         }
 
         SECTION("cd example:twoKeyList[")
@@ -167,7 +167,7 @@
         SECTION("cd example:twoKeyList[name=\"AHOJ\"][number=123]")
         {
             input = "cd example:twoKeyList[name=\"AHOJ\"][number=123]";
-            expected = {"/"};
+            expected = {"]/"};
         }
     }
 
diff --git a/tests/utils.cpp b/tests/utils.cpp
index 62e4c23..b7ce8fb 100644
--- a/tests/utils.cpp
+++ b/tests/utils.cpp
@@ -11,17 +11,16 @@
 
 TEST_CASE("utils")
 {
-    SECTION("filterAndErasePrefix")
+    SECTION("filterByPrefix")
     {
         std::set<std::string> set{"ahoj", "coze", "copak", "aha", "polivka"};
 
-        REQUIRE((filterAndErasePrefix(set, "a") == std::set<std::string>{"hoj", "ha"}));
-        REQUIRE((filterAndErasePrefix(set, "ah") == std::set<std::string>{"oj", "a"}));
-        REQUIRE((filterAndErasePrefix(set, "aho") == std::set<std::string>{"j"}));
-        REQUIRE((filterAndErasePrefix(set, "polivka") == std::set<std::string>{""}));
-        REQUIRE((filterAndErasePrefix(set, "polivka") == std::set<std::string>{""}));
-        REQUIRE((filterAndErasePrefix(set, "polivkax") == std::set<std::string>{}));
-        REQUIRE((filterAndErasePrefix(set, "co") == std::set<std::string>{"pak", "ze"}));
+        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"}));
     }
 
 }