Selaa lähdekoodia

[WIP-BROKEN] backup
[add] optional class (and tests)
[refactor] moved code along Curse*Output

isundil 9 vuotta sitten
vanhempi
commit
56dcb522f2

+ 1 - 0
.gitignore

@@ -37,6 +37,7 @@
 /bin
 /test/json_test
 /test/wrapped_test
+/test/optional_test
 /cmake_install.cmake
 /Makefile
 .fuse_hidden*

+ 11 - 1
CMakeLists.txt

@@ -13,7 +13,6 @@ include_directories(include ${CURSES_INCLUDE_DIRS})
 
 # jsonstroll
 add_executable(jsonstroll
-    src/main.cpp
     src/warning.cpp
     src/params.cpp
     src/curseOutput.cpp
@@ -33,6 +32,8 @@ add_executable(jsonstroll
 
     src/jsonException.cpp
     src/except.cpp
+
+    src/main.cpp
     )
 set_property(TARGET jsonstroll PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CUSTOM_BINARY_OUTPUT_DIR})
 target_link_libraries(jsonstroll ${ncurses++_LIBRARIES} ${CURSES_LIBRARIES})
@@ -69,6 +70,15 @@ set_property(
     )
 add_test(wrapped_test test/wrapped_test)
 
+add_executable(optional_test
+    test/src/optional.cpp
+    )
+add_test(optional_test test/optional_test)
+set_property(
+    TARGET optional_test
+    PROPERTY RUNTIME_OUTPUT_DIRECTORY test
+    )
+
 # Add manual page
 find_program (HELP2MAN help2man)
 add_custom_target(man ALL)

+ 13 - 42
include/curseOutput.hh

@@ -11,6 +11,7 @@
 #include <list>
 #include <set>
 #include <map>
+#include "optional.hpp"
 #include "outputFlag.hh"
 #include "params.hh"
 
@@ -60,13 +61,14 @@ class CurseOutput
         /**
          * redraw item and children
         **/
-        bool redraw(std::pair<int, int> &, const std::pair<unsigned int, unsigned int> &maxWidth, JSonElement *item);
+        virtual bool redraw(std::pair<int, int> &, const std::pair<unsigned int, unsigned int> &maxWidth, JSonElement *item) =0;
 
         /**
          * Wait for input
          * @return false if ncurses should stop
         **/
         bool readInput();
+        virtual Optional<bool> evalKey(int k) =0;
 
         /**
          * get the screen size
@@ -76,7 +78,7 @@ class CurseOutput
         /**
          * set the select_up and select_down pointers, scroll to selection if it is above view port
         **/
-        void checkSelection(const JSonElement *item, const std::pair<int, int>&);
+        virtual void checkSelection(const JSonElement *item, const std::pair<int, int>&) =0;
 
         /**
          * Return the number of lines written while writting nbChar bytes
@@ -90,7 +92,7 @@ class CurseOutput
          * get flags to be passed to write.
          * Contains indications on who to write item
         **/
-        const OutputFlag getFlag(const JSonElement *item) const;
+        virtual const OutputFlag getFlag(const JSonElement *item) const =0;
 
         /**
          * Write data
@@ -100,20 +102,14 @@ class CurseOutput
         **/
         void write(const std::string &str, const OutputFlag flags) const;
         unsigned int write(const int &x, const int &y, JSonElement *item, unsigned int maxWidth, const OutputFlag);
-        unsigned int write(const int &x, const int &y, const std::string &item, const size_t len, unsigned int maxWidth, const OutputFlag);
-        unsigned int write(const int &x, const int &y, const char item, unsigned int maxWidth, const OutputFlag);
-        bool writeKey(const std::string &key, const size_t keylen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxWidth, OutputFlag, unsigned int extraLen =0);
-        bool writeKey(const std::string &key, const size_t keylen, const std::string &after, const size_t afterlen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxWidth, OutputFlag);
-        bool writeKey(const std::string &key, const size_t keylen, const std::string &after, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxWidth, OutputFlag);
-        bool writeContainer(std::pair<int, int> &, const std::pair<unsigned int, unsigned int> &maxSize, const JSonContainer *);
-        bool writeContent(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, std::list<JSonElement *> * obj);
+        virtual unsigned int write(const int &x, const int &y, const std::string &item, const size_t len, unsigned int maxWidth, const OutputFlag) =0;
+        virtual unsigned int write(const int &x, const int &y, const char item, unsigned int maxWidth, const OutputFlag) =0;
+        virtual bool writeKey(const std::string &key, const size_t keylen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxWidth, OutputFlag, unsigned int extraLen =0) =0;
+        virtual bool writeKey(const std::string &key, const size_t keylen, const std::string &after, const size_t afterlen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxWidth, OutputFlag) =0;
+        virtual bool writeKey(const std::string &key, const size_t keylen, const std::string &after, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxWidth, OutputFlag);
 
-        /**
-         * find next search occurence and select it (wich cause viewport to move, eventually)
-         * Have it own redraw because may have to write message
-        **/
-        bool jumpToNextSearch(const JSonElement *initial_selection, bool &);
-        bool jumpToNextSearch();
+        virtual bool writeContainer(std::pair<int, int> &, const std::pair<unsigned int, unsigned int> &maxSize, const JSonContainer *) =0;
+        virtual bool writeContent(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, std::list<JSonElement *> * obj) =0;
 
         /**
          * prompt for user input, and return it
@@ -124,7 +120,7 @@ class CurseOutput
         /**
          * find occurences of search result and fill this#search_result
         **/
-        unsigned int search(const SearchPattern &, const JSonElement *);
+        virtual unsigned int search(const SearchPattern &, const JSonElement *) =0;
 
         /**
          * Write a message on the last line, using color
@@ -144,16 +140,6 @@ class CurseOutput
         **/
         std::set<const JSonContainer *> collapsed;
 
-        /**
-         * Root item
-        **/
-        JSonElement *data;
-
-        /**
-         * currently searching pattern and its results
-        **/
-        std::list<const JSonElement*> search_result;
-
         /**
          * Program params (ac/av)
         **/
@@ -170,11 +156,6 @@ class CurseOutput
         **/
         bool breakLoop;
 
-        /**
-         * Viewport start
-        **/
-        int scrollTop;
-
         /**
          * initialized colors
         **/
@@ -186,16 +167,6 @@ class CurseOutput
         **/
         bool selectFound, selectIsLast;
 
-        /**
-         * selected item
-        **/
-        const JSonElement *selection;
-
-        /**
-         * prev/next items to be selected on up/down keys
-        **/
-        const JSonElement *select_up, *select_down;
-
         class SelectionOutOfRange { };
 };
 

+ 43 - 4
include/curseSimpleOutput.hh

@@ -21,10 +21,9 @@ class CurseSimpleOutput: public CurseOutput
         **/
         void run(JSonElement *);
 
-        /**
-         * Root item
-        **/
-        JSonElement *data;
+        bool jumpToNextSearch(const JSonElement *current, bool &selectFound);
+        bool jumpToNextSearch();
+        unsigned int search(const SearchPattern &search_pattern, const JSonElement *current);
 
         /**
          * Initialize ncurses
@@ -35,5 +34,45 @@ class CurseSimpleOutput: public CurseOutput
          * Release ncurses
         **/
         void shutdown();
+
+        /**
+         * get flags to be passed to write.
+         * Contains indications on who to write item
+        **/
+        const OutputFlag getFlag(const JSonElement *item) const;
+        const OutputFlag getFlag(const JSonElement *item, const JSonElement *selection) const;
+
+        unsigned int write(const int &x, const int &y, const char item, unsigned int maxWidth, OutputFlag flags);
+        unsigned int write(const int &x, const int &y, const std::string &str, const size_t strlen, unsigned int maxWidth, const OutputFlag flags);
+        bool writeKey(const std::string &key, const size_t keylen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxWidth, OutputFlag, unsigned int extraLen =0);
+        bool writeKey(const std::string &key, const size_t keylen, const std::string &after, size_t afterlen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, OutputFlag flags);
+        bool writeContainer(std::pair<int, int> &, const std::pair<unsigned int, unsigned int> &maxSize, const JSonContainer *);
+        bool writeContent(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, std::list<JSonElement *> * obj);
+        bool redraw(std::pair<int, int> &, const std::pair<unsigned int, unsigned int> &, JSonElement *);
+
+        Optional<bool> evalKey(int k);
+        void checkSelection(const JSonElement *item, const std::pair<int, int> &cursor);
+
+    protected:
+        /**
+         * Root item
+        **/
+        JSonElement *data;
+        const JSonElement *selection;
+
+        /**
+         * Viewport start
+        **/
+        int scrollTop;
+
+        /**
+         * currently searching pattern and its results
+        **/
+        std::list<const JSonElement*> search_result;
+
+        /**
+         * prev/next items to be selected on up/down keys
+        **/
+        const JSonElement *select_up, *select_down;
 };
 

+ 38 - 3
include/curseSplitOutput.hh

@@ -14,7 +14,23 @@ class CurseSplitOutput: public CurseOutput
         **/
         void run(const std::deque<std::string> &, const std::deque<JSonElement *> &);
 
+        void checkSelection(const JSonElement *item, const std::pair<int, int> &cursor);
+        Optional<bool> evalKey(int k);
+
         bool redraw();
+        bool redraw(std::pair<int, int> &, const std::pair<unsigned int, unsigned int> &, JSonElement *);
+
+        bool writeContainer(std::pair<int, int> &, const std::pair<unsigned int, unsigned int> &, const JSonContainer *);
+        bool writeContent(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, std::list<JSonElement*> *_item);
+        bool writeKey(const std::string &key, const size_t keylen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, OutputFlag flags, unsigned int extraLen =0);
+        bool writeKey(const std::string &key, const size_t keylen, const std::string &after, size_t afterlen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, OutputFlag flags);
+        unsigned int write(const int &x, const int &y, const char item, unsigned int maxWidth, OutputFlag flags);
+        unsigned int write(const int &x, const int &y, const std::string &str, const size_t strlen, unsigned int maxWidth, const OutputFlag flags);
+
+        bool jumpToNextSearch(const JSonElement *current, bool &selectFound);
+        bool jumpToNextSearch();
+        unsigned int search(const SearchPattern &search_pattern);
+        unsigned int search(const SearchPattern &search_pattern, const JSonElement *current);
 
         /**
          * get the screen size
@@ -33,11 +49,30 @@ class CurseSplitOutput: public CurseOutput
 
         void destroyAllSubWin();
 
+        /**
+         * get flags to be passed to write.
+         * Contains indications on who to write item
+        **/
+        const OutputFlag getFlag(const JSonElement *item) const;
+        const OutputFlag getFlag(const JSonElement *item, const JSonElement *selection) const;
+
     protected:
-        std::deque<JSonElement *> roots;
-        std::deque<JSonElement *> selections;
         std::deque<std::string> fileNames;
+        std::deque<JSonElement *> roots;
+        std::deque<const JSonElement *> selection, select_up, select_down;
         std::deque<WINDOW *> subwindows;
-        size_t nbInputs, currentWin;
+
+        /**
+         * currently searching pattern and its results
+        **/
+        std::deque<std::list<const JSonElement*> > search_result;
+
+        /**
+         * Viewport start
+        **/
+        std::deque<int> scrollTop;
+
+        WINDOW *currentWin;
+        size_t nbInputs, selectedWin, workingWin;
 };
 

+ 34 - 0
include/fifoMap.hpp

@@ -0,0 +1,34 @@
+#pragma once
+
+#include <stdexcept>
+#include <utility>
+#include <deque>
+
+template <class K, class V>
+class fifoMap: public std::deque<std::pair<K, V>>
+{
+    public:
+        bool contains(const K &) const;
+        V &operator[](const K&);
+};
+
+template<class K, class V>
+bool fifoMap<K, V>::contains(const K &k) const
+{
+    for (std::pair<K, V> i: *this)
+        if (i.first == k)
+            return true;
+    return false;
+}
+
+template<class K, class V>
+V &fifoMap<K, V>::operator[](const K &k)
+{
+    for (std::pair<K, V> &i: *this)
+        if (i.first == k)
+            return i.second;
+    this->push_back(std::pair<K, V>(k, V()));
+    std::pair<K, V> &i = this->back();
+    return i.second;
+}
+

+ 65 - 0
include/optional.hpp

@@ -0,0 +1,65 @@
+#pragma once
+
+#include <stdexcept>
+
+template <typename T>
+class Optional
+{
+    public:
+        static Optional<T> of(T value)
+        {
+            Optional<T> result;
+            result.exists = true;
+            result.value = value;
+            return result;
+        }
+
+        static Optional<T> _empty()
+        {
+            Optional<T> result;
+            result.exists = false;
+            return result;
+        }
+
+        /**
+         * Check whether value exists or not
+        **/
+        bool absent() const
+        {
+            return !exists;
+        }
+
+        T orValue(T value) const
+        {
+            return exists ? this->value : value;
+        }
+
+        /**
+         * return the element, or throw if absent
+         * @throws std::logic_error
+         * @return T value
+        **/
+        T get() const
+        {
+            if (!exists)
+                throw std::logic_error("Optional does not contains value");
+            return value;
+        }
+
+        T operator*() const
+        {
+            return get();
+        }
+
+        static Optional<T> empty;
+
+    protected:
+        T value;
+        bool exists;
+
+    private:
+        Optional() {}
+};
+
+template <typename T> Optional<T> Optional<T>::empty = Optional<T>::_empty();
+

+ 6 - 2
include/params.hh

@@ -9,7 +9,11 @@
 #include <map>
 #include <list>
 #include <string>
+#include <utility>
 #include <istream>
+#include "fifoMap.hpp"
+
+typedef fifoMap<std::string, std::basic_istream<char> *> IndexedDeque;
 
 class AParams
 {
@@ -43,7 +47,7 @@ class Params: public AParams
          * retun input
          * can be file stream (-f), stringstream ( -- INPUT), or std::cin (none)
         **/
-        std::map<std::string, std::basic_istream<char>*> getInputs() const;
+        IndexedDeque getInputs() const;
 
         /**
          * false if invalid argument is passed
@@ -80,7 +84,7 @@ class Params: public AParams
          * input stream
          * can be null for stdin
         **/
-        std::map<std::string, std::basic_istream<char>*> inputs;
+        IndexedDeque inputs;
 
         const std::string progName;
         std::list<std::string> params;

+ 18 - 438
src/curseOutput.cpp

@@ -22,7 +22,7 @@
 
 static CurseOutput *runningInst = nullptr;
 
-CurseOutput::CurseOutput(const Params &p): data(nullptr), params(p)
+CurseOutput::CurseOutput(const Params &p): params(p)
 {
     runningInst = this;
 }
@@ -74,197 +74,42 @@ void _resizeFnc(int signo)
     runningInst->onsig(signo);
 }
 
-bool CurseOutput::redraw(const std::string &errorMsg)
-{
-    bool result = redraw();
-    writeBottomLine(errorMsg, OutputFlag::SPECIAL_ERROR);
-    return result;
-}
-
-bool CurseOutput::redraw(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, JSonElement *item)
-{
-    checkSelection(item, cursor);
-    if (dynamic_cast<const JSonContainer*>(item))
-    {
-        if (!writeContainer(cursor, maxSize, (const JSonContainer *) item))
-            return false;
-    }
-    else
-    {
-        cursor.second += write(cursor.first, cursor.second, item, maxSize.first, getFlag(item));
-        if (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1)
-            return false;
-    }
-    return true;
-}
-
-bool CurseOutput::writeContainer(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, const JSonContainer *item)
+/**
+ * Read input and expect signal
+ * @Return true on:
+ *  - Windows resized
+ *  - Key press and need redraw
+ * false on:
+ *  - exit signal
+**/
+bool CurseOutput::readInput()
 {
-    char childDelimiter[2];
-
-    if (dynamic_cast<const JSonObject *>(item))
-        memcpy(childDelimiter, "{}", sizeof(*childDelimiter) * 2);
-    else
-        memcpy(childDelimiter, "[]", sizeof(*childDelimiter) * 2);
-
-    if (collapsed.find((const JSonContainer *)item) != collapsed.end())
-    {
-        std::string ss;
-        ss.append(&childDelimiter[0], 1).append(" ... ").append(&childDelimiter[1], 1);
-        cursor.second += write(cursor.first, cursor.second, ss, 7, maxSize.first, getFlag(item));
-    }
-    else
+    while (!breakLoop)
     {
-        cursor.second += write(cursor.first, cursor.second, childDelimiter[0], maxSize.first, getFlag(item));
-        if (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1)
-                return false;
-        if (!writeContent(cursor, maxSize, (std::list<JSonElement *> *)item))
-            return false;
-        cursor.second += write(cursor.first, cursor.second, childDelimiter[1], maxSize.first, getFlag(item));
+        Optional<bool> r = evalKey(getch());
+        if (!r.absent())
+            return *r;
     }
-    return (cursor.second - scrollTop < 0 || (unsigned)(cursor.second - scrollTop) <= maxSize.second -1);
+    return false;
 }
 
-bool CurseOutput::writeContent(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, std::list<JSonElement*> *_item)
+bool CurseOutput::redraw(const std::string &errorMsg)
 {
-    JSonContainer *item = (JSonContainer *)_item;
-    bool containerIsObject = (dynamic_cast<JSonObject *>(item) != nullptr);
-    bool result = true;
-    cursor.first += INDENT_LEVEL;
-
-    for (JSonElement *i : *item)
-    {
-        result = false;
-        if (containerIsObject)
-        {
-            JSonObjectEntry *ent = (JSonObjectEntry*) i;
-            bool isContainer = (dynamic_cast<JSonContainer *>(**ent) != nullptr);
-            std::string key = ent->stringify();
-            checkSelection(ent, cursor);
-            if (isContainer && collapsed.find((JSonContainer*)(**ent)) != collapsed.cend())
-            {
-                if (dynamic_cast<JSonObject *>(**ent))
-                {
-                    if (!writeKey(key, ent->lazystrlen(), "{ ... }", cursor, maxSize, getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
-                        break;
-                }
-                else if (!writeKey(key, ent->lazystrlen(), "[ ... ]", cursor, maxSize, getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
-                    break;
-            }
-            else if (!isContainer)
-            {
-                JSonElement *eContent = **ent;
-                if (!writeKey(key, ent->lazystrlen(), eContent->stringify(), eContent->lazystrlen(), cursor, maxSize, getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
-                    break;
-            }
-            else if (((JSonContainer*)(**ent))->size() == 0)
-            {
-                if (dynamic_cast<const JSonObject *>(**ent) )
-                {
-                    if (!writeKey(key, ent->lazystrlen(), "{ }", cursor, maxSize, getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
-                        break;
-                }
-                else if (!writeKey(key, ent->lazystrlen(), "[ ]", cursor, maxSize, getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
-                    break;
-            }
-            else
-            {
-                if (!writeKey(key, ent->lazystrlen(), cursor, maxSize, getFlag(ent)))
-                    break;
-                const JSonElement *saveSelection = selection;
-                if (selection == ent)
-                    selection = **ent;
-                cursor.first += INDENT_LEVEL /2;
-                if (!redraw(cursor, maxSize, **ent))
-                {
-                    selection = saveSelection;
-                    cursor.first -= INDENT_LEVEL /2;
-                    return false;
-                }
-                selection = saveSelection;
-                cursor.first -= INDENT_LEVEL /2;
-            }
-        }
-        else
-        {
-            if (!redraw(cursor, maxSize, i))
-                break;
-        }
-        result = true;
-    }
-    cursor.first -= INDENT_LEVEL;
-    //result will be false if for loop break'd at some time, true otherwise
+    bool result = redraw();
+    writeBottomLine(errorMsg, OutputFlag::SPECIAL_ERROR);
     return result;
 }
 
-bool CurseOutput::writeKey(const std::string &key, const size_t keylen, const std::string &after, size_t afterlen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, OutputFlag flags)
-{
-    if (cursor.second - scrollTop < 0)
-    {
-        cursor.second++;
-        return true;
-    }
-    char oldType = flags.type();
-    flags.type(OutputFlag::TYPE_OBJKEY);
-    write(cursor.first, cursor.second, key, 0, 1, flags);
-    flags.type(OutputFlag::TYPE_OBJ);
-    write(": ", flags);
-    flags.type(oldType);
-    write(after.c_str(), flags);
-    cursor.second += getNbLines(cursor.first +keylen +2 +afterlen, maxSize.first);
-    return (cursor.second - scrollTop < 0 || (unsigned)(cursor.second - scrollTop) <= maxSize.second);
-}
-
 bool CurseOutput::writeKey(const std::string &key, const size_t keylen, const std::string &after, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, OutputFlag flags)
 {
     return writeKey(key, keylen, after, after.size(), cursor, maxSize, flags);
 }
 
-bool CurseOutput::writeKey(const std::string &key, const size_t keylen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, OutputFlag flags, unsigned int extraLen)
-{
-    if (cursor.second - scrollTop < 0)
-    {
-        cursor.second++;
-        return true;
-    }
-    char oldType = flags.type();
-    flags.type(OutputFlag::TYPE_OBJKEY);
-    cursor.second += write(cursor.first, cursor.second, key, keylen, maxSize.first -extraLen -2, flags);
-    flags.type(OutputFlag::TYPE_OBJ);
-    write(": ", flags);
-    flags.type(oldType);
-    return (cursor.second - scrollTop < 0 || (unsigned)(cursor.second - scrollTop) <= maxSize.second);
-}
-
 unsigned int CurseOutput::write(const int &x, const int &y, JSonElement *item, unsigned int maxWidth, OutputFlag flags)
 {
     return write(x, y, item->stringify(), item->lazystrlen(), maxWidth, flags);
 }
 
-unsigned int CurseOutput::write(const int &x, const int &y, const char item, unsigned int maxWidth, OutputFlag flags)
-{
-    int offsetY = y - scrollTop;
-    char color = OutputFlag::SPECIAL_NONE;
-
-    if (offsetY < 0)
-        return 1;
-
-    if (flags.selected())
-        attron(A_REVERSE | A_BOLD);
-    if (flags.searched())
-        color = OutputFlag::SPECIAL_SEARCH;
-    else if (colors.find(flags.type()) != colors.end())
-        color = flags.type();
-
-    if (color != OutputFlag::SPECIAL_NONE)
-        attron(COLOR_PAIR(color));
-    mvprintw(offsetY, x, "%c", item);
-    attroff(A_REVERSE | A_BOLD);
-    if (color != OutputFlag::SPECIAL_NONE)
-        attroff(COLOR_PAIR(color));
-    return getNbLines(x +1, maxWidth);
-}
-
 void CurseOutput::write(const std::string &str, const OutputFlag flags) const
 {
     char color = OutputFlag::SPECIAL_NONE;
@@ -283,16 +128,6 @@ void CurseOutput::write(const std::string &str, const OutputFlag flags) const
         attroff(COLOR_PAIR(color));
 }
 
-unsigned int CurseOutput::write(const int &x, const int &y, const std::string &str, const size_t strlen, unsigned int maxWidth, const OutputFlag flags)
-{
-    int offsetY = y - scrollTop;
-    if (offsetY < 0)
-        return 1;
-    move(offsetY, x);
-    write(str, flags);
-    return getNbLines(strlen +x, maxWidth);
-}
-
 unsigned int CurseOutput::getNbLines(const size_t nbChar, unsigned int maxWidth)
 {
     double nLine = (double) nbChar / maxWidth;
@@ -312,261 +147,6 @@ const std::pair<unsigned int, unsigned int> CurseOutput::getScreenSize() const
     return sc;
 }
 
-const OutputFlag CurseOutput::getFlag(const JSonElement *item) const
-{
-    OutputFlag res;
-    const JSonElement *i = dynamic_cast<const JSonObjectEntry*>(item) ? **((const JSonObjectEntry*)item) : item;
-
-    res.selected(item == selection);
-    res.searched(std::find(search_result.cbegin(), search_result.cend(), item) != search_result.cend());
-    if (dynamic_cast<const JSonPrimitive<std::string> *>(i))
-        res.type(OutputFlag::TYPE_STRING);
-    else if (dynamic_cast<const JSonPrimitive<bool> *>(i))
-        res.type(OutputFlag::TYPE_BOOL);
-    else if (dynamic_cast<const JSonPrimitive<Null> *>(i))
-        res.type(OutputFlag::TYPE_NULL);
-    else if (dynamic_cast<const AJSonPrimitive *>(i))
-        res.type(OutputFlag::TYPE_NUMBER);
-    else if (dynamic_cast<const JSonObject*>(i))
-        res.type(OutputFlag::TYPE_OBJ);
-    else if (dynamic_cast<const JSonArray*>(i))
-        res.type(OutputFlag::TYPE_ARR);
-    return res;
-}
-
-void CurseOutput::checkSelection(const JSonElement *item, const std::pair<int, int> &cursor)
-{
-    if (!selectFound)
-    {
-        if (selection == item)
-        {
-            if (cursor.second < scrollTop) //Selection is above vp, move scroll pos to selection and start drawing
-                scrollTop = cursor.second;
-            selectFound = true;
-        }
-        else if (!item->getParent() || !dynamic_cast<const JSonObjectEntry*>(item->getParent()))
-            select_up = item;
-    }
-    else if (!select_down)
-    {
-        const JSonElement *parent = item->getParent();
-        if (!dynamic_cast<const JSonContainer*>(item) && parent && selection != parent && dynamic_cast<const JSonObjectEntry*>(parent))
-            item = parent;
-        if (!parent || !dynamic_cast<const JSonObjectEntry*>(parent))
-            select_down = item;
-    }
-}
-
-/**
- * Read input and expect signal
- * @Return true on:
- *  - Windows resized
- *  - Key press and need redraw
- * false on:
- *  - exit signal
-**/
-bool CurseOutput::readInput()
-{
-    while (!breakLoop)
-    {
-        int c;
-        c = getch();
-
-        switch (c)
-        {
-            case 'q':
-            case 'Q':
-                return false;
-
-            case KEY_UP:
-            case 'K':
-            case 'k':
-                selection = select_up;
-                return true;
-
-            case KEY_DOWN:
-            case 'j':
-            case 'J':
-                if (selectIsLast)
-                    scrollTop += 2;
-                else if (selection != select_down)
-                    selection = select_down;
-                else
-                    break;
-                return true;
-
-            case KEY_PPAGE:
-            {
-                const JSonElement *brother = selection->findPrev();
-                if (brother == nullptr)
-                {
-                    const JSonElement *parent = selection->getParent();
-                    if (parent && dynamic_cast<const JSonContainer*>(parent))
-                    {
-                        selection = parent;
-                        if (selection->getParent() && dynamic_cast<const JSonObjectEntry*> (selection->getParent()))
-                            selection = selection->getParent();
-                    }
-                    else
-                        break;
-                }
-                else
-                    selection = brother;
-                return true;
-                break;
-            }
-
-            case KEY_NPAGE:
-            {
-                const JSonElement *brother = selection->findNext();
-                if (brother)
-                {
-                    selection = brother;
-                    return true;
-                }
-                break;
-            }
-
-            case 'l':
-            case 'L':
-            case KEY_RIGHT:
-            {
-                const JSonElement *_selection = selection;
-                if (dynamic_cast<const JSonObjectEntry*>(selection))
-                    _selection = **((const JSonObjectEntry*)_selection);
-                if (!dynamic_cast<const JSonContainer*>(_selection))
-                    break;
-
-                if (collapsed.erase((const JSonContainer *)_selection))
-                    return true;
-                if (!((const JSonContainer*)_selection)->size())
-                    break;
-                selection = select_down;
-                return true;
-            }
-
-            case 'h':
-            case 'H':
-            case KEY_LEFT:
-            {
-                const JSonElement *_selection = selection;
-                if (dynamic_cast<const JSonObjectEntry*>(_selection))
-                    _selection = **((const JSonObjectEntry*)_selection);
-                if (selection->getParent() && (!dynamic_cast<const JSonContainer*>(_selection)
-                        || collapsed.find((const JSonContainer *)_selection) != collapsed.end()
-                        || (dynamic_cast<const JSonContainer*>(_selection) && ((const JSonContainer*)_selection)->size() == 0)))
-                {
-                    selection = selection->getParent();
-                    if (selection->getParent() && dynamic_cast<const JSonObjectEntry*>(selection->getParent()))
-                        selection = selection->getParent();
-                }
-                else if (_selection)
-                    collapsed.insert((const JSonContainer *)_selection);
-                else
-                    break;
-                return true;
-            }
-
-            case '/':
-            {
-                const SearchPattern *search_pattern = inputSearch();
-                if (!search_pattern)
-                    return true;
-                search_result.clear();
-                if (search_pattern->isEmpty())
-                    return true;
-                search(*search_pattern, data);
-                delete search_pattern;
-            }
-
-            case 'n':
-            case 'N':
-                if (search_result.empty())
-                    this->redraw("Pattern not found");
-                else if (jumpToNextSearch())
-                    return true;
-                break;
-        }
-    }
-    return false;
-}
-
-unsigned int CurseOutput::search(const SearchPattern &search_pattern, const JSonElement *current)
-{
-    const JSonContainer *container = dynamic_cast<const JSonContainer *> (current);
-    const JSonObjectEntry *objEntry = dynamic_cast<const JSonObjectEntry *> (current);
-    unsigned int result =0;
-
-    if (container)
-    {
-        if (!container->empty())
-            for (const JSonElement *it : *container)
-                result += search(search_pattern, it);
-    }
-    else
-    {
-        if (current && current->match(search_pattern))
-        {
-            if (current->getParent() && dynamic_cast<const JSonObjectEntry*>(current->getParent()))
-            {
-                if (current->getParent() != selection)
-                    search_result.push_back(current->getParent());
-            }
-            else
-                search_result.push_back(current);
-            result++;
-        }
-        if (objEntry)
-            result += search(search_pattern, **objEntry);
-    }
-    result = search_result.size();
-    return result;
-}
-
-bool CurseOutput::jumpToNextSearch(const JSonElement *current, bool &selectFound)
-{
-    const JSonContainer *container = dynamic_cast<const JSonContainer *> (current);
-    const JSonObjectEntry *objEntry = dynamic_cast<const JSonObjectEntry *> (current);
-
-    if (selection == current)
-        selectFound = true;
-    if (container)
-    {
-        if (!container->empty())
-            for (const JSonElement *it : *container)
-                if (jumpToNextSearch(it, selectFound))
-                    return true;
-    }
-    else
-    {
-        if (current && std::find(search_result.cbegin(), search_result.cend(), current) != search_result.cend() && current != selection && selectFound)
-        {
-            selection = current;
-            return true;
-        }
-        if (objEntry)
-            if (jumpToNextSearch(**objEntry, selectFound))
-                return true;
-    }
-    return false;
-}
-
-bool CurseOutput::jumpToNextSearch()
-{
-    bool selectFound = false;
-    bool res = jumpToNextSearch(data, selectFound);
-
-    if (!res)
-    {
-        selection = *(search_result.cbegin());
-        unfold(selection);
-        redraw("Search hit BOTTOM, continuing at TOP");
-        return false;
-    }
-    unfold(selection);
-    return true;
-}
-
 void CurseOutput::unfold(const JSonElement *item)
 {
     while (item->getParent())

+ 431 - 2
src/curseSimpleOutput.cpp

@@ -9,7 +9,10 @@
 #include <signal.h>
 
 #include "curseSimpleOutput.hh"
-#include "jsonContainer.hh"
+#include "searchPattern.hh"
+#include "jsonPrimitive.hh"
+#include "jsonObject.hh"
+#include "jsonArray.hh"
 
 CurseSimpleOutput::CurseSimpleOutput(const Params &p): CurseOutput(p)
 {
@@ -41,7 +44,7 @@ bool CurseSimpleOutput::redraw()
     selectFound = selectIsLast = false;
     clear();
     try {
-        result = CurseOutput::redraw(cursor, screenSize, data);
+        result = redraw(cursor, screenSize, data);
     }
     catch (SelectionOutOfRange &e)
     {
@@ -71,6 +74,432 @@ bool CurseSimpleOutput::redraw()
     return true;
 }
 
+Optional<bool> CurseSimpleOutput::evalKey(int c)
+{
+    switch (c)
+    {
+        case 'q':
+        case 'Q':
+            return Optional<bool>::of(false);
+
+        case KEY_UP:
+        case 'K':
+        case 'k':
+            selection = select_up;
+            return Optional<bool>::of(true);
+
+        case KEY_DOWN:
+        case 'j':
+        case 'J':
+            if (selectIsLast)
+                scrollTop += 2;
+            else if (selection != select_down)
+                selection = select_down;
+            else
+                break;
+            return Optional<bool>::of(true);
+
+        case KEY_PPAGE:
+        {
+            const JSonElement *brother = selection->findPrev();
+
+            if (brother == nullptr)
+            {
+                const JSonElement *parent = selection->getParent();
+                if (parent && dynamic_cast<const JSonContainer*>(parent))
+                {
+                    selection = parent;
+                    if (selection->getParent() && dynamic_cast<const JSonObjectEntry*> (selection->getParent()))
+                        selection = selection->getParent();
+                }
+                else
+                    break;
+            }
+            else
+                selection = brother;
+            return Optional<bool>::of(true);
+        }
+
+        case KEY_NPAGE:
+        {
+            const JSonElement *brother = selection->findNext();
+
+            if (brother)
+            {
+                selection = brother;
+                return Optional<bool>::of(true);
+            }
+            break;
+        }
+
+        case 'l':
+        case 'L':
+        case KEY_RIGHT:
+        {
+            if (dynamic_cast<const JSonObjectEntry*>(selection))
+                selection = **((const JSonObjectEntry*)selection);
+            if (!dynamic_cast<const JSonContainer*>(selection))
+                return Optional<bool>::empty;
+
+            if (collapsed.erase((const JSonContainer *)selection))
+                return Optional<bool>::of(true);
+            if (!((const JSonContainer*)selection)->size())
+                break;
+            selection = select_down;
+            return Optional<bool>::of(true);
+        }
+
+        case 'h':
+        case 'H':
+        case KEY_LEFT:
+        {
+            if (dynamic_cast<const JSonObjectEntry*>(selection))
+                selection = **((const JSonObjectEntry*)selection);
+            if (selection->getParent() && (!dynamic_cast<const JSonContainer*>(selection)
+                    || collapsed.find((const JSonContainer *)selection) != collapsed.end()
+                    || (dynamic_cast<const JSonContainer*>(selection) && ((const JSonContainer*)selection)->size() == 0)))
+            {
+                selection = selection->getParent();
+                if (selection->getParent() && dynamic_cast<const JSonObjectEntry*>(selection->getParent()))
+                    selection = selection->getParent();
+            }
+            else if (selection)
+                collapsed.insert((const JSonContainer *)selection);
+            else
+                break;
+            return Optional<bool>::of(false);
+        }
+
+        case '/':
+        {
+            const SearchPattern *search_pattern = inputSearch();
+            if (!search_pattern)
+                return Optional<bool>::of(true);
+            search_result.clear();
+            if (search_pattern->isEmpty())
+                return Optional<bool>::of(true);
+            search(*search_pattern, data);
+            delete search_pattern;
+        }
+
+        case 'n':
+        case 'N':
+            if (search_result.empty())
+                CurseOutput::redraw("Pattern not found");
+            else if (jumpToNextSearch())
+                return Optional<bool>::of(true);
+            break;
+    }
+    return Optional<bool>::empty;
+}
+
+bool CurseSimpleOutput::redraw(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, JSonElement *item)
+{
+    checkSelection(item, cursor);
+    if (dynamic_cast<const JSonContainer*>(item))
+    {
+        if (!writeContainer(cursor, maxSize, (const JSonContainer *) item))
+            return false;
+    }
+    else
+    {
+        cursor.second += CurseOutput::write(cursor.first, cursor.second, item, maxSize.first, CurseSimpleOutput::getFlag(item));
+        if (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1)
+            return false;
+    }
+    return true;
+}
+
+bool CurseSimpleOutput::writeContainer(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, const JSonContainer *item)
+{
+    char childDelimiter[2];
+
+    if (dynamic_cast<const JSonObject *>(item))
+        memcpy(childDelimiter, "{}", sizeof(*childDelimiter) * 2);
+    else
+        memcpy(childDelimiter, "[]", sizeof(*childDelimiter) * 2);
+
+    if (collapsed.find((const JSonContainer *)item) != collapsed.end())
+    {
+        std::string ss;
+        ss.append(&childDelimiter[0], 1).append(" ... ").append(&childDelimiter[1], 1);
+        cursor.second += write(cursor.first, cursor.second, ss, 7, maxSize.first, CurseSimpleOutput::getFlag(item));
+    }
+    else
+    {
+        cursor.second += write(cursor.first, cursor.second, childDelimiter[0], maxSize.first, CurseSimpleOutput::getFlag(item));
+        if (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1)
+                return false;
+        if (!writeContent(cursor, maxSize, (std::list<JSonElement *> *)item))
+            return false;
+        cursor.second += write(cursor.first, cursor.second, childDelimiter[1], maxSize.first, CurseSimpleOutput::getFlag(item));
+    }
+    return (cursor.second - scrollTop < 0 || (unsigned)(cursor.second - scrollTop) <= maxSize.second -1);
+}
+
+bool CurseSimpleOutput::writeContent(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, std::list<JSonElement*> *_item)
+{
+    JSonContainer *item = (JSonContainer *)_item;
+    bool containerIsObject = (dynamic_cast<JSonObject *>(item) != nullptr);
+    bool result = true;
+    cursor.first += INDENT_LEVEL;
+
+    for (JSonElement *i : *item)
+    {
+        result = false;
+        if (containerIsObject)
+        {
+            JSonObjectEntry *ent = (JSonObjectEntry*) i;
+            bool isContainer = (dynamic_cast<JSonContainer *>(**ent) != nullptr);
+            std::string key = ent->stringify();
+            checkSelection(ent, cursor);
+            if (isContainer && collapsed.find((JSonContainer*)(**ent)) != collapsed.cend())
+            {
+                if (dynamic_cast<JSonObject *>(**ent))
+                {
+                    if (!CurseOutput::writeKey(key, ent->lazystrlen(), "{ ... }", cursor, maxSize, CurseSimpleOutput::getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
+                        break;
+                }
+                else if (!CurseOutput::writeKey(key, ent->lazystrlen(), "[ ... ]", cursor, maxSize, CurseSimpleOutput::getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
+                    break;
+            }
+            else if (!isContainer)
+            {
+                JSonElement *eContent = **ent;
+                if (!writeKey(key, ent->lazystrlen(), eContent->stringify(), eContent->lazystrlen(), cursor, maxSize, CurseSimpleOutput::getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
+                    break;
+            }
+            else if (((JSonContainer*)(**ent))->size() == 0)
+            {
+                if (dynamic_cast<const JSonObject *>(**ent) )
+                {
+                    if (!CurseOutput::writeKey(key, ent->lazystrlen(), "{ }", cursor, maxSize, CurseSimpleOutput::getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
+                        break;
+                }
+                else if (!CurseOutput::writeKey(key, ent->lazystrlen(), "[ ]", cursor, maxSize, CurseSimpleOutput::getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
+                    break;
+            }
+            else
+            {
+                if (!writeKey(key, ent->lazystrlen(), cursor, maxSize, CurseSimpleOutput::getFlag(ent)))
+                    break;
+                const JSonElement *saveSelection = selection;
+                if (selection == ent)
+                    selection = **ent;
+                cursor.first += INDENT_LEVEL /2;
+                if (!redraw(cursor, maxSize, **ent))
+                {
+                    selection = saveSelection;
+                    cursor.first -= INDENT_LEVEL /2;
+                    return false;
+                }
+                selection = saveSelection;
+                cursor.first -= INDENT_LEVEL /2;
+            }
+        }
+        else
+        {
+            if (!redraw(cursor, maxSize, i))
+                break;
+        }
+        result = true;
+    }
+    cursor.first -= INDENT_LEVEL;
+    //result will be false if for loop break'd at some time, true otherwise
+    return result;
+}
+
+bool CurseSimpleOutput::writeKey(const std::string &key, const size_t keylen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, OutputFlag flags, unsigned int extraLen)
+{
+    if (cursor.second - scrollTop < 0)
+    {
+        cursor.second++;
+        return true;
+    }
+    char oldType = flags.type();
+    flags.type(OutputFlag::TYPE_OBJKEY);
+    cursor.second += write(cursor.first, cursor.second, key, keylen, maxSize.first -extraLen -2, flags);
+    flags.type(OutputFlag::TYPE_OBJ);
+    CurseOutput::write(": ", flags);
+    flags.type(oldType);
+    return (cursor.second - scrollTop < 0 || (unsigned)(cursor.second - scrollTop) <= maxSize.second);
+}
+
+bool CurseSimpleOutput::writeKey(const std::string &key, const size_t keylen, const std::string &after, size_t afterlen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, OutputFlag flags)
+{
+    if (cursor.second - scrollTop < 0)
+    {
+        cursor.second++;
+        return true;
+    }
+    char oldType = flags.type();
+    flags.type(OutputFlag::TYPE_OBJKEY);
+    write(cursor.first, cursor.second, key, 0, 1, flags);
+    flags.type(OutputFlag::TYPE_OBJ);
+    CurseOutput::write(": ", flags);
+    flags.type(oldType);
+    CurseOutput::write(after.c_str(), flags);
+    cursor.second += getNbLines(cursor.first +keylen +2 +afterlen, maxSize.first);
+    return (cursor.second - scrollTop < 0 || (unsigned)(cursor.second - scrollTop) <= maxSize.second);
+}
+
+unsigned int CurseSimpleOutput::write(const int &x, const int &y, const char item, unsigned int maxWidth, OutputFlag flags)
+{
+    int offsetY = y - scrollTop;
+    char color = OutputFlag::SPECIAL_NONE;
+
+    if (offsetY < 0)
+        return 1;
+
+    if (flags.selected())
+        attron(A_REVERSE | A_BOLD);
+    if (flags.searched())
+        color = OutputFlag::SPECIAL_SEARCH;
+    else if (colors.find(flags.type()) != colors.end())
+        color = flags.type();
+
+    if (color != OutputFlag::SPECIAL_NONE)
+        attron(COLOR_PAIR(color));
+    mvprintw(offsetY, x, "%c", item);
+    attroff(A_REVERSE | A_BOLD);
+    if (color != OutputFlag::SPECIAL_NONE)
+        attroff(COLOR_PAIR(color));
+    return getNbLines(x +1, maxWidth);
+}
+
+unsigned int CurseSimpleOutput::write(const int &x, const int &y, const std::string &str, const size_t strlen, unsigned int maxWidth, const OutputFlag flags)
+{
+    int offsetY = y - scrollTop;
+    if (offsetY < 0)
+        return 1;
+    move(offsetY, x);
+    CurseOutput::write(str, flags);
+    return getNbLines(strlen +x, maxWidth);
+}
+
+bool CurseSimpleOutput::jumpToNextSearch(const JSonElement *current, bool &selectFound)
+{
+    const JSonContainer *container = dynamic_cast<const JSonContainer *> (current);
+    const JSonObjectEntry *objEntry = dynamic_cast<const JSonObjectEntry *> (current);
+
+    if (selection == current)
+        selectFound = true;
+    if (container)
+    {
+        if (!container->empty())
+            for (const JSonElement *it : *container)
+                if (jumpToNextSearch(it, selectFound))
+                    return true;
+    }
+    else
+    {
+        if (current && std::find(search_result.cbegin(), search_result.cend(), current) != search_result.cend() && current != selection && selectFound)
+        {
+            selection = current;
+            return true;
+        }
+        if (objEntry)
+            if (jumpToNextSearch(**objEntry, selectFound))
+                return true;
+    }
+    return false;
+}
+
+const OutputFlag CurseSimpleOutput::getFlag(const JSonElement *e) const
+{
+    return getFlag(e, selection);
+}
+
+const OutputFlag CurseSimpleOutput::getFlag(const JSonElement *item, const JSonElement *selection) const
+{
+    OutputFlag res;
+    const JSonElement *i = dynamic_cast<const JSonObjectEntry*>(item) ? **((const JSonObjectEntry*)item) : item;
+
+    res.selected(item == selection);
+    res.searched(std::find(search_result.cbegin(), search_result.cend(), item) != search_result.cend());
+    if (dynamic_cast<const JSonPrimitive<std::string> *>(i))
+        res.type(OutputFlag::TYPE_STRING);
+    else if (dynamic_cast<const JSonPrimitive<bool> *>(i))
+        res.type(OutputFlag::TYPE_BOOL);
+    else if (dynamic_cast<const JSonPrimitive<Null> *>(i))
+        res.type(OutputFlag::TYPE_NULL);
+    else if (dynamic_cast<const AJSonPrimitive *>(i))
+        res.type(OutputFlag::TYPE_NUMBER);
+    else if (dynamic_cast<const JSonObject*>(i))
+        res.type(OutputFlag::TYPE_OBJ);
+    else if (dynamic_cast<const JSonArray*>(i))
+        res.type(OutputFlag::TYPE_ARR);
+    return res;
+}
+
+unsigned int CurseSimpleOutput::search(const SearchPattern &search_pattern, const JSonElement *current)
+{
+    const JSonContainer *container = dynamic_cast<const JSonContainer *> (current);
+    const JSonObjectEntry *objEntry = dynamic_cast<const JSonObjectEntry *> (current);
+    unsigned int result =0;
+
+    if (container)
+    {
+        if (!container->empty())
+            for (const JSonElement *it : *container)
+                result += search(search_pattern, it);
+    }
+    else
+    {
+        if (current && current->match(search_pattern))
+        {
+            if (current->getParent() && dynamic_cast<const JSonObjectEntry*>(current->getParent()))
+                search_result.push_back(current->getParent());
+            else
+                search_result.push_back(current);
+            result++;
+        }
+        if (objEntry)
+            result += search(search_pattern, **objEntry);
+    }
+    result = search_result.size();
+    return result;
+}
+
+bool CurseSimpleOutput::jumpToNextSearch()
+{
+    bool selectFound = false;
+    bool res = jumpToNextSearch(data, selectFound);
+
+    if (!res)
+    {
+        selection = *(search_result.cbegin());
+        unfold(selection);
+        CurseOutput::redraw("Search hit BOTTOM, continuing at TOP");
+        return false;
+    }
+    unfold(selection);
+    return true;
+}
+
+void CurseSimpleOutput::checkSelection(const JSonElement *item, const std::pair<int, int> &cursor)
+{
+    if (!selectFound)
+    {
+        if (selection == item)
+        {
+            if (cursor.second < scrollTop) //Selection is above vp, move scroll pos to selection and start drawing
+                scrollTop = cursor.second;
+            selectFound = true;
+        }
+        else if (!item->getParent() || !dynamic_cast<const JSonObjectEntry*>(item->getParent()))
+            select_up = item;
+    }
+    else if (!select_down)
+    {
+        const JSonElement *parent = item->getParent();
+        if (!dynamic_cast<const JSonContainer*>(item) && parent && selection != parent && dynamic_cast<const JSonObjectEntry*>(parent))
+            item = parent;
+        if (!parent || !dynamic_cast<const JSonObjectEntry*>(parent))
+            select_down = item;
+    }
+}
+
 void CurseSimpleOutput::init()
 {
     if (!isatty(fileno(stdin)) || !isatty(fileno(stdout)))

+ 476 - 37
src/curseSplitOutput.cpp

@@ -9,8 +9,11 @@
 #include <signal.h>
 #include <curses.h>
 
+#include "searchPattern.hh"
 #include "curseSplitOutput.hh"
-#include "jsonContainer.hh"
+#include "jsonObject.hh"
+#include "jsonArray.hh"
+#include "jsonPrimitive.hh"
 
 CurseSplitOutput::CurseSplitOutput(const Params &p): CurseOutput(p)
 {
@@ -24,74 +27,483 @@ CurseSplitOutput::~CurseSplitOutput()
 
 void CurseSplitOutput::run(const std::deque<std::string> &inputName, const std::deque<JSonElement*> &roots)
 {
-    selection = data = *roots.begin();
     nbInputs = inputName.size();
-    currentWin = 0;
+    selectedWin = 0;
+    scrollTop.clear();
+    select_up.clear();
+    select_down.clear();
+    selection.clear();
     for (size_t i =0; i < nbInputs; i++)
     {
         this->roots.push_back(roots.at(i));
-        selections.push_back(roots.at(i));
+        scrollTop.push_back(0);
+        selection.push_back(roots.at(i));
+        select_up.push_back(nullptr);
+        select_down.push_back(nullptr);
     }
     fileNames = inputName;
     loop();
 }
 
+Optional<bool> CurseSplitOutput::evalKey(int c)
+{
+    switch (c)
+    {
+        case 'q':
+        case 'Q':
+            return Optional<bool>::of(false);
+
+        case KEY_UP:
+        case 'K':
+        case 'k':
+            selection = select_up;
+            return Optional<bool>::of(true);
+
+        case KEY_DOWN:
+        case 'j':
+        case 'J':
+            if (selectIsLast)
+                scrollTop[selectedWin] += 2;
+            else if (selection != select_down)
+                selection = select_down;
+            else
+                break;
+            return Optional<bool>::of(true);
+
+        case KEY_PPAGE:
+        {
+            const JSonElement *_selection = selection[selectedWin];
+            const JSonElement *brother = _selection->findPrev();
+
+            if (brother == nullptr)
+            {
+                const JSonElement *parent = _selection->getParent();
+                if (parent && dynamic_cast<const JSonContainer*>(parent))
+                {
+                    selection[selectedWin] = _selection = parent;
+                    if (_selection->getParent() && dynamic_cast<const JSonObjectEntry*> (_selection->getParent()))
+                        selection[selectedWin] = _selection->getParent();
+                }
+                else
+                    break;
+            }
+            else
+                selection[selectedWin] = brother;
+            return Optional<bool>::of(true);
+        }
+
+        case KEY_NPAGE:
+        {
+            const JSonElement *brother = selection[selectedWin]->findNext();
+
+            if (brother)
+            {
+                selection[selectedWin] = brother;
+                return Optional<bool>::of(true);
+            }
+            break;
+        }
+
+        case 'l':
+        case 'L':
+        case KEY_RIGHT:
+        {
+            const JSonElement *_selection = selection[selectedWin];
+            if (dynamic_cast<const JSonObjectEntry*>(selection[selectedWin]))
+                _selection = **((const JSonObjectEntry*)_selection);
+            if (!dynamic_cast<const JSonContainer*>(_selection))
+                return Optional<bool>::empty;
+
+            if (collapsed.erase((const JSonContainer *)_selection))
+                return Optional<bool>::of(true);
+            if (!((const JSonContainer*)_selection)->size())
+                break;
+            selection[selectedWin] = select_down[selectedWin];
+            return Optional<bool>::of(true);
+        }
+
+        case 'h':
+        case 'H':
+        case KEY_LEFT:
+        {
+            const JSonElement *_selection = selection[selectedWin];
+            if (dynamic_cast<const JSonObjectEntry*>(_selection))
+                _selection = **((const JSonObjectEntry*)_selection);
+            if (_selection->getParent() && (!dynamic_cast<const JSonContainer*>(_selection)
+                    || collapsed.find((const JSonContainer *)_selection) != collapsed.end()
+                    || (dynamic_cast<const JSonContainer*>(_selection) && ((const JSonContainer*)_selection)->size() == 0)))
+            {
+                selection[selectedWin] = _selection = _selection->getParent();
+                if (_selection->getParent() && dynamic_cast<const JSonObjectEntry*>(_selection->getParent()))
+                    selection[selectedWin] = _selection->getParent();
+            }
+            else if (_selection)
+                collapsed.insert((const JSonContainer *)_selection);
+            else
+                break;
+            return Optional<bool>::of(false);
+        }
+
+        case '/':
+        {
+            const SearchPattern *search_pattern = inputSearch();
+            if (!search_pattern)
+                return Optional<bool>::of(true);
+            search_result.clear();
+            if (search_pattern->isEmpty())
+                return Optional<bool>::of(true);
+            search(*search_pattern);
+            delete search_pattern;
+        }
+
+        case 'n':
+        case 'N':
+            if (search_result.empty())
+                CurseOutput::redraw("Pattern not found");
+            else if (jumpToNextSearch())
+                return Optional<bool>::of(true);
+            break;
+    }
+    return Optional<bool>::empty;
+}
+
+void CurseSplitOutput::checkSelection(const JSonElement *item, const std::pair<int, int> &cursor)
+{
+    if (!selectFound)
+    {
+        if (selection[workingWin] == item)
+        {
+            if (cursor.second < scrollTop[workingWin]) //Selection is above vp, move scroll pos to selection and start drawing
+                scrollTop[workingWin] = cursor.second;
+            selectFound = true;
+        }
+        else if (!item->getParent() || !dynamic_cast<const JSonObjectEntry*>(item->getParent()))
+            select_up[workingWin] = item;
+    }
+    else if (!select_down[workingWin])
+    {
+        const JSonElement *parent = item->getParent();
+        if (!dynamic_cast<const JSonContainer*>(item) && parent && selection[workingWin] != parent && dynamic_cast<const JSonObjectEntry*>(parent))
+            item = parent;
+        if (!parent || !dynamic_cast<const JSonObjectEntry*>(parent))
+            select_down[workingWin] = item;
+    }
+}
+
+bool CurseSplitOutput::jumpToNextSearch(const JSonElement *current, bool &selectFound)
+{
+    const JSonContainer *container = dynamic_cast<const JSonContainer *> (current);
+    const JSonObjectEntry *objEntry = dynamic_cast<const JSonObjectEntry *> (current);
+
+    if (selection[selectedWin] == current)
+        selectFound = true;
+    if (container)
+    {
+        if (!container->empty())
+            for (const JSonElement *it : *container)
+                if (jumpToNextSearch(it, selectFound))
+                    return true;
+    }
+    else
+    {
+        if (current && std::find(search_result[selectedWin].cbegin(), search_result[selectedWin].cend(), current) != search_result[selectedWin].cend() && current != selection[selectedWin] && selectFound)
+        {
+            selection[selectedWin] = current;
+            return true;
+        }
+        if (objEntry)
+            if (jumpToNextSearch(**objEntry, selectFound))
+                return true;
+    }
+    return false;
+}
+
+unsigned int CurseSplitOutput::search(const SearchPattern &search_pattern)
+{
+    unsigned int result =0;
+
+    for (workingWin =0; workingWin < nbInputs; ++workingWin)
+        result += search(search_pattern, roots[workingWin]);
+    return result;
+}
+
+unsigned int CurseSplitOutput::search(const SearchPattern &search_pattern, const JSonElement *current)
+{
+    const JSonContainer *container = dynamic_cast<const JSonContainer *> (current);
+    const JSonObjectEntry *objEntry = dynamic_cast<const JSonObjectEntry *> (current);
+
+    if (container)
+    {
+        if (!container->empty())
+            for (const JSonElement *it : *container)
+                search(search_pattern, it);
+    }
+    else
+    {
+        if (current && current->match(search_pattern))
+        {
+            if (current->getParent() && dynamic_cast<const JSonObjectEntry*>(current->getParent()))
+                search_result[workingWin].push_back(current->getParent());
+            else
+                search_result[workingWin].push_back(current);
+        }
+        if (objEntry)
+            search(search_pattern, **objEntry);
+    }
+    return search_result.size();
+}
+
+bool CurseSplitOutput::jumpToNextSearch()
+{
+    bool selectFound = false;
+    bool res = jumpToNextSearch(roots[selectedWin], selectFound);
+
+    if (!res)
+    {
+        selection[selectedWin] = *(search_result[selectedWin].cbegin());
+        unfold(selection[selectedWin]);
+        CurseOutput::redraw("Search hit BOTTOM, continuing at TOP");
+        return false;
+    }
+    unfold(selection[selectedWin]);
+    return true;
+}
+
 bool CurseSplitOutput::redraw()
 {
     const std::pair<unsigned int, unsigned int> screenSize = getScreenSize();
 
     destroyAllSubWin();
-    for (size_t i=0; i < nbInputs; i++)
+    clear();
+    for (workingWin =0; workingWin < nbInputs; workingWin++)
     {
-        WINDOW *currentWin = newwin(screenSize.second, screenSize.first, 0, i * screenSize.first - (i ? 1 : 0));
+        std::pair<int, int> cursor(workingWin * screenSize.first + (workingWin ? 0 : 1), 0);
+        bool result;
+
+        currentWin = newwin(screenSize.second, screenSize.first, 0, cursor.first -1);
         box(currentWin, 0, 0);
-        redraw(currentWin, screenSize); // TODO
+        select_up[workingWin] = select_down[workingWin] = nullptr;
+        selectFound = selectIsLast = false;
+
+        try {
+            result = redraw(cursor, screenSize, roots[workingWin]);
+        }
+        catch (SelectionOutOfRange &e)
+        {
+            return false;
+        }
+        if (!result && !selectFound)
+        {
+            scrollTop[workingWin]++;
+            return false;
+        }
+        if (!result && !select_down[workingWin])
+            selectIsLast = true;
+        if (!select_down[workingWin])
+        {
+            const JSonContainer *pselect = dynamic_cast<const JSonContainer*>(selection[workingWin]);
+            if (pselect && !pselect->empty())
+                select_down[workingWin] = *(pselect->cbegin());
+            else
+            {
+                const JSonElement *next = selection[workingWin]->findNext();
+                select_down[workingWin] = next ? next : selection[workingWin];
+            }
+        }
+        if (!select_up[workingWin])
+            select_up[workingWin] = selection[workingWin];
         wrefresh(currentWin);
     }
     refresh();
-    return false;
+    return true;
+}
 
-    // TODO
-    std::pair<int, int> cursor(0, 0);
-    /**
-     * Will be true if the json's last item is visible
-    **/
-    bool result;
+bool CurseSplitOutput::writeContainer(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, const JSonContainer *item)
+{
+    char childDelimiter[2];
+    const int scrollTop = this->scrollTop[workingWin];
 
-    select_up = select_down = nullptr;
-    selectFound = selectIsLast = false;
-    clear();
-    try {
-        result = CurseOutput::redraw(cursor, screenSize, data);
-    }
-    catch (SelectionOutOfRange &e)
+    if (dynamic_cast<const JSonObject *>(item))
+        memcpy(childDelimiter, "{}", sizeof(*childDelimiter) * 2);
+    else
+        memcpy(childDelimiter, "[]", sizeof(*childDelimiter) * 2);
+
+    if (collapsed.find((const JSonContainer *)item) != collapsed.end())
     {
-        return false;
+        std::string ss;
+        ss.append(&childDelimiter[0], 1).append(" ... ").append(&childDelimiter[1], 1);
+        cursor.second += write(cursor.first, cursor.second, ss, 7, maxSize.first, CurseSplitOutput::getFlag(item));
     }
-    if (!result && !selectFound)
+    else
     {
-        scrollTop++;
-        return false;
+        cursor.second += write(cursor.first, cursor.second, childDelimiter[0], maxSize.first, CurseSplitOutput::getFlag(item));
+        if (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1)
+                return false;
+        if (!writeContent(cursor, maxSize, (std::list<JSonElement *> *)item))
+            return false;
+        cursor.second += write(cursor.first, cursor.second, childDelimiter[1], maxSize.first, CurseSplitOutput::getFlag(item));
     }
-    if (!result && !select_down)
-        selectIsLast = true;
-    if (!select_down)
+    return (cursor.second - scrollTop < 0 || (unsigned)(cursor.second - scrollTop) <= maxSize.second -1);
+}
+
+bool CurseSplitOutput::writeContent(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, std::list<JSonElement*> *_item)
+{
+    JSonContainer *item = (JSonContainer *)_item;
+    bool containerIsObject = (dynamic_cast<JSonObject *>(item) != nullptr);
+    bool result = true;
+    cursor.first += INDENT_LEVEL;
+    const int scrollTop = this->scrollTop[workingWin];
+
+    for (JSonElement *i : *item)
     {
-        const JSonContainer *pselect = dynamic_cast<const JSonContainer*>(selection);
-        if (pselect && !pselect->empty())
-            select_down = *(pselect->cbegin());
+        result = false;
+        if (containerIsObject)
+        {
+            JSonObjectEntry *ent = (JSonObjectEntry*) i;
+            bool isContainer = (dynamic_cast<JSonContainer *>(**ent) != nullptr);
+            std::string key = ent->stringify();
+            checkSelection(ent, cursor);
+            if (isContainer && collapsed.find((JSonContainer*)(**ent)) != collapsed.cend())
+            {
+                if (dynamic_cast<JSonObject *>(**ent))
+                {
+                    if (!CurseOutput::writeKey(key, ent->lazystrlen(), "{ ... }", cursor, maxSize, CurseSplitOutput::getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
+                        break;
+                }
+                else if (!CurseOutput::writeKey(key, ent->lazystrlen(), "[ ... ]", cursor, maxSize, CurseSplitOutput::getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
+                    break;
+            }
+            else if (!isContainer)
+            {
+                JSonElement *eContent = **ent;
+                if (!writeKey(key, ent->lazystrlen(), eContent->stringify(), eContent->lazystrlen(), cursor, maxSize, CurseSplitOutput::getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
+                    break;
+            }
+            else if (((JSonContainer*)(**ent))->size() == 0)
+            {
+                if (dynamic_cast<const JSonObject *>(**ent) )
+                {
+                    if (!CurseOutput::writeKey(key, ent->lazystrlen(), "{ }", cursor, maxSize, CurseSplitOutput::getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
+                        break;
+                }
+                else if (!CurseOutput::writeKey(key, ent->lazystrlen(), "[ ]", cursor, maxSize, CurseSplitOutput::getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
+                    break;
+            }
+            else
+            {
+                if (!writeKey(key, ent->lazystrlen(), cursor, maxSize, getFlag(ent)))
+                    break;
+                const JSonElement *saveSelection = selection[workingWin];
+                if (selection[workingWin] == ent)
+                    selection[workingWin] = **ent;
+                cursor.first += INDENT_LEVEL /2;
+                if (!redraw(cursor, maxSize, **ent))
+                {
+                    selection[workingWin] = saveSelection;
+                    cursor.first -= INDENT_LEVEL /2;
+                    return false;
+                }
+                selection[workingWin] = saveSelection;
+                cursor.first -= INDENT_LEVEL /2;
+            }
+        }
         else
         {
-            const JSonElement *next = selection->findNext();
-            select_down = next ? next : selection;
+            if (!redraw(cursor, maxSize, i))
+                break;
         }
+        result = true;
+    }
+    cursor.first -= INDENT_LEVEL;
+    //result will be false if for loop break'd at some time, true otherwise
+    return result;
+}
+
+bool CurseSplitOutput::writeKey(const std::string &key, const size_t keylen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, OutputFlag flags, unsigned int extraLen)
+{
+    if (cursor.second - scrollTop[workingWin] < 0)
+    {
+        cursor.second++;
+        return true;
+    }
+    char oldType = flags.type();
+    flags.type(OutputFlag::TYPE_OBJKEY);
+    cursor.second += write(cursor.first, cursor.second, key, keylen, maxSize.first -extraLen -2, flags);
+    flags.type(OutputFlag::TYPE_OBJ);
+    CurseOutput::write(": ", flags);
+    flags.type(oldType);
+    return (cursor.second - scrollTop[workingWin] < 0 || (unsigned)(cursor.second - scrollTop[workingWin]) <= maxSize.second);
+}
+
+bool CurseSplitOutput::writeKey(const std::string &key, const size_t keylen, const std::string &after, size_t afterlen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, OutputFlag flags)
+{
+    if (cursor.second - scrollTop[workingWin] < 0)
+    {
+        cursor.second++;
+        return true;
+    }
+    char oldType = flags.type();
+    flags.type(OutputFlag::TYPE_OBJKEY);
+    write(cursor.first, cursor.second, key, 0, 1, flags);
+    flags.type(OutputFlag::TYPE_OBJ);
+    CurseOutput::write(": ", flags);
+    flags.type(oldType);
+    CurseOutput::write(after.c_str(), flags);
+    cursor.second += getNbLines(cursor.first +keylen +2 +afterlen, maxSize.first);
+    return (cursor.second - scrollTop[workingWin] < 0 || (unsigned)(cursor.second - scrollTop[workingWin]) <= maxSize.second);
+}
+
+bool CurseSplitOutput::redraw(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, JSonElement *item)
+{
+    checkSelection(item, cursor);
+    if (dynamic_cast<const JSonContainer*>(item))
+    {
+        if (!writeContainer(cursor, maxSize, (const JSonContainer *) item))
+            return false;
+    }
+    else
+    {
+        cursor.second += CurseOutput::write(cursor.first, cursor.second, item, maxSize.first, CurseSplitOutput::getFlag(item));
+        if (cursor.second - scrollTop[workingWin] > 0 && (unsigned)(cursor.second - scrollTop[workingWin]) > maxSize.second -1)
+            return false;
     }
-    if (!select_up)
-        select_up = selection;
-    refresh();
     return true;
 }
 
+unsigned int CurseSplitOutput::write(const int &x, const int &y, const char item, unsigned int maxWidth, OutputFlag flags)
+{
+    int offsetY = y - scrollTop[workingWin];
+    char color = OutputFlag::SPECIAL_NONE;
+
+    if (offsetY < 0)
+        return 1;
+
+    if (flags.selected())
+        attron(A_REVERSE | A_BOLD);
+    if (flags.searched())
+        color = OutputFlag::SPECIAL_SEARCH;
+    else if (colors.find(flags.type()) != colors.end())
+        color = flags.type();
+
+    if (color != OutputFlag::SPECIAL_NONE)
+        attron(COLOR_PAIR(color));
+    mvprintw(offsetY, x, "%c", item);
+    attroff(A_REVERSE | A_BOLD);
+    if (color != OutputFlag::SPECIAL_NONE)
+        attroff(COLOR_PAIR(color));
+    return getNbLines(x +1, maxWidth);
+}
+
+unsigned int CurseSplitOutput::write(const int &x, const int &y, const std::string &str, const size_t strlen, unsigned int maxWidth, const OutputFlag flags)
+{
+    int offsetY = y - scrollTop[workingWin];
+    if (offsetY < 0)
+        return 1;
+    move(offsetY, x);
+    CurseOutput::write(str, flags);
+    return getNbLines(strlen +x, maxWidth);
+}
+
 void CurseSplitOutput::destroyAllSubWin()
 {
     for (WINDOW *i: subwindows)
@@ -151,7 +563,7 @@ void CurseSplitOutput::init()
     signal(SIGINT, _resizeFnc);
     signal(SIGTERM, _resizeFnc);
     signal(SIGKILL, _resizeFnc);
-    scrollTop = 0;
+    scrollTop[selectedWin] = 0;
 }
 
 void CurseSplitOutput::shutdown()
@@ -166,3 +578,30 @@ void CurseSplitOutput::shutdown()
     screen = nullptr;
 }
 
+const OutputFlag CurseSplitOutput::getFlag(const JSonElement *e) const
+{
+    return getFlag(e, selection[workingWin]);
+}
+
+const OutputFlag CurseSplitOutput::getFlag(const JSonElement *item, const JSonElement *selection) const
+{
+    OutputFlag res;
+    const JSonElement *i = dynamic_cast<const JSonObjectEntry*>(item) ? **((const JSonObjectEntry*)item) : item;
+
+    res.selected(item == selection);
+    res.searched(std::find(search_result[selectedWin].cbegin(), search_result[selectedWin].cend(), item) != search_result[selectedWin].cend());
+    if (dynamic_cast<const JSonPrimitive<std::string> *>(i))
+        res.type(OutputFlag::TYPE_STRING);
+    else if (dynamic_cast<const JSonPrimitive<bool> *>(i))
+        res.type(OutputFlag::TYPE_BOOL);
+    else if (dynamic_cast<const JSonPrimitive<Null> *>(i))
+        res.type(OutputFlag::TYPE_NULL);
+    else if (dynamic_cast<const AJSonPrimitive *>(i))
+        res.type(OutputFlag::TYPE_NUMBER);
+    else if (dynamic_cast<const JSonObject*>(i))
+        res.type(OutputFlag::TYPE_OBJ);
+    else if (dynamic_cast<const JSonArray*>(i))
+        res.type(OutputFlag::TYPE_ARR);
+    return res;
+}
+

+ 2 - 2
src/main.cpp

@@ -33,7 +33,7 @@ StreamConsumer *readFile(std::pair<std::string, std::basic_istream<char>*> input
 
 void runDiff(const Params &params)
 {
-    const std::map<std::string, std::basic_istream<char>*> inputs = params.getInputs();
+    const IndexedDeque inputs = params.getInputs();
     const size_t nbInputs = inputs.size();
     std::set<StreamConsumer *> streams;
     std::deque<JSonElement *> roots;
@@ -88,7 +88,7 @@ void runDiff(const Params &params)
 
 void run(const Params &params)
 {
-    std::map<std::string, std::basic_istream<char>*> inputs = params.getInputs();
+    IndexedDeque inputs = params.getInputs();
     CurseSimpleOutput *out = new CurseSimpleOutput(params);
     std::list<Warning> warns;
 

+ 3 - 3
src/params.cpp

@@ -81,7 +81,7 @@ bool Params::read()
                     delete in;
                     throw std::runtime_error("Cannot open " +tmp +" for reading");
                 }
-                if (inputs.find(tmp) != inputs.cend())
+                if (inputs.contains(tmp))
                 {
                     delete in;
                     throw std::runtime_error("Cannot compare " +tmp +" and " +tmp +" files (path are identical)");
@@ -117,11 +117,11 @@ bool Params::read()
     return true;
 }
 
-std::map<std::string, std::basic_istream<char>*> Params::getInputs() const
+IndexedDeque Params::getInputs() const
 {
     if (!inputs.empty())
         return inputs;
-    std::map<std::string, std::basic_istream<char>*> result;
+    IndexedDeque result;
     result["STDIN"] = &std::cin;
     return result;
 }

+ 30 - 0
test/src/optional.cpp

@@ -0,0 +1,30 @@
+#include <iostream>
+#include "optional.hpp"
+
+#define FAILED(got, op, expt) {std::cout << __FILE__ << ":" << __LINE__ << ": failed asserting " << got << " " << op << " expected " << expt << std::endl; return false; }
+
+bool simpleTest()
+{
+    Optional<int> test = Optional<int>::of(42);
+
+    if (test.absent())
+        FAILED(test.absent(), "!=", true);
+    if (test.get() != 42)
+        FAILED(test.get(), "!=", 42);
+    if (test.orValue(1) != 42)
+        FAILED(test.get(), "!=", 42);
+    test = Optional<int>::empty;
+    if (!test.absent())
+        FAILED(test.absent(), "!=", false);
+    if (test.orValue(0) != 0)
+        FAILED(test.get(), "!=", 0);
+    return true;
+}
+
+int main()
+{
+    if (!simpleTest())
+        exit(EXIT_FAILURE);
+    exit(EXIT_SUCCESS);
+}
+