#include "UndoRedoController.h" #include "MessageFlushUpdates.h" #include "MessageSearch.h" #include "utility.h" #include "Application.h" #include "Project.h" #include "StorageAccess.h" #include "UndoRedoView.h" UndoRedoController::UndoRedoController(StorageAccess* storageAccess): m_storageAccess(storageAccess) { m_iterator = m_list.end(); } Id UndoRedoController::getSchedulerId() const { return Controller::getTabId(); } UndoRedoView* UndoRedoController::getView() { return Controller::getView(); } void UndoRedoController::clear() { m_list.clear(); m_iterator = m_list.begin(); m_historyOffset = 0; m_history.clear(); updateHistoryMenu(nullptr); updateHistory(); getView()->setUndoButtonEnabled(false); getView()->setRedoButtonEnabled(false); } UndoRedoController::Command::Command(std::shared_ptr message, Order order, bool replayLastOnly) : message(message), order(order), replayLastOnly(replayLastOnly) { } void UndoRedoController::handleMessage(MessageActivateErrors* message) { if (sameMessageTypeAsLast(message) && static_cast(lastMessage())->filter == message->filter && static_cast(lastMessage())->file == message->file) { return; } Command command(std::make_shared(*message), Command::ORDER_ACTIVATE); processCommand(command); } void UndoRedoController::handleMessage(MessageActivateFullTextSearch* message) { if (sameMessageTypeAsLast(message) && static_cast(lastMessage())->searchTerm == message->searchTerm && static_cast(lastMessage())->caseSensitive == message->caseSensitive) { return; } Command command( std::make_shared(*message), Command::ORDER_ACTIVATE); processCommand(command); } void UndoRedoController::handleMessage(MessageActivateLegend* message) { if (sameMessageTypeAsLast(message)) { return; } Command command(std::make_shared(*message), Command::ORDER_ACTIVATE); processCommand(command); } void UndoRedoController::handleMessage(MessageActivateLocalSymbols* message) { if (sameMessageTypeAsLast(message)) { static_cast(lastMessage())->symbolIds = message->symbolIds; return; } Command command( std::make_shared(*message), Command::ORDER_VIEW, true); processCommand(command); } void UndoRedoController::handleMessage(MessageActivateOverview* message) { if (sameMessageTypeAsLast(message) && static_cast(lastMessage())->acceptedNodeTypes == message->acceptedNodeTypes) { return; } Command command(std::make_shared(*message), Command::ORDER_ACTIVATE); processCommand(command); } void UndoRedoController::handleMessage(MessageActivateTokens* message) { if (sameMessageTypeAsLast(message) && static_cast(lastMessage())->tokenIds == message->tokenIds) { return; } Command command( std::make_shared(*message), message->isEdge ? Command::ORDER_ADAPT : Command::ORDER_ACTIVATE); processCommand(command); } void UndoRedoController::handleMessage(MessageActivateTrail* message) { if (sameMessageTypeAsLast(message) && static_cast(lastMessage())->originId == message->originId && static_cast(lastMessage())->targetId == message->targetId) { return; } Command command( std::make_shared(*message), message->custom ? Command::ORDER_ACTIVATE : Command::ORDER_ADAPT); processCommand(command); } void UndoRedoController::handleMessage(MessageActivateTrailEdge* message) { if (sameMessageTypeAsLast(message) && static_cast(lastMessage())->edgeIds == message->edgeIds) { return; } Command command(std::make_shared(*message), Command::ORDER_ADAPT, true); processCommand(command); } void UndoRedoController::handleMessage(MessageChangeFileView* message) { Command command( std::make_shared(*message), message->switchesViewMode ? Command::ORDER_ADAPT : Command::ORDER_VIEW); processCommand(command); } void UndoRedoController::handleMessage(MessageCodeShowDefinition* message) { if (sameMessageTypeAsLast(message) && static_cast(lastMessage())->nodeId == message->nodeId) { return; } Command command(std::make_shared(*message), Command::ORDER_ADAPT); processCommand(command); } void UndoRedoController::handleMessage(MessageDeactivateEdge* message) { if (sameMessageTypeAsLast(message) && static_cast(lastMessage())->scrollToDefinition == message->scrollToDefinition) { return; } Command command(std::make_shared(*message), Command::ORDER_ADAPT); processCommand(command); } void UndoRedoController::handleMessage(MessageFocusChanged* message) { if (sameMessageTypeAsLast(message) && static_cast(lastMessage())->tokenOrLocationId == message->tokenOrLocationId) { return; } Command command(std::make_shared(*message), Command::ORDER_VIEW, true); processCommand(command); } void UndoRedoController::handleMessage(MessageGraphNodeBundleSplit* message) { Command command(std::make_shared(*message), Command::ORDER_ADAPT); processCommand(command); } void UndoRedoController::handleMessage(MessageGraphNodeExpand* message) { Command command(std::make_shared(*message), Command::ORDER_VIEW); processCommand(command); } void UndoRedoController::handleMessage(MessageGraphNodeHide* message) { Command command(std::make_shared(*message), Command::ORDER_ADAPT); processCommand(command); } void UndoRedoController::handleMessage(MessageGraphNodeMove* message) { Command command(std::make_shared(*message), Command::ORDER_VIEW); processCommand(command); } void UndoRedoController::handleMessage(MessageHistoryRedo* message) { if (m_iterator == m_list.end()) { return; } std::list::iterator oldIterator = m_iterator; std::advance(m_iterator, 1); while (m_iterator != m_list.end() && m_iterator->order == Command::ORDER_VIEW) { std::advance(m_iterator, 1); } getView()->setUndoButtonEnabled(true); if (m_iterator == m_list.end()) { getView()->setRedoButtonEnabled(false); } replayCommands(oldIterator); updateHistory(); } void UndoRedoController::handleMessage(MessageHistoryToPosition* message) { size_t index = 0; const size_t activeIndex = message->index + m_historyOffset; for (std::list::reverse_iterator rit = m_list.rbegin(); rit != m_list.rend(); rit++) { if (rit->order == Command::ORDER_ACTIVATE) { if (index == activeIndex) { std::list::iterator it = --(rit.base()); std::list::iterator start = it; do { std::advance(it, 1); } while (it != m_list.end() && it->order != Command::ORDER_ACTIVATE); m_iterator = it; if (start != m_iterator) { replayCommands(start); } else { replayCommand(m_iterator); MessageFlushUpdates msg(false); msg.setSchedulerId(getSchedulerId()); msg.dispatch(); } } index++; if (index > activeIndex + 1) { break; } } } getView()->setUndoButtonEnabled(index > activeIndex + 1); getView()->setRedoButtonEnabled( m_iterator != m_list.end() && m_iterator->order == Command::ORDER_ACTIVATE); updateHistory(); } void UndoRedoController::handleMessage(MessageHistoryUndo* message) { if (!m_list.size()) { return; } // return to last non view command std::list::iterator it = m_iterator; while (std::prev(it)->order == Command::ORDER_VIEW) { std::advance(it, -1); } std::advance(it, -1); // disable undo button if there is no non view command till the first command std::list::iterator it2 = std::prev(it); while (it2->order == Command::ORDER_VIEW) { std::advance(it2, -1); } if (it2 == m_list.begin()) { getView()->setUndoButtonEnabled(false); } // abort if first command is reached if (it == m_list.begin()) { return; } getView()->setRedoButtonEnabled(true); m_iterator = it; replayCommands(); updateHistory(); } void UndoRedoController::handleMessage(MessageIndexingFinished* message) { std::list newList; for (const Command& command: m_list) { if (command.order == Command::ORDER_ACTIVATE) { MessageActivateTokens* msg = dynamic_cast(command.message.get()); if (msg) { if (msg->isBundledEdges) { continue; } msg->isFromSearch = false; } newList.insert(newList.end(), command); } } m_list = newList; m_iterator = m_list.end(); } void UndoRedoController::handleMessage(MessageRefreshUIState* message) { std::list::iterator startIterator = m_iterator; do { std::advance(startIterator, -1); } while (startIterator != m_list.begin() && startIterator->order != Command::ORDER_ACTIVATE); if (startIterator == m_list.begin() || (message->isAfterIndexing && dynamic_cast(startIterator->message.get()) && m_storageAccess->getErrorCount().total == 0)) { MessageActivateOverview msg; msg.setSchedulerId(getSchedulerId()); msg.dispatch(); } else { replayCommands(); } } void UndoRedoController::handleMessage(MessageScrollCode* message) { if (sameMessageTypeAsLast(message)) { static_cast(lastMessage())->value = message->value; return; } Command command(std::make_shared(*message), Command::ORDER_VIEW, true); processCommand(command); } void UndoRedoController::handleMessage(MessageScrollGraph* message) { if (sameMessageTypeAsLast(message)) { static_cast(lastMessage())->xValue = message->xValue; static_cast(lastMessage())->yValue = message->yValue; return; } Command command(std::make_shared(*message), Command::ORDER_VIEW, true); processCommand(command); } void UndoRedoController::handleMessage(MessageShowError* message) { if (sameMessageTypeAsLast(message) && static_cast(lastMessage())->errorId == message->errorId) { return; } Command command(std::make_shared(*message), Command::ORDER_ADAPT); processCommand(command); } void UndoRedoController::handleMessage(MessageShowReference* message) { if (sameMessageTypeAsLast(message) && static_cast(lastMessage())->refIndex == message->refIndex) { return; } Command command(std::make_shared(*message), Command::ORDER_VIEW, true); processCommand(command); } void UndoRedoController::handleMessage(MessageShowScope* message) { Command command(std::make_shared(*message), Command::ORDER_VIEW); processCommand(command); } void UndoRedoController::replayCommands() { std::list::iterator startIterator = m_iterator; do { std::advance(startIterator, -1); } while (startIterator != m_list.begin() && startIterator->order != Command::ORDER_ACTIVATE); replayCommands(startIterator); } void UndoRedoController::replayCommands(std::list::iterator it) { std::map::iterator> lastOfType; std::list::iterator at = it; while (at != m_iterator) { if (at->replayLastOnly) { lastOfType[at->message->getType()] = at; } std::advance(at, 1); } bool keepsContent = true; while (it != m_iterator) { if (!it->replayLastOnly || lastOfType[it->message->getType()] == it) { replayCommand(it); if (!it->message->keepContent()) { keepsContent = false; } } std::advance(it, 1); } MessageFlushUpdates msg(keepsContent); msg.setSchedulerId(getSchedulerId()); msg.dispatch(); } void UndoRedoController::replayCommand(std::list::iterator it) { std::shared_ptr m = it->message; if (m->getType() == MessageActivateTokens::getStaticType()) { MessageActivateTokens* msg = dynamic_cast(m.get()); if (!msg->isEdge && !msg->isBundledEdges) { std::vector matches = msg->getSearchMatches(); msg->searchMatches.clear(); msg->tokenIds.clear(); for (SearchMatch match: matches) { // TODO: replace duplicate main definition fix with better solution if (match.nodeType.getKind() != NODE_FUNCTION || !match.tokenNames.size() || match.tokenNames[0].getRawName() != L"main") { match.tokenIds = m_storageAccess->getNodeIdsForNameHierarchies(match.tokenNames); } if (!match.tokenIds.size()) { match.nodeType = NodeType(NODE_SYMBOL); } utility::append(msg->tokenIds, match.tokenIds); msg->searchMatches.push_back(match); } } } else if (m->getType() == MessageActivateErrors::getStaticType()) { std::shared_ptr currentProject = Application::getInstance()->getCurrentProject(); if (currentProject && currentProject->isIndexing()) { Application::getInstance()->handleDialog(L"Errors cannot be activated while indexing."); ErrorFilter filter; filter.error = false; filter.fatal = false; filter.unindexedError = false; filter.unindexedFatal = false; MessageActivateErrors msg(filter); msg.setSchedulerId(getSchedulerId()); msg.setIsReplayed(true); msg.setIsLast(it == std::prev(m_iterator)); msg.dispatch(); return; } } m->setIsReplayed(true); m->setIsLast(it == std::prev(m_iterator)); m->dispatch(); m->setIsReplayed(false); m->setIsLast(false); } void UndoRedoController::processCommand(Command command) { if (!command.message->isReplayed()) { if (command.order != Command::ORDER_ACTIVATE && m_iterator == m_list.begin()) { return; } if (command.order == Command::ORDER_ACTIVATE && command.message->keepContent()) { command.order = Command::ORDER_ADAPT; } if (command.order == Command::ORDER_ACTIVATE) { m_iterator = m_list.erase(m_iterator, m_list.end()); } else if (command.order == Command::ORDER_ADAPT) { std::list::iterator end = m_iterator; while (end != m_list.end()) { if (end->order == Command::ORDER_ACTIVATE) { break; } std::advance(end, 1); } m_iterator = m_list.erase(m_iterator, end); } m_list.insert(m_iterator, command); if (command.order != Command::ORDER_VIEW) { if (m_list.begin() != std::prev(m_iterator)) { getView()->setUndoButtonEnabled(true); } if (m_list.end() == m_iterator) { getView()->setRedoButtonEnabled(false); } } } if (command.order == Command::ORDER_ACTIVATE) { updateHistoryMenu(command.message); updateHistory(); } } bool UndoRedoController::sameMessageTypeAsLast(MessageBase* message) const { if (message->isReplayed()) { return false; } if (!m_list.size() || m_list.begin() == m_iterator) { return false; } return lastMessage()->getType() == message->getType(); } MessageBase* UndoRedoController::lastMessage() const { return std::prev(m_iterator)->message.get(); } void UndoRedoController::updateHistoryMenu(std::shared_ptr message) { Application::getInstance()->updateHistoryMenu(message); } void UndoRedoController::updateHistory() { const size_t historyListSize = 50; std::vector historyListMatches; int index = 0; int currentIndex = -1; m_historyOffset = 0; for (std::list::const_reverse_iterator it = m_list.rbegin(); it != m_list.rend(); it++) { if (m_iterator == it.base()) { currentIndex = index; } if (it->order == Command::ORDER_ACTIVATE && dynamic_cast(it->message.get())) { index++; std::vector matches = dynamic_cast(it->message.get())->getSearchMatches(); if (!matches.size() || matches[0].text.empty()) { continue; } historyListMatches.push_back(matches[0]); if (historyListMatches.size() > historyListSize) { historyListMatches.erase(historyListMatches.begin()); m_historyOffset++; } if (historyListMatches.size() == historyListSize && currentIndex != -1 && currentIndex - m_historyOffset != historyListSize - 1) { break; } } } getView()->updateHistory(historyListMatches, currentIndex - m_historyOffset); } void UndoRedoController::dump() const { std::cout << "\nUndo Redo Stack:\n----------\n"; std::list::const_iterator it = m_list.begin(); while (it != m_list.end()) { switch (it->order) { case Command::ORDER_VIEW: std::cout << "\t"; case Command::ORDER_ADAPT: std::cout << "\t"; case Command::ORDER_ACTIVATE: break; } std::cout << it->message->getType(); if (it == m_iterator) { std::cout << " <-"; } std::cout << std::endl; std::advance(it, 1); } if (m_list.end() == m_iterator) { std::cout << "<-" << std::endl; } std::cout << "----------" << std::endl; }