From fec7abbc9bafc1a89d05dee72f2efb47695215d4 Mon Sep 17 00:00:00 2001 From: Eberhard Graether Date: Thu, 11 Sep 2014 14:53:03 +0200 Subject: [PATCH] ui: integrated autocompletion and filtering into SearchView - QtSearchView was split into QtSearchView and QtSearchBox. - QtSearchBox contains all Qt elements and can use them purely - QtSearchView forwards calls from the SearchController to QtSearchBox - Searching is initiated with MessageSearch to the SearchController - Autocompletion is initiated with MessageSearchAutocomplete to the SearchController - The search field is able to create filter queries by only giving autocompletions for the last token in the query - For named tokens the search field adds their token ids to the query in the form of "A,25" for faster lookup bug id = #21 --- bin/test/data/log/test_log.txt | 41 +++-- src/app/CMakeLists.txt | 6 +- src/app/qt/element/QtButton.cpp | 27 ---- src/app/qt/element/QtButton.h | 25 --- src/app/qt/element/QtEditBox.cpp | 43 ----- src/app/qt/element/QtEditBox.h | 28 ---- src/app/qt/element/QtSearchBox.cpp | 147 ++++++++++++++++++ src/app/qt/element/QtSearchBox.h | 48 ++++++ src/app/qt/view/QtSearchView.cpp | 82 ++-------- src/app/qt/view/QtSearchView.h | 22 +-- src/lib/CMakeLists.txt | 1 + .../component/controller/SearchController.cpp | 56 ++++--- .../component/controller/SearchController.h | 13 +- src/lib/component/view/SearchView.h | 3 +- src/lib/component/view/View.h | 1 + src/lib/data/SearchIndex.cpp | 23 ++- src/lib/data/SearchIndex.h | 5 +- src/lib/data/Storage.cpp | 14 +- src/lib/data/Storage.h | 2 +- src/lib/data/access/GraphAccess.h | 3 +- src/lib/data/access/GraphAccessProxy.cpp | 6 +- src/lib/data/access/GraphAccessProxy.h | 2 +- src/lib/data/graph/FilterableGraph.cpp | 10 ++ src/lib/data/graph/FilterableGraph.h | 7 + src/lib/data/graph/Graph.cpp | 24 +-- src/lib/data/graph/Graph.h | 7 +- src/lib/data/graph/SubGraph.cpp | 20 +++ src/lib/data/graph/SubGraph.h | 4 +- .../graph/filter/GraphFilterConductor.cpp | 2 +- .../graph/filter/GraphFilterImplementations.h | 32 +++- src/lib/data/query/QueryCommand.cpp | 2 +- src/lib/data/query/QueryCommand.h | 2 +- src/lib/data/query/QueryNode.cpp | 7 +- src/lib/data/query/QueryNode.h | 3 +- src/lib/data/query/QueryOperator.cpp | 7 +- src/lib/data/query/QueryOperator.h | 4 +- src/lib/data/query/QueryToken.cpp | 44 +++++- src/lib/data/query/QueryToken.h | 14 +- src/lib/data/query/QueryTree.cpp | 75 ++++++--- src/lib/data/query/QueryTree.h | 7 +- .../utility/messaging/type/MessageSearch.h | 23 +++ .../type/MessageSearchAutocomplete.h | 23 +++ src/lib/utility/utilityString.h | 31 ++++ src/test/GraphFilterConductorTestSuite.h | 76 ++++++++- src/test/GraphFilterTestSuite.h | 2 +- src/test/QueryTreeTestSuite.h | 71 +++++---- src/test/SearchIndexTestSuite.h | 42 ++--- src/test/UtilityStringTestSuite.h | 40 +++++ 48 files changed, 786 insertions(+), 391 deletions(-) delete mode 100644 src/app/qt/element/QtButton.cpp delete mode 100644 src/app/qt/element/QtButton.h delete mode 100644 src/app/qt/element/QtEditBox.cpp delete mode 100644 src/app/qt/element/QtEditBox.h create mode 100644 src/app/qt/element/QtSearchBox.cpp create mode 100644 src/app/qt/element/QtSearchBox.h create mode 100644 src/lib/utility/messaging/type/MessageSearch.h create mode 100644 src/lib/utility/messaging/type/MessageSearchAutocomplete.h diff --git a/bin/test/data/log/test_log.txt b/bin/test/data/log/test_log.txt index 90208c2a..c6e80470 100644 --- a/bin/test/data/log/test_log.txt +++ b/bin/test/data/log/test_log.txt @@ -20,18 +20,35 @@ Storage.cpp INFO: call: main -> A::getCount Storage.cpp INFO: class: A Storage.cpp INFO: method: A::A Storage.cpp INFO: global usage: A::A -> A::count -Storage.cpp INFO: method: A::getCount -Storage.cpp INFO: global usage: A::getCount -> A::count -Storage.cpp INFO: method: A::process -Storage.cpp INFO: field: A::count -Storage.cpp INFO: class: B -Storage.cpp INFO: inheritance: B : A -Storage.cpp INFO: method: B::process -Storage.cpp INFO: type usage: B::process -> int -Storage.cpp INFO: function: main -Storage.cpp INFO: type usage: main -> B -Storage.cpp INFO: call: main -> B::B -Storage.cpp INFO: call: main -> A::getCount +Storage.cpp INFO: method: A::A +Storage.cpp INFO: global usage: A::A -> A::count +Storage.cpp INFO: method: A::getCount +Storage.cpp INFO: global usage: A::getCount -> A::count +Storage.cpp INFO: method: A::process +Storage.cpp INFO: field: A::count +Storage.cpp INFO: class: B +Storage.cpp INFO: inheritance: B : A +Storage.cpp INFO: method: B::process +Storage.cpp INFO: type usage: B::process -> int +Storage.cpp INFO: function: main +Storage.cpp INFO: type usage: main -> B +Storage.cpp INFO: call: main -> B::B +Storage.cpp INFO: call: main -> A::getCount +SearchIndex.cpp INFO: +1 matches for "main": +474 main + ^^^^ + +SearchIndex.cpp INFO: +1 matches for "main": +474 main + ^^^^ + +SearchIndex.cpp INFO: +1 matches for "A::A": +237 A::A + ^^^^ + Settings.cpp WARNING: File for Settings not found. ConfigManager.cpp ERROR: value Bool is not present in config. ConfigManager.cpp ERROR: value Int is not present in config. diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 84126f80..fe350b8c 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -9,14 +9,12 @@ add_files( qt/element/QtCodeFile.h qt/element/QtCodeFileList.cpp qt/element/QtCodeFileList.h - qt/element/QtButton.cpp - qt/element/QtButton.h qt/element/QtCodeSnippet.cpp qt/element/QtCodeSnippet.h - qt/element/QtEditBox.cpp - qt/element/QtEditBox.h qt/element/QtMainWindow.cpp qt/element/QtMainWindow.h + qt/element/QtSearchBox.cpp + qt/element/QtSearchBox.h qt/utility/QtHighLighter.cpp qt/utility/QtHighLighter.h diff --git a/src/app/qt/element/QtButton.cpp b/src/app/qt/element/QtButton.cpp deleted file mode 100644 index b07d0e9d..00000000 --- a/src/app/qt/element/QtButton.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "qt/element/QtButton.h" - -QtButton::QtButton(QWidget *parent) - : QPushButton(parent) - , m_onClick(nullptr) -{ - setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac - - connect(this, SIGNAL(clicked()), this, SLOT(slotOnClick())); -} - -QtButton::~QtButton() -{ -} - -void QtButton::setCallbackOnClick(std::function callback) -{ - m_onClick = callback; -} - -void QtButton::slotOnClick() -{ - if (m_onClick) - { - m_onClick(); - } -} diff --git a/src/app/qt/element/QtButton.h b/src/app/qt/element/QtButton.h deleted file mode 100644 index a8e825f3..00000000 --- a/src/app/qt/element/QtButton.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef QT_BUTTON_H -#define QT_BUTTON_H - -#include - -#include - -class QtButton: public QPushButton -{ - Q_OBJECT - -public: - QtButton(QWidget *parent); - ~QtButton(); - - void setCallbackOnClick(std::function callback); - -private slots: - void slotOnClick(); - -private: - std::function m_onClick; -}; - -#endif // QT_BUTTON_H diff --git a/src/app/qt/element/QtEditBox.cpp b/src/app/qt/element/QtEditBox.cpp deleted file mode 100644 index 9e945747..00000000 --- a/src/app/qt/element/QtEditBox.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "qt/element/QtEditBox.h" -#include "utility/logging/logging.h" - -QtEditBox::QtEditBox(QWidget *parent) - : QLineEdit(parent) - , m_onReturnPressed(nullptr) - , m_onTextEdited(nullptr) -{ - setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac - - connect(this, SIGNAL(returnPressed()), this, SLOT(slotOnReturnPressed())); - connect(this, SIGNAL(textEdited(const QString&)), this, SLOT(slotOnTextEdited(const QString&))); -} - -QtEditBox::~QtEditBox() -{ -} - -void QtEditBox::setCallbackOnReturnPressed(std::function callback) -{ - m_onReturnPressed = callback; -} - -void QtEditBox::setCallbackOnTextEdited(std::function callback) -{ - m_onTextEdited = callback; -} - -void QtEditBox::slotOnReturnPressed() -{ - if (m_onReturnPressed) - { - m_onReturnPressed(); - } -} - -void QtEditBox::slotOnTextEdited(const QString& text) -{ - if (m_onTextEdited) - { - m_onTextEdited(text.toStdString()); - } -} diff --git a/src/app/qt/element/QtEditBox.h b/src/app/qt/element/QtEditBox.h deleted file mode 100644 index 744db97a..00000000 --- a/src/app/qt/element/QtEditBox.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef QT_EDIT_BOX_H -#define QT_EDIT_BOX_H - -#include - -#include - -class QtEditBox: public QLineEdit -{ - Q_OBJECT - -public: - QtEditBox(QWidget *parent); - ~QtEditBox(); - - void setCallbackOnReturnPressed(std::function callback); - void setCallbackOnTextEdited(std::function callback); - -private slots: - void slotOnReturnPressed(); - void slotOnTextEdited(const QString& text); - -private: - std::function m_onReturnPressed; - std::function m_onTextEdited; -}; - -#endif // QT_EDIT_BOX_H diff --git a/src/app/qt/element/QtSearchBox.cpp b/src/app/qt/element/QtSearchBox.cpp new file mode 100644 index 00000000..129bcdae --- /dev/null +++ b/src/app/qt/element/QtSearchBox.cpp @@ -0,0 +1,147 @@ +#include "qt/element/QtSearchBox.h" + +#include +#include +#include +#include + +#include "data/query/QueryTree.h" +#include "utility/messaging/type/MessageSearch.h" +#include "utility/messaging/type/MessageSearchAutocomplete.h" +#include "utility/utilityString.h" + +QtSearchBox::QtSearchBox() + : m_preventQueryChange(false) +{ + setObjectName("search_view"); + + QBoxLayout* layout = new QHBoxLayout(); + layout->setSpacing(0); + layout->setAlignment(Qt::AlignTop); + setLayout(layout); + + m_searchButton = new QPushButton(this); + m_searchButton->setObjectName("search_button"); + m_searchButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac + connect(m_searchButton, SIGNAL(clicked()), this, SLOT(onSearchButtonClick())); + layout->addWidget(m_searchButton); + + m_searchBox = new QLineEdit(this); + m_searchBox->setObjectName("search_box"); + m_searchBox->setPlaceholderText("Please enter your search string."); + m_searchBox->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac + connect(m_searchBox, SIGNAL(returnPressed()), this, SLOT(onSearchButtonClick())); + connect(m_searchBox, SIGNAL(textEdited(const QString&)), this, SLOT(onSearchQueryEdited(const QString&))); + connect(m_searchBox, SIGNAL(textChanged(const QString&)), this, SLOT(onSearchQueryChanged(const QString&))); + layout->addWidget(m_searchBox); + + m_caseSensitiveButton = new QPushButton(this); + m_caseSensitiveButton->setObjectName("case_sensitive_button"); + m_caseSensitiveButton->setCheckable(true); + m_caseSensitiveButton->setToolTip("case sensitive"); + m_caseSensitiveButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac + layout->addWidget(m_caseSensitiveButton); +} + +QtSearchBox::~QtSearchBox() +{ +} + +void QtSearchBox::setText(const std::string& text) +{ + if (m_searchBox->text() != text.c_str()) + { + m_searchBox->setText(text.c_str()); + } +} + +void QtSearchBox::setFocus() +{ + m_searchBox->setFocus(Qt::ShortcutFocusReason); +} + +void QtSearchBox::setAutocompletionList(const std::vector& autocompletionList) +{ + m_matches = autocompletionList; + + QStringList wordList; + for (const SearchIndex::SearchMatch& match: autocompletionList) + { + wordList << match.fullName.c_str(); + } + + QCompleter *completer = new QCompleter(wordList, m_searchBox); + completer->popup()->setObjectName("search_box_popup"); + completer->setCaseSensitivity(Qt::CaseInsensitive); + m_searchBox->setCompleter(completer); + completer->complete(); + + connect(completer, SIGNAL(highlighted(const QModelIndex&)), this, SLOT(onSearchCompletionHighlighted(const QModelIndex&))); + connect(completer, SIGNAL(activated(const QString&)), this, SLOT(onSearchCompletionActivated(const QString&))); +} + +QAbstractItemView* QtSearchBox::getCompleterPopup() +{ + if (m_searchBox->completer()) + { + return m_searchBox->completer()->popup(); + } + return nullptr; +} + +void QtSearchBox::onSearchButtonClick() +{ + m_query = m_searchBox->text().toStdString(); + MessageSearch(m_query).dispatch(); +} + +void QtSearchBox::onSearchQueryEdited(const QString& text) +{ + m_query = text.toStdString(); + m_oldQuery = m_query; + + std::deque tokens = QueryTree::tokenizeQuery(text.toStdString()); + if (tokens.size()) + { + MessageSearchAutocomplete(tokens.back()).dispatch(); + } +} + +void QtSearchBox::onSearchQueryChanged(const QString& text) +{ + if (m_preventQueryChange) + { + m_preventQueryChange = false; + setText(m_query); + } +} + +void QtSearchBox::onSearchCompletionHighlighted(const QModelIndex& index) +{ + if (index.row() < 0 || index.row() >= int(m_matches.size())) + { + m_query = m_oldQuery; + } + else + { + std::deque tokens = QueryTree::tokenizeQuery(m_query); + if (tokens.size()) + { + tokens.pop_back(); + } + + std::string match = m_matches[index.row()].encodeForQuery(); + tokens.push_back(match); + + m_query = utility::join>(tokens, ""); + } + + setText(m_query); + m_preventQueryChange = true; +} + +void QtSearchBox::onSearchCompletionActivated(const QString& text) +{ + setText(m_query); + m_preventQueryChange = true; +} diff --git a/src/app/qt/element/QtSearchBox.h b/src/app/qt/element/QtSearchBox.h new file mode 100644 index 00000000..eefe7d5d --- /dev/null +++ b/src/app/qt/element/QtSearchBox.h @@ -0,0 +1,48 @@ +#ifndef QT_SEARCH_BOX_H +#define QT_SEARCH_BOX_H + +#include + +#include +#include + +#include "data/SearchIndex.h" + +class QLineEdit; +class QPushButton; + +class QtSearchBox + : public QFrame +{ + Q_OBJECT + +public: + QtSearchBox(); + virtual ~QtSearchBox(); + + void setText(const std::string& text); + void setFocus(); + void setAutocompletionList(const std::vector& autocompletionList); + + QAbstractItemView* getCompleterPopup(); + +private slots: + void onSearchButtonClick(); + void onSearchQueryEdited(const QString& text); + void onSearchQueryChanged(const QString& text); + void onSearchCompletionHighlighted(const QModelIndex& index); + void onSearchCompletionActivated(const QString& text); + +private: + QLineEdit* m_searchBox; + QPushButton* m_searchButton; + QPushButton* m_caseSensitiveButton; + + std::string m_query; + std::string m_oldQuery; + bool m_preventQueryChange; + + std::vector m_matches; +}; + +#endif // QT_SEARCH_BOX_H diff --git a/src/app/qt/view/QtSearchView.cpp b/src/app/qt/view/QtSearchView.cpp index e49a66f7..3eb69ae5 100644 --- a/src/app/qt/view/QtSearchView.cpp +++ b/src/app/qt/view/QtSearchView.cpp @@ -1,14 +1,6 @@ #include "qt/view/QtSearchView.h" -#include -#include -#include -#include - #include "component/controller/SearchController.h" -#include "qt/element/QtButton.h" -#include "qt/element/QtEditBox.h" -#include "qt/utility/utilityQt.h" #include "qt/view/QtViewWidgetWrapper.h" #include "utility/text/TextAccess.h" @@ -19,6 +11,8 @@ QtSearchView::QtSearchView(ViewLayout* viewLayout) , m_setFocusFunctor(std::bind(&QtSearchView::doSetFocus, this)) , m_setAutocompletionListFunctor(std::bind(&QtSearchView::doSetAutocompletionList, this, std::placeholders::_1)) { + m_widget = std::make_shared(); + setStyleSheet(); } QtSearchView::~QtSearchView() @@ -27,37 +21,11 @@ QtSearchView::~QtSearchView() void QtSearchView::createWidgetWrapper() { - setWidgetWrapper(std::make_shared(std::make_shared())); + setWidgetWrapper(std::make_shared(m_widget)); } void QtSearchView::initView() { - QWidget* widget = QtViewWidgetWrapper::getWidgetOfView(this); - widget->setObjectName("search_view"); - - QBoxLayout* layout = new QHBoxLayout(); - layout->setSpacing(0); - layout->setAlignment(Qt::AlignTop); - widget->setLayout(layout); - - m_searchButton = new QtButton(widget); - m_searchButton->setObjectName("search_button"); - m_searchButton->setCallbackOnClick(std::bind(&QtSearchView::onSearchButtonClick, this)); - widget->layout()->addWidget(m_searchButton); - - m_searchBox = new QtEditBox(widget); - m_searchBox->setObjectName("search_box"); - m_searchBox->setPlaceholderText("Please enter your search string."); - m_searchBox->setCallbackOnReturnPressed(std::bind(&QtSearchView::onSearchButtonClick, this)); - widget->layout()->addWidget(m_searchBox); - - m_caseSensitiveButton = new QtButton(widget); - m_caseSensitiveButton->setObjectName("case_sensitive_button"); - m_caseSensitiveButton->setCheckable(true); - m_caseSensitiveButton->setToolTip("case sensitive"); - widget->layout()->addWidget(m_caseSensitiveButton); - - setStyleSheet(); } void QtSearchView::refreshView() @@ -65,9 +33,9 @@ void QtSearchView::refreshView() m_refreshViewFunctor(); } -void QtSearchView::setText(const std::string& s) +void QtSearchView::setText(const std::string& text) { - m_setTextFunctor(s); + m_setTextFunctor(text); } void QtSearchView::setFocus() @@ -75,51 +43,30 @@ void QtSearchView::setFocus() m_setFocusFunctor(); } -void QtSearchView::setAutocompletionList(const std::vector& autocompletionList) +void QtSearchView::setAutocompletionList(const std::vector& autocompletionList) { m_setAutocompletionListFunctor(autocompletionList); } -void QtSearchView::onSearchButtonClick() -{ - SearchController* controller = getController(); - if (controller) - { - controller->search(m_searchBox->text().toStdString()); - } -} - void QtSearchView::doRefreshView() { setStyleSheet(); } -void QtSearchView::doSetText(const std::string& s) +void QtSearchView::doSetText(const std::string& text) { - if (m_searchBox->text() != s.c_str()) - { - m_searchBox->setText(s.c_str()); - } + m_widget->setText(text); } void QtSearchView::doSetFocus() { getViewLayout()->showView(this); - m_searchBox->setFocus(Qt::ShortcutFocusReason); + m_widget->setFocus(); } -void QtSearchView::doSetAutocompletionList(const std::vector& autocompletionList) +void QtSearchView::doSetAutocompletionList(const std::vector& autocompletionList) { - QStringList wordList; - for (const std::string& s: autocompletionList) - { - wordList << s.c_str(); - } - - QCompleter *completer = new QCompleter(wordList, m_searchBox); - completer->popup()->setObjectName("search_box_popup"); - completer->setCaseSensitivity(Qt::CaseInsensitive); - m_searchBox->setCompleter(completer); + m_widget->setAutocompletionList(autocompletionList); setStyleSheet(); } @@ -127,11 +74,10 @@ void QtSearchView::setStyleSheet() { std::string css = TextAccess::createFromFile("data/gui/search_view/search_view.css")->getText(); - QWidget* widget = QtViewWidgetWrapper::getWidgetOfView(this); - widget->setStyleSheet(css.c_str()); + m_widget->setStyleSheet(css.c_str()); - if (m_searchBox->completer()) + if (m_widget->getCompleterPopup()) { - m_searchBox->completer()->popup()->setStyleSheet(css.c_str()); + m_widget->getCompleterPopup()->setStyleSheet(css.c_str()); } } diff --git a/src/app/qt/view/QtSearchView.h b/src/app/qt/view/QtSearchView.h index bef09cb6..031ce0f3 100644 --- a/src/app/qt/view/QtSearchView.h +++ b/src/app/qt/view/QtSearchView.h @@ -4,11 +4,9 @@ #include #include "component/view/SearchView.h" +#include "qt/element/QtSearchBox.h" #include "qt/utility/QtThreadedFunctor.h" -class QtEditBox; -class QtButton; - class QtSearchView: public SearchView { public: @@ -21,28 +19,24 @@ public: virtual void refreshView(); // SearchView implementation - virtual void setText(const std::string& s); + virtual void setText(const std::string& text); virtual void setFocus(); - virtual void setAutocompletionList(const std::vector& autocompletionList); + virtual void setAutocompletionList(const std::vector& autocompletionList); private: - void onSearchButtonClick(); - void doRefreshView(); - void doSetText(const std::string& s); + void doSetText(const std::string& text); void doSetFocus(); - void doSetAutocompletionList(const std::vector& autocompletionList); + void doSetAutocompletionList(const std::vector& autocompletionList); void setStyleSheet(); - QtEditBox* m_searchBox; - QtButton* m_searchButton; - QtButton* m_caseSensitiveButton; - QtThreadedFunctor<> m_refreshViewFunctor; QtThreadedFunctor m_setTextFunctor; QtThreadedFunctor<> m_setFocusFunctor; - QtThreadedFunctor&> m_setAutocompletionListFunctor; + QtThreadedFunctor&> m_setAutocompletionListFunctor; + + std::shared_ptr m_widget; }; # endif // QT_SEARCH_VIEW_H diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 582481c7..fef79c93 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -189,6 +189,7 @@ add_files( utility/messaging/type/MessageLoadProject.h utility/messaging/type/MessageLoadSource.h utility/messaging/type/MessageRefresh.h + utility/messaging/type/MessageSearch.h utility/messaging/type/MessageShowFile.h utility/messaging/Message.h diff --git a/src/lib/component/controller/SearchController.cpp b/src/lib/component/controller/SearchController.cpp index 349a432c..67c99682 100644 --- a/src/lib/component/controller/SearchController.cpp +++ b/src/lib/component/controller/SearchController.cpp @@ -13,30 +13,6 @@ SearchController::~SearchController() { } -void SearchController::search(const std::string& s) -{ - LOG_INFO("searching string: \"" + s + "\""); - - std::vector ids = m_graphAccess->getTokenIdsForQuery(s); - if (ids.size()) - { - MessageActivateTokens(ids).dispatch(); - return; - } - - Id nodeId = m_graphAccess->getIdForNodeWithName(s); - if (nodeId > 0) - { - LOG_INFO("Node with name \"" + s + "\" found."); - MessageActivateToken message(nodeId); - message.dispatch(); - } - else - { - LOG_INFO("Node with name \"" + s + "\" not found."); - } -} - void SearchController::handleMessage(MessageActivateToken* message) { getView()->setText(m_graphAccess->getNameForNodeWithId(message->tokenId)); @@ -47,16 +23,38 @@ void SearchController::handleMessage(MessageFind* message) getView()->setFocus(); } -void SearchController::handleMessage(MessageFinishedParsing* message) -{ - getView()->setAutocompletionList(m_graphAccess->getNamesForNodesWithNamePrefix(":")); -} - void SearchController::handleMessage(MessageRefresh* message) { getView()->refreshView(); } +void SearchController::handleMessage(MessageSearch* message) +{ + const std::string& query = message->query; + + LOG_INFO("search string: \"" + query + "\""); + + std::vector ids = m_graphAccess->getTokenIdsForQuery(query); + if (ids.size()) + { + MessageActivateTokens(ids).dispatch(); + return; + } + + Id nodeId = m_graphAccess->getIdForNodeWithName(query); + if (nodeId > 0) + { + MessageActivateToken message(nodeId); + message.dispatch(); + } +} + +void SearchController::handleMessage(MessageSearchAutocomplete* message) +{ + LOG_INFO("autocomplete string: \"" + message->query + "\""); + getView()->setAutocompletionList(m_graphAccess->getAutocompletionMatches(message->query)); +} + SearchView* SearchController::getView() { return Controller::getView(); diff --git a/src/lib/component/controller/SearchController.h b/src/lib/component/controller/SearchController.h index 71995540..cff6a382 100644 --- a/src/lib/component/controller/SearchController.h +++ b/src/lib/component/controller/SearchController.h @@ -7,8 +7,9 @@ #include "utility/messaging/MessageListener.h" #include "utility/messaging/type/MessageActivateToken.h" #include "utility/messaging/type/MessageFind.h" -#include "utility/messaging/type/MessageFinishedParsing.h" #include "utility/messaging/type/MessageRefresh.h" +#include "utility/messaging/type/MessageSearch.h" +#include "utility/messaging/type/MessageSearchAutocomplete.h" class GraphAccess; class SearchView; @@ -17,21 +18,21 @@ class SearchController : public Controller , public MessageListener , public MessageListener - , public MessageListener , public MessageListener + , public MessageListener + , public MessageListener { public: SearchController(GraphAccess* graphAccess); ~SearchController(); - void search(const std::string& s); - //void autocomplete(const std::string& s); - private: virtual void handleMessage(MessageActivateToken* message); virtual void handleMessage(MessageFind* message); - virtual void handleMessage(MessageFinishedParsing* message); virtual void handleMessage(MessageRefresh* message); + virtual void handleMessage(MessageSearch* message); + virtual void handleMessage(MessageSearchAutocomplete* message); + SearchView* getView(); GraphAccess* m_graphAccess; diff --git a/src/lib/component/view/SearchView.h b/src/lib/component/view/SearchView.h index 1bc55d02..292d1446 100644 --- a/src/lib/component/view/SearchView.h +++ b/src/lib/component/view/SearchView.h @@ -2,6 +2,7 @@ #define SEARCH_VIEW_H #include "component/view/View.h" +#include "data/SearchIndex.h" class SearchController; @@ -15,7 +16,7 @@ public: virtual void setText(const std::string& s) = 0; virtual void setFocus() = 0; - virtual void setAutocompletionList(const std::vector& autocompletionList) = 0; + virtual void setAutocompletionList(const std::vector& autocompletionList) = 0; protected: SearchController* getController(); diff --git a/src/lib/component/view/View.h b/src/lib/component/view/View.h index a4a5f8ee..579c2694 100644 --- a/src/lib/component/view/View.h +++ b/src/lib/component/view/View.h @@ -20,6 +20,7 @@ public: virtual ~View(); virtual std::string getName() const = 0; + virtual void createWidgetWrapper() = 0; virtual void initView() = 0; virtual void refreshView() = 0; diff --git a/src/lib/data/SearchIndex.cpp b/src/lib/data/SearchIndex.cpp index 29d008a8..ec26a381 100644 --- a/src/lib/data/SearchIndex.cpp +++ b/src/lib/data/SearchIndex.cpp @@ -3,6 +3,7 @@ #include #include +#include "data/query/QueryToken.h" #include "utility/logging/logging.h" #include "utility/text/Dictionary.h" #include "utility/utilityString.h" @@ -22,7 +23,7 @@ namespace void SearchIndex::SearchMatch::print(std::ostream& ostream) const { - ostream << weight << '\t' << node->getFullName() << std::endl << '\t'; + ostream << weight << '\t' << fullName << std::endl << '\t'; size_t i = 0; for (size_t index : indices) { @@ -37,6 +38,23 @@ void SearchIndex::SearchMatch::print(std::ostream& ostream) const ostream << std::endl; } +std::string SearchIndex::SearchMatch::encodeForQuery() const +{ + if (!tokenIds.size()) + { + return fullName; + } + + std::stringstream ss; + ss << QueryToken::BOUNDARY << fullName; + for (Id tokenId : tokenIds) + { + ss << QueryToken::DELIMITER << tokenId; + } + ss << QueryToken::BOUNDARY; + return ss.str(); +} + SearchIndex::SearchNode::SearchNode(SearchNode* parent, const std::string& name, Id nameId) : m_parent(parent) , m_name(name) @@ -284,7 +302,8 @@ std::pair SearchIndex::SearchNode::fuzzyMatch( SearchIndex::SearchMatch SearchIndex::SearchNode::fuzzyMatchData(const std::string& query, const SearchNode* parent) const { SearchMatch data; - data.node = this; + data.fullName = getFullName(); + data.tokenIds = m_tokenIds; data.weight = 0; size_t pos = 0; diff --git a/src/lib/data/SearchIndex.h b/src/lib/data/SearchIndex.h index 5299fd53..d3cba199 100644 --- a/src/lib/data/SearchIndex.h +++ b/src/lib/data/SearchIndex.h @@ -19,7 +19,10 @@ public: { void print(std::ostream& ostream) const; - const SearchIndex::SearchNode* node; + std::string encodeForQuery() const; + + std::string fullName; + std::set tokenIds; std::vector indices; size_t weight; }; diff --git a/src/lib/data/Storage.cpp b/src/lib/data/Storage.cpp index daa7fef7..43aa97d6 100644 --- a/src/lib/data/Storage.cpp +++ b/src/lib/data/Storage.cpp @@ -336,15 +336,11 @@ std::string Storage::getNameForNodeWithId(Id id) const } } -std::vector Storage::getNamesForNodesWithNamePrefix(const std::string& prefix) const +std::vector Storage::getAutocompletionMatches(const std::string& query) const { - std::vector names; - std::vector matches = m_index.findFuzzyMatches(prefix); - for (const SearchIndex::SearchMatch& match : matches) - { - names.push_back(match.node->getFullName()); - } - return names; + std::vector matches = m_index.findFuzzyMatches(query); + SearchIndex::logMatches(matches, query); + return matches; } std::shared_ptr Storage::getGraphForActiveTokenIds(const std::vector& tokenIds) const @@ -444,8 +440,6 @@ std::vector Storage::getTokenIdsForQuery(std::string query) const LOG_INFO_STREAM(<< '\n' << tree << '\n' << outGraph); - SearchIndex::logMatches(m_index.findFuzzyMatches(query), query); - return outGraph.getTokenIds(); } diff --git a/src/lib/data/Storage.h b/src/lib/data/Storage.h index eb3033c2..7d547563 100644 --- a/src/lib/data/Storage.h +++ b/src/lib/data/Storage.h @@ -67,7 +67,7 @@ public: // GraphAccess implementation virtual Id getIdForNodeWithName(const std::string& fullName) const; virtual std::string getNameForNodeWithId(Id id) const; - virtual std::vector getNamesForNodesWithNamePrefix(const std::string& prefix) const; + virtual std::vector getAutocompletionMatches(const std::string& query) const; virtual std::shared_ptr getGraphForActiveTokenIds(const std::vector& tokenIds) const; diff --git a/src/lib/data/access/GraphAccess.h b/src/lib/data/access/GraphAccess.h index fa0210c6..403f8988 100644 --- a/src/lib/data/access/GraphAccess.h +++ b/src/lib/data/access/GraphAccess.h @@ -6,6 +6,7 @@ #include #include "data/graph/Graph.h" +#include "data/SearchIndex.h" #include "utility/types.h" class GraphAccess @@ -15,7 +16,7 @@ public: virtual Id getIdForNodeWithName(const std::string& name) const = 0; virtual std::string getNameForNodeWithId(Id id) const = 0; - virtual std::vector getNamesForNodesWithNamePrefix(const std::string& prefix) const = 0; + virtual std::vector getAutocompletionMatches(const std::string& query) const = 0; virtual std::shared_ptr getGraphForActiveTokenIds(const std::vector& tokenIds) const = 0; diff --git a/src/lib/data/access/GraphAccessProxy.cpp b/src/lib/data/access/GraphAccessProxy.cpp index 3fd1405e..f04b1c18 100644 --- a/src/lib/data/access/GraphAccessProxy.cpp +++ b/src/lib/data/access/GraphAccessProxy.cpp @@ -47,14 +47,14 @@ std::string GraphAccessProxy::getNameForNodeWithId(Id id) const return ""; } -std::vector GraphAccessProxy::getNamesForNodesWithNamePrefix(const std::string& prefix) const +std::vector GraphAccessProxy::getAutocompletionMatches(const std::string& query) const { if (hasSubject()) { - return m_subject->getNamesForNodesWithNamePrefix(prefix); + return m_subject->getAutocompletionMatches(query); } - return std::vector(); + return std::vector(); } std::shared_ptr GraphAccessProxy::getGraphForActiveTokenIds(const std::vector& tokenIds) const diff --git a/src/lib/data/access/GraphAccessProxy.h b/src/lib/data/access/GraphAccessProxy.h index 98fcd4f4..66396e38 100644 --- a/src/lib/data/access/GraphAccessProxy.h +++ b/src/lib/data/access/GraphAccessProxy.h @@ -15,7 +15,7 @@ public: // GraphAccess implementation virtual Id getIdForNodeWithName(const std::string& name) const; virtual std::string getNameForNodeWithId(Id id) const; - virtual std::vector getNamesForNodesWithNamePrefix(const std::string& prefix) const; + virtual std::vector getAutocompletionMatches(const std::string& query) const; virtual std::shared_ptr getGraphForActiveTokenIds(const std::vector& tokenIds) const; diff --git a/src/lib/data/graph/FilterableGraph.cpp b/src/lib/data/graph/FilterableGraph.cpp index c90b42a5..9e66ce43 100644 --- a/src/lib/data/graph/FilterableGraph.cpp +++ b/src/lib/data/graph/FilterableGraph.cpp @@ -11,6 +11,16 @@ FilterableGraph::~FilterableGraph() { } +Token* FilterableGraph::getTokenById(Id id) const +{ + Token* token = getNodeById(id); + if (!token) + { + token = getEdgeById(id); + } + return token; +} + void FilterableGraph::print(std::ostream& ostream) const { ostream << "Graph:\n"; diff --git a/src/lib/data/graph/FilterableGraph.h b/src/lib/data/graph/FilterableGraph.h index 01f06577..e1c51f60 100644 --- a/src/lib/data/graph/FilterableGraph.h +++ b/src/lib/data/graph/FilterableGraph.h @@ -4,6 +4,8 @@ #include #include +#include "utility/types.h" + class Edge; class Node; class Token; @@ -29,6 +31,11 @@ public: virtual size_t getNodeCount() const = 0; virtual size_t getEdgeCount() const = 0; + virtual Node* getNodeById(Id id) const = 0; + virtual Edge* getEdgeById(Id id) const = 0; + + Token* getTokenById(Id id) const; + void print(std::ostream& ostream) const; void printBasic(std::ostream& ostream) const; }; diff --git a/src/lib/data/graph/Graph.cpp b/src/lib/data/graph/Graph.cpp index b4f85831..213657d2 100644 --- a/src/lib/data/graph/Graph.cpp +++ b/src/lib/data/graph/Graph.cpp @@ -75,16 +75,6 @@ size_t Graph::getEdgeCount() const return m_edges.size(); } -const std::map>& Graph::getNodes() const -{ - return m_nodes; -} - -const std::map>& Graph::getEdges() const -{ - return m_edges; -} - Node* Graph::getNodeById(Id id) const { std::map>::const_iterator it = m_nodes.find(id); @@ -105,14 +95,14 @@ Edge* Graph::getEdgeById(Id id) const return nullptr; } -Token* Graph::getTokenById(Id id) const +const std::map>& Graph::getNodes() const { - Token* token = getNodeById(id); - if (!token) - { - token = getEdgeById(id); - } - return token; + return m_nodes; +} + +const std::map>& Graph::getEdges() const +{ + return m_edges; } void Graph::removeNode(Node* node) diff --git a/src/lib/data/graph/Graph.h b/src/lib/data/graph/Graph.h index 147a8fc6..d9ec378a 100644 --- a/src/lib/data/graph/Graph.h +++ b/src/lib/data/graph/Graph.h @@ -32,13 +32,12 @@ public: virtual size_t getNodeCount() const; virtual size_t getEdgeCount() const; + virtual Node* getNodeById(Id id) const; + virtual Edge* getEdgeById(Id id) const; + const std::map>& getNodes() const; const std::map>& getEdges() const; - Node* getNodeById(Id id) const; - Edge* getEdgeById(Id id) const; - Token* getTokenById(Id id) const; - void removeNode(Node* node); void removeEdge(Edge* edge); diff --git a/src/lib/data/graph/SubGraph.cpp b/src/lib/data/graph/SubGraph.cpp index c7ee0104..ca12f582 100644 --- a/src/lib/data/graph/SubGraph.cpp +++ b/src/lib/data/graph/SubGraph.cpp @@ -71,6 +71,26 @@ size_t SubGraph::getEdgeCount() const return m_edges.size(); } +Node* SubGraph::getNodeById(Id id) const +{ + std::map::const_iterator it = m_nodes.find(id); + if (it != m_nodes.end()) + { + return it->second; + } + return nullptr; +} + +Edge* SubGraph::getEdgeById(Id id) const +{ + std::map::const_iterator it = m_edges.find(id); + if (it != m_edges.end()) + { + return it->second; + } + return nullptr; +} + std::vector SubGraph::getTokenIds() const { std::vector ids; diff --git a/src/lib/data/graph/SubGraph.h b/src/lib/data/graph/SubGraph.h index 8358f9c2..65ecb2ab 100644 --- a/src/lib/data/graph/SubGraph.h +++ b/src/lib/data/graph/SubGraph.h @@ -6,7 +6,6 @@ #include #include "data/graph/FilterableGraph.h" -#include "utility/types.h" class Edge; class Node; @@ -35,6 +34,9 @@ public: virtual size_t getNodeCount() const; virtual size_t getEdgeCount() const; + virtual Node* getNodeById(Id id) const; + virtual Edge* getEdgeById(Id id) const; + std::vector getTokenIds() const; void subtract(const SubGraph& other); diff --git a/src/lib/data/graph/filter/GraphFilterConductor.cpp b/src/lib/data/graph/filter/GraphFilterConductor.cpp index e4fe7653..1bfaa9b1 100644 --- a/src/lib/data/graph/filter/GraphFilterConductor.cpp +++ b/src/lib/data/graph/filter/GraphFilterConductor.cpp @@ -174,5 +174,5 @@ void GraphFilterConductor::filterCommandNode(const QueryCommand* node, const Fil void GraphFilterConductor::filterTokenNode(const QueryToken* node, const FilterableGraph* in, FilterableGraph* out) const { - GraphFilterToken(node->getName()).apply(in, out); + GraphFilterToken(node->getTokenName(), node->getTokenIds()).apply(in, out); } diff --git a/src/lib/data/graph/filter/GraphFilterImplementations.h b/src/lib/data/graph/filter/GraphFilterImplementations.h index f85cc557..a711c46b 100644 --- a/src/lib/data/graph/filter/GraphFilterImplementations.h +++ b/src/lib/data/graph/filter/GraphFilterImplementations.h @@ -1,8 +1,11 @@ #ifndef GRAPH_FILTER_IMPLEMENTATIONS_H #define GRAPH_FILTER_IMPLEMENTATIONS_H +#include + #include "data/graph/Edge.h" #include "data/graph/filter/GraphFilter.h" +#include "data/graph/FilterableGraph.h" #include "data/graph/Node.h" #include "data/graph/token_component/TokenComponentAbstraction.h" #include "data/graph/token_component/TokenComponentAccess.h" @@ -258,22 +261,43 @@ class GraphFilterToken : public GraphFilter { public: - GraphFilterToken(const std::string& name) - : m_name(name) + GraphFilterToken(const std::string& tokenName, const std::set& tokenIds) + : m_tokenName(tokenName) + , m_tokenIds(tokenIds) { } + void apply(const FilterableGraph* in, FilterableGraph* out) + { + if (m_tokenIds.size()) + { + for (Id tokenId : m_tokenIds) + { + Node* node = in->getNodeById(tokenId); + if (node) + { + out->addNode(node); + } + } + } + else + { + GraphFilter::apply(in, out); + } + } + protected: virtual void visitNode(Node* node) { - if (node->getName() == m_name) + if (node->getFullName() == m_tokenName) { addNode(node); } } private: - const std::string& m_name; + const std::string& m_tokenName; + const std::set& m_tokenIds; }; #endif // GRAPH_FILTER_IMPLEMENTATIONS_H diff --git a/src/lib/data/query/QueryCommand.cpp b/src/lib/data/query/QueryCommand.cpp index 1d68ee36..1e95e6b9 100644 --- a/src/lib/data/query/QueryCommand.cpp +++ b/src/lib/data/query/QueryCommand.cpp @@ -80,7 +80,7 @@ bool QueryCommand::isToken() const return false; } -bool QueryCommand::isComplete() const +bool QueryCommand::derivedIsComplete() const { return m_type != COMMAND_INVALID; } diff --git a/src/lib/data/query/QueryCommand.h b/src/lib/data/query/QueryCommand.h index 813b116c..ca71ebb3 100644 --- a/src/lib/data/query/QueryCommand.h +++ b/src/lib/data/query/QueryCommand.h @@ -53,7 +53,7 @@ public: virtual bool isOperator() const; virtual bool isToken() const; - virtual bool isComplete() const; + virtual bool derivedIsComplete() const; virtual void print(std::ostream& ostream) const; diff --git a/src/lib/data/query/QueryNode.cpp b/src/lib/data/query/QueryNode.cpp index 7359779e..c54b73c2 100644 --- a/src/lib/data/query/QueryNode.cpp +++ b/src/lib/data/query/QueryNode.cpp @@ -29,7 +29,7 @@ void QueryNode::print(std::ostream& ostream, int n) const ostream << ')'; } - if (!m_isComplete || !isComplete()) + if (!isComplete()) { ostream << " INVALID"; } @@ -47,6 +47,11 @@ void QueryNode::setIsGroup(bool isGroup) m_isGroup = isGroup; } +bool QueryNode::isComplete() const +{ + return m_isComplete && derivedIsComplete(); +} + void QueryNode::setIsComplete(bool isComplete) { m_isComplete = isComplete; diff --git a/src/lib/data/query/QueryNode.h b/src/lib/data/query/QueryNode.h index 170e0ec4..bf836b97 100644 --- a/src/lib/data/query/QueryNode.h +++ b/src/lib/data/query/QueryNode.h @@ -13,7 +13,7 @@ public: virtual bool isOperator() const = 0; virtual bool isToken() const = 0; - virtual bool isComplete() const = 0; + virtual bool derivedIsComplete() const = 0; virtual void print(std::ostream& ostream) const = 0; virtual void print(std::ostream& ostream, int n) const; @@ -21,6 +21,7 @@ public: bool isGroup() const; void setIsGroup(bool isGroup); + bool isComplete() const; void setIsComplete(bool isComplete); private: diff --git a/src/lib/data/query/QueryOperator.cpp b/src/lib/data/query/QueryOperator.cpp index ef6d17d2..80ad3785 100644 --- a/src/lib/data/query/QueryOperator.cpp +++ b/src/lib/data/query/QueryOperator.cpp @@ -1,5 +1,7 @@ #include "data/query/QueryOperator.h" +#include "data/query/QueryToken.h" + const std::map& QueryOperator::getOperatorTypeMap() { static std::map operatorMap; @@ -17,7 +19,8 @@ const std::map& QueryOperator::getOperatorTyp operatorMap.emplace('&', OPERATOR_AND); operatorMap.emplace('|', OPERATOR_OR); - operatorMap.emplace('"', OPERATOR_NAME); + operatorMap.emplace(QueryToken::BOUNDARY, OPERATOR_TOKEN); + operatorMap.emplace('(', OPERATOR_GROUP_OPEN); operatorMap.emplace(')', OPERATOR_GROUP_CLOSE); @@ -74,7 +77,7 @@ bool QueryOperator::isToken() const return false; } -bool QueryOperator::isComplete() const +bool QueryOperator::derivedIsComplete() const { if (m_type == OPERATOR_NOT) { diff --git a/src/lib/data/query/QueryOperator.h b/src/lib/data/query/QueryOperator.h index 506971d0..6100d489 100644 --- a/src/lib/data/query/QueryOperator.h +++ b/src/lib/data/query/QueryOperator.h @@ -20,7 +20,7 @@ public: OPERATOR_AND, OPERATOR_OR, - OPERATOR_NAME, + OPERATOR_TOKEN, OPERATOR_GROUP_OPEN, OPERATOR_GROUP_CLOSE }; @@ -36,7 +36,7 @@ public: virtual bool isOperator() const; virtual bool isToken() const; - virtual bool isComplete() const; + virtual bool derivedIsComplete() const; virtual void print(std::ostream& ostream) const; virtual void print(std::ostream& ostream, int n) const; diff --git a/src/lib/data/query/QueryToken.cpp b/src/lib/data/query/QueryToken.cpp index 95a7f35e..0a5e1864 100644 --- a/src/lib/data/query/QueryToken.cpp +++ b/src/lib/data/query/QueryToken.cpp @@ -1,8 +1,29 @@ #include "data/query/QueryToken.h" +#include + +#include "utility/utilityString.h" + QueryToken::QueryToken(const std::string& name) - : m_name(name) { + std::deque names = utility::split>(name, DELIMITER); + + m_tokenName = names.front(); + names.pop_front(); + + while (names.size()) + { + std::stringstream ss; + ss << names.front(); + names.pop_front(); + + Id tokenId = 0; + ss >> tokenId; + if (tokenId) + { + m_tokenIds.insert(tokenId); + } + } } QueryToken::~QueryToken() @@ -24,17 +45,30 @@ bool QueryToken::isToken() const return true; } -bool QueryToken::isComplete() const +bool QueryToken::derivedIsComplete() const { return true; } void QueryToken::print(std::ostream& ostream) const { - ostream << '"' << m_name << '"'; + ostream << BOUNDARY << m_tokenName; + for (Id tokenId : m_tokenIds) + { + ostream << DELIMITER << tokenId; + } + ostream << BOUNDARY; } -const std::string& QueryToken::getName() const +const std::string& QueryToken::getTokenName() const { - return m_name; + return m_tokenName; } + +const std::set& QueryToken::getTokenIds() const +{ + return m_tokenIds; +} + +const char QueryToken::DELIMITER = ','; +const char QueryToken::BOUNDARY = '"'; diff --git a/src/lib/data/query/QueryToken.h b/src/lib/data/query/QueryToken.h index 206b52e3..75b71365 100644 --- a/src/lib/data/query/QueryToken.h +++ b/src/lib/data/query/QueryToken.h @@ -1,9 +1,11 @@ #ifndef QUERY_TOKEN_H #define QUERY_TOKEN_H +#include #include #include "data/query/QueryNode.h" +#include "utility/types.h" class QueryToken : public QueryNode @@ -15,14 +17,20 @@ public: virtual bool isCommand() const; virtual bool isOperator() const; virtual bool isToken() const; - virtual bool isComplete() const; + + virtual bool derivedIsComplete() const; virtual void print(std::ostream& ostream) const; - const std::string& getName() const; + const std::string& getTokenName() const; + const std::set& getTokenIds() const; + + static const char DELIMITER; + static const char BOUNDARY; private: - const std::string m_name; + std::string m_tokenName; + std::set m_tokenIds; }; #endif // QUERY_TOKEN_H diff --git a/src/lib/data/query/QueryTree.cpp b/src/lib/data/query/QueryTree.cpp index b890e17c..8c6e8536 100644 --- a/src/lib/data/query/QueryTree.cpp +++ b/src/lib/data/query/QueryTree.cpp @@ -6,22 +6,51 @@ #include "data/query/QueryToken.h" #include "utility/utilityString.h" -QueryTree::QueryTree(std::string query) - : m_valid(true) +std::deque QueryTree::tokenizeQuery(const std::string& query) { - std::deque tokens = + std::deque tokensTmp = utility::split>(query, QueryOperator::getOperator(QueryOperator::OPERATOR_NONE)); for (const std::pair& p : QueryOperator::getOperatorTypeMap()) { - tokens = utility::tokenize>(tokens, p.first); + tokensTmp = utility::tokenize>(tokensTmp, p.first); } - for (std::string str : tokens) + char operatorToken = QueryOperator::getOperator(QueryOperator::OPERATOR_TOKEN); + bool isToken = false; + + std::string token; + std::deque tokens; + + while (tokensTmp.size()) { - m_query += str + ' '; + std::string tokenTmp = tokensTmp.front(); + tokensTmp.pop_front(); + + token += tokenTmp; + + if (tokenTmp.size() == 1 && tokenTmp[0] == operatorToken) + { + isToken = !isToken; + } + + if (!isToken || (!tokensTmp.size() && token.size())) + { + tokens.push_back(token); + token.clear(); + } } + return tokens; +} + +QueryTree::QueryTree(const std::string& query) + : m_valid(true) +{ + std::deque tokens = tokenizeQuery(query); + + m_query = utility::join>(tokens, ' '); + m_root = buildTree(tokens, nullptr); } @@ -45,7 +74,7 @@ void QueryTree::print(std::ostream& ostream) const if (!m_valid) { - ostream << "INVALID"; + ostream << " INVALID"; } ostream << '\n'; @@ -156,18 +185,10 @@ std::shared_ptr QueryTree::buildGroup(std::deque& tokens return nullptr; } - std::shared_ptr groupNode; - if (closeType == QueryOperator::OPERATOR_NAME) - { - groupNode = std::make_shared(name); - } - else - { - groupNode = buildTree(group, nullptr); - groupNode->setIsGroup(true); - } - + std::shared_ptr groupNode = buildTree(group, nullptr); + groupNode->setIsGroup(true); groupNode->setIsComplete(valid); + return groupNode; } @@ -189,8 +210,9 @@ std::shared_ptr QueryTree::getNextNode(std::deque& token case QueryOperator::OPERATOR_OR: return std::make_shared(type); - case QueryOperator::OPERATOR_NAME: - return buildGroup(tokens, QueryOperator::OPERATOR_NAME); + case QueryOperator::OPERATOR_TOKEN: + return createToken(token); + case QueryOperator::OPERATOR_GROUP_OPEN: return buildGroup(tokens, QueryOperator::OPERATOR_GROUP_CLOSE); case QueryOperator::OPERATOR_GROUP_CLOSE: @@ -205,7 +227,7 @@ std::shared_ptr QueryTree::getNextNode(std::deque& token return nullptr; } -std::shared_ptr QueryTree::createCommand(std::string name) +std::shared_ptr QueryTree::createCommand(const std::string& name) { std::shared_ptr node = std::make_shared(name); @@ -218,6 +240,17 @@ std::shared_ptr QueryTree::createCommand(std::string name) return node; } +std::shared_ptr QueryTree::createToken(const std::string& name) +{ + if (name.size() < 3 || name.front() != QueryToken::BOUNDARY || name.back() != QueryToken::BOUNDARY) + { + m_valid = false; + return nullptr; + } + + return std::make_shared(name.substr(1, name.size() - 2)); +} + std::ostream& operator<<(std::ostream& ostream, const QueryTree& tree) { tree.print(ostream); diff --git a/src/lib/data/query/QueryTree.h b/src/lib/data/query/QueryTree.h index b99dea9c..380be4b4 100644 --- a/src/lib/data/query/QueryTree.h +++ b/src/lib/data/query/QueryTree.h @@ -13,7 +13,9 @@ class QueryNode; class QueryTree { public: - QueryTree(std::string query); + static std::deque tokenizeQuery(const std::string& query); + + QueryTree(const std::string& query); ~QueryTree(); std::shared_ptr getRoot() const; @@ -26,7 +28,8 @@ private: std::shared_ptr buildTree(std::deque& tokens, std::shared_ptr frontNode); std::shared_ptr buildGroup(std::deque& tokens, QueryOperator::OperatorType closeType); std::shared_ptr getNextNode(std::deque& tokens); - std::shared_ptr createCommand(std::string name); + std::shared_ptr createCommand(const std::string& name); + std::shared_ptr createToken(const std::string& name); std::shared_ptr m_root; std::string m_query; diff --git a/src/lib/utility/messaging/type/MessageSearch.h b/src/lib/utility/messaging/type/MessageSearch.h new file mode 100644 index 00000000..1c1893bb --- /dev/null +++ b/src/lib/utility/messaging/type/MessageSearch.h @@ -0,0 +1,23 @@ +#ifndef MESSAGE_SEARCH_H +#define MESSAGE_SEARCH_H + +#include "utility/messaging/Message.h" +#include "utility/types.h" + +class MessageSearch: public Message +{ +public: + MessageSearch(const std::string& query) + : query(query) + { + } + + static const std::string getStaticType() + { + return "MessageSearch"; + } + + const std::string query; +}; + +#endif // MESSAGE_SEARCH_H diff --git a/src/lib/utility/messaging/type/MessageSearchAutocomplete.h b/src/lib/utility/messaging/type/MessageSearchAutocomplete.h new file mode 100644 index 00000000..53e31a78 --- /dev/null +++ b/src/lib/utility/messaging/type/MessageSearchAutocomplete.h @@ -0,0 +1,23 @@ +#ifndef MESSAGE_SEARCH_AUTOCOMPLETE_H +#define MESSAGE_SEARCH_AUTOCOMPLETE_H + +#include "utility/messaging/Message.h" +#include "utility/types.h" + +class MessageSearchAutocomplete: public Message +{ +public: + MessageSearchAutocomplete(const std::string& query) + : query(query) + { + } + + static const std::string getStaticType() + { + return "MessageSearchAutocomplete"; + } + + const std::string query; +}; + +#endif // MESSAGE_SEARCH_AUTOCOMPLETE_H diff --git a/src/lib/utility/utilityString.h b/src/lib/utility/utilityString.h index 99a2819e..28f4533d 100644 --- a/src/lib/utility/utilityString.h +++ b/src/lib/utility/utilityString.h @@ -1,6 +1,7 @@ #ifndef UTILITY_STRING_H #define UTILITY_STRING_H +#include #include #include @@ -12,6 +13,12 @@ namespace utility template ContainerType split(const std::string& str, const std::string& delimiter); + template + std::string join(const ContainerType& list, char delimiter); + + template + std::string join(const ContainerType& list, const std::string& delimiter); + template ContainerType tokenize(const std::string& str, char delimiter); @@ -53,6 +60,30 @@ ContainerType utility::split(const std::string& str, const std::string& delimite return c; } +template +std::string utility::join(const ContainerType& list, char delimiter) +{ + return join(list, std::string(1, delimiter)); +} + +template +std::string utility::join(const ContainerType& list, const std::string& delimiter) +{ + std::stringstream ss; + bool first = true; + for (const std::string& str : list) + { + if (!first) + { + ss << delimiter; + } + first = false; + + ss << str; + } + return ss.str(); +} + template ContainerType utility::tokenize(const std::string& str, char delimiter) { diff --git a/src/test/GraphFilterConductorTestSuite.h b/src/test/GraphFilterConductorTestSuite.h index 569c31cc..307b27ff 100644 --- a/src/test/GraphFilterConductorTestSuite.h +++ b/src/test/GraphFilterConductorTestSuite.h @@ -17,12 +17,69 @@ public: ); } + void test_token_query_with_id() + { + std::set ids = getIdsForNodeWithName("main"); + std::stringstream ss; + ss << "\"main"; + for (Id id : ids) + { + ss << ',' << id; + } + ss << '"'; + + TS_ASSERT_EQUALS( + printedFilteredTestGraph(ss.str()), // "main," + + "1 nodes: function:main\n" + "0 edges:\n" + ); + } + + void test_token_query_with_id_and_wrong_name_uses_id() + { + std::set ids = getIdsForNodeWithName("main"); + std::stringstream ss; + ss << "\"hello"; + for (Id id : ids) + { + ss << ',' << id; + } + ss << '"'; + + TS_ASSERT_EQUALS( + printedFilteredTestGraph(ss.str()), // "hello," + + "1 nodes: function:main\n" + "0 edges:\n" + ); + } + + void test_token_query_with_ids() + { + std::set ids = getIdsForNodeWithName("A::A"); + std::stringstream ss; + ss << "\"A::A"; + for (Id id : ids) + { + ss << ',' << id; + } + ss << '"'; + + TS_ASSERT_EQUALS( + printedFilteredTestGraph(ss.str()), // "A::A,," + + "2 nodes: method:A::A method:A::A\n" + "0 edges:\n" + ); + } + void test_command_query() { TS_ASSERT_EQUALS( printedFilteredTestGraph("method"), - "4 nodes: method:A::A method:A::getCount method:A::process method:B::process\n" + "5 nodes: method:A::A method:A::A method:A::getCount method:A::process method:B::process\n" "0 edges:\n" ); @@ -104,6 +161,19 @@ private: return ss.str(); } + std::set getIdsForNodeWithName(const std::string& name) + { + createTestStorage(); + + std::vector matches = m_storage->getAutocompletionMatches(name); + if (matches.size() && matches[0].fullName == name) + { + return matches[0].tokenIds; + } + + return std::set(); + } + void createTestStorage() { if (m_storage) @@ -121,6 +191,10 @@ private: " count++;\n" " }\n" "\n" + " A(int c) {\n" + " count += c;\n" + " }\n" + "\n" " static int getCount()\n" " {\n" " return count;\n" diff --git a/src/test/GraphFilterTestSuite.h b/src/test/GraphFilterTestSuite.h index bc166e7b..7fa96305 100644 --- a/src/test/GraphFilterTestSuite.h +++ b/src/test/GraphFilterTestSuite.h @@ -161,7 +161,7 @@ public: void test_GraphFilterToken() { - GraphFilterToken filter("main"); + GraphFilterToken filter("main", std::set()); TS_ASSERT_EQUALS( printedFilteredTestGraph(&filter), diff --git a/src/test/QueryTreeTestSuite.h b/src/test/QueryTreeTestSuite.h index 97a7e9b6..22812752 100644 --- a/src/test/QueryTreeTestSuite.h +++ b/src/test/QueryTreeTestSuite.h @@ -10,7 +10,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree(""), - "INVALID\n" + " INVALID\n" ); } @@ -28,7 +28,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree("class"), - "class \n" + "class\n" "class\n" ); } @@ -47,7 +47,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree("\"A\""), - "\" A \" \n" + "\"A\"\n" "\"A\"\n" ); } @@ -57,14 +57,13 @@ public: TS_ASSERT_EQUALS( printedQueryTree("\"A"), - "\" A INVALID\n" - "\"A\" INVALID\n" + "\"A INVALID\n" ); TS_ASSERT_EQUALS( printedQueryTree("\"\""), - "\" \" INVALID\n" + "\"\" INVALID\n" ); TS_ASSERT_EQUALS( @@ -74,12 +73,32 @@ public: ); } + void test_token_query_with_id() + { + TS_ASSERT_EQUALS( + printedQueryTree("\"A,1\""), + + "\"A,1\"\n" + "\"A,1\"\n" + ); + } + + void test_token_query_with_ids() + { + TS_ASSERT_EQUALS( + printedQueryTree("\"A,1,2\""), + + "\"A,1,2\"\n" + "\"A,1,2\"\n" + ); + } + void test_operator_not_query() { TS_ASSERT_EQUALS( printedQueryTree("!field"), - "! field \n" + "! field\n" "!\n" " field\n" ); @@ -87,7 +106,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree("!!field"), - "! ! field \n" + "! ! field\n" "!\n" " !\n" " field\n" @@ -117,7 +136,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree("\"A\".\"B\""), - "\" A \" . \" B \" \n" + "\"A\" . \"B\"\n" " \"A\"\n" ".\n" " \"B\"\n" @@ -129,7 +148,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree("\"A\"."), - "\" A \" . INVALID\n" + "\"A\" . INVALID\n" " \"A\"\n" ". INVALID\n" ); @@ -144,7 +163,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree(".\"A\""), - ". \" A \" INVALID\n" + ". \"A\" INVALID\n" ". INVALID\n" " \"A\"\n" ); @@ -152,7 +171,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree("\"A\"..\"B\""), - "\" A \" . . \" B \" INVALID\n" + "\"A\" . . \"B\" INVALID\n" " \"A\"\n" ".\n" " . INVALID\n" @@ -165,7 +184,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree("\"A\":\"B\""), - "\" A \" : \" B \" \n" + "\"A\" : \"B\"\n" " \"A\"\n" ":\n" " \"B\"\n" @@ -177,7 +196,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree("\"A\"&\"B\""), - "\" A \" & \" B \" \n" + "\"A\" & \"B\"\n" " \"A\"\n" "&\n" " \"B\"\n" @@ -189,7 +208,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree("\"A\"|\"B\""), - "\" A \" | \" B \" \n" + "\"A\" | \"B\"\n" " \"A\"\n" "|\n" " \"B\"\n" @@ -201,14 +220,14 @@ public: TS_ASSERT_EQUALS( printedQueryTree("(\"A\")"), - "( \" A \" ) \n" + "( \"A\" )\n" "(\"A\")\n" ); TS_ASSERT_EQUALS( printedQueryTree("(\"A\"|\"B\")"), - "( \" A \" | \" B \" ) \n" + "( \"A\" | \"B\" )\n" " \"A\"\n" "(|)\n" " \"B\"\n" @@ -226,14 +245,14 @@ public: TS_ASSERT_EQUALS( printedQueryTree("(\"A\""), - "( \" A \" INVALID\n" + "( \"A\" INVALID\n" "(\"A\") INVALID\n" ); TS_ASSERT_EQUALS( printedQueryTree("\"A\")"), - "\" A \" ) INVALID\n" + "\"A\" ) INVALID\n" ); TS_ASSERT_EQUALS( @@ -248,7 +267,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree("\"A\"(\"B\")"), - "\" A \" ( \" B \" ) \n" + "\"A\" ( \"B\" )\n" " \"A\"\n" ".\n" " (\"B\")\n" @@ -260,7 +279,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree("!method.!const"), - "! method . ! const \n" + "! method . ! const\n" " !\n" " method\n" ".\n" @@ -274,7 +293,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree("namespace.class:method"), - "namespace . class : method \n" + "namespace . class : method\n" " namespace\n" " .\n" " class\n" @@ -288,7 +307,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree("class:method|field"), - "class : method | field \n" + "class : method | field\n" " class\n" " :\n" " method\n" @@ -302,7 +321,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree("namespace.(class:method)"), - "namespace . ( class : method ) \n" + "namespace . ( class : method )\n" " namespace\n" ".\n" " class\n" @@ -313,7 +332,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree("class:(method|field)"), - "class : ( method | field ) \n" + "class : ( method | field )\n" " class\n" ":\n" " method\n" @@ -327,7 +346,7 @@ public: TS_ASSERT_EQUALS( printedQueryTree(" \"Field \":(method | field) .const | public "), - "\" Field \" : ( method | field ) . const | public \n" + "\"Field\" : ( method | field ) . const | public\n" " \"Field\"\n" " :\n" " method\n" diff --git a/src/test/SearchIndexTestSuite.h b/src/test/SearchIndexTestSuite.h index 1d86581d..b7d8be12 100644 --- a/src/test/SearchIndexTestSuite.h +++ b/src/test/SearchIndexTestSuite.h @@ -102,7 +102,7 @@ public: std::vector matches = index.findFuzzyMatches("u"); TS_ASSERT_EQUALS(1, matches.size()); - TS_ASSERT_EQUALS("util", matches[0].node->getName()); + TS_ASSERT_EQUALS("util", matches[0].fullName); TS_ASSERT_EQUALS(1, matches[0].indices.size()); TS_ASSERT_EQUALS(0, matches[0].indices[0]); @@ -120,14 +120,14 @@ public: std::vector matches = index.findFuzzyMatches("t"); TS_ASSERT_EQUALS(2, matches.size()); - TS_ASSERT_EQUALS("MATH", matches[0].node->getName()); - TS_ASSERT_EQUALS("util", matches[1].node->getName()); + TS_ASSERT_EQUALS("MATH", matches[0].fullName); + TS_ASSERT_EQUALS("util", matches[1].fullName); matches = index.findFuzzyMatches("T"); TS_ASSERT_EQUALS(2, matches.size()); - TS_ASSERT_EQUALS("MATH", matches[0].node->getName()); - TS_ASSERT_EQUALS("util", matches[1].node->getName()); + TS_ASSERT_EQUALS("MATH", matches[0].fullName); + TS_ASSERT_EQUALS("util", matches[1].fullName); } void test_fuzzy_matching_wheighs_by_distance_and_alphabet() @@ -140,9 +140,9 @@ public: std::vector matches = index.findFuzzyMatches("t"); TS_ASSERT_EQUALS(3, matches.size()); - TS_ASSERT_EQUALS("string", matches[0].node->getName()); - TS_ASSERT_EQUALS("util", matches[1].node->getName()); - TS_ASSERT_EQUALS("math", matches[2].node->getName()); + TS_ASSERT_EQUALS("string", matches[0].fullName); + TS_ASSERT_EQUALS("util", matches[1].fullName); + TS_ASSERT_EQUALS("math", matches[2].fullName); TS_ASSERT_EQUALS(1, matches[0].indices.size()); TS_ASSERT_EQUALS(1, matches[0].indices[0]); @@ -163,8 +163,8 @@ public: std::vector matches = index.findFuzzyMatches("t"); TS_ASSERT_EQUALS(2, matches.size()); - TS_ASSERT_EQUALS("uTil", matches[0].node->getName()); - TS_ASSERT_EQUALS("string", matches[1].node->getName()); + TS_ASSERT_EQUALS("uTil", matches[0].fullName); + TS_ASSERT_EQUALS("string", matches[1].fullName); } void test_fuzzy_matching_wheighs_higher_on_consecutive_letters() @@ -176,8 +176,8 @@ public: std::vector matches = index.findFuzzyMatches("abc"); TS_ASSERT_EQUALS(2, matches.size()); - TS_ASSERT_EQUALS("ocbaabc", matches[0].node->getName()); - TS_ASSERT_EQUALS("oaabbcc", matches[1].node->getName()); + TS_ASSERT_EQUALS("ocbaabc", matches[0].fullName); + TS_ASSERT_EQUALS("oaabbcc", matches[1].fullName); } void test_fuzzy_matching_in_hierarchy() @@ -190,13 +190,13 @@ public: std::vector matches = index.findFuzzyMatches("t"); TS_ASSERT_EQUALS(1, matches.size()); - TS_ASSERT_EQUALS("util", matches[0].node->getName()); + TS_ASSERT_EQUALS("util", matches[0].fullName); matches = index.findFuzzyMatches("uml"); TS_ASSERT_EQUALS(2, matches.size()); - TS_ASSERT_EQUALS("floor", matches[0].node->getName()); - TS_ASSERT_EQUALS("ceil", matches[1].node->getName()); + TS_ASSERT_EQUALS("util::math::floor", matches[0].fullName); + TS_ASSERT_EQUALS("util::math::ceil", matches[1].fullName); } void test_fuzzy_matching_in_hierarchy_respects_collin() @@ -209,13 +209,13 @@ public: std::vector matches = index.findFuzzyMatches("u:i"); TS_ASSERT_EQUALS(2, matches.size()); - TS_ASSERT_EQUALS("string", matches[0].node->getName()); - TS_ASSERT_EQUALS("ceil", matches[1].node->getName()); + TS_ASSERT_EQUALS("util::string", matches[0].fullName); + TS_ASSERT_EQUALS("util::math::ceil", matches[1].fullName); matches = index.findFuzzyMatches("u:t:i"); TS_ASSERT_EQUALS(1, matches.size()); - TS_ASSERT_EQUALS("ceil", matches[0].node->getName()); + TS_ASSERT_EQUALS("util::math::ceil", matches[0].fullName); } void test_fuzzy_matching_in_hierarchy_weighs_front_letters_higher() @@ -227,8 +227,8 @@ public: std::vector matches = index.findFuzzyMatches("g"); TS_ASSERT_EQUALS(2, matches.size()); - TS_ASSERT_EQUALS("ghi", matches[0].node->getName()); - TS_ASSERT_EQUALS("hgi", matches[1].node->getName()); + TS_ASSERT_EQUALS("abc::dfe::ghi", matches[0].fullName); + TS_ASSERT_EQUALS("abc::hgi", matches[1].fullName); } void test_fuzzy_matching_with_defined_start_node() @@ -241,7 +241,7 @@ public: std::vector matches = index.findFuzzyMatches("\"math\"c"); TS_ASSERT_EQUALS(1, matches.size()); - TS_ASSERT_EQUALS("ceil", matches[0].node->getName()); + TS_ASSERT_EQUALS("math::ceil", matches[0].fullName); matches = index.findFuzzyMatches("\"mathc"); TS_ASSERT_EQUALS(0, matches.size()); diff --git a/src/test/UtilityStringTestSuite.h b/src/test/UtilityStringTestSuite.h index e6123931..4e29f849 100644 --- a/src/test/UtilityStringTestSuite.h +++ b/src/test/UtilityStringTestSuite.h @@ -71,6 +71,46 @@ public: TS_ASSERT_EQUALS(result[2], ""); } + void test_join_with_char_delimiter() + { + std::vector list; + list.push_back("A"); + list.push_back("B"); + list.push_back("C"); + + std::string result = utility::join >(list, ','); + TS_ASSERT_EQUALS(result, "A,B,C"); + } + + void test_join_with_string_delimiter() + { + std::vector list; + list.push_back("A"); + list.push_back("B"); + list.push_back("C"); + + std::string result = utility::join >(list, "=="); + TS_ASSERT_EQUALS(result, "A==B==C"); + } + + void test_join_on_empty_list() + { + std::vector list; + std::string result = utility::join >(list, ','); + TS_ASSERT_EQUALS(result, ""); + } + + void test_join_with_empty_strings_in_list() + { + std::vector list; + list.push_back("A"); + list.push_back(""); + list.push_back(""); + + std::string result = utility::join >(list, ':'); + TS_ASSERT_EQUALS(result, "A::"); + } + void test_tokenize_with_string() { std::vector result = utility::tokenize >("A->B->C", "->");