#include "component/controller/CodeController.h" #include #include "utility/messaging/type/MessageStatus.h" #include "utility/text/TextAccess.h" #include "utility/utility.h" #include "utility/utilityString.h" #include "data/access/StorageAccess.h" #include "data/location/TokenLocation.h" #include "data/location/TokenLocationCollection.h" #include "data/location/TokenLocationFile.h" #include "data/location/TokenLocationLine.h" #include "settings/ApplicationSettings.h" #include "settings/ProjectSettings.h" CodeController::CodeController(StorageAccess* storageAccess) : m_storageAccess(storageAccess) { } CodeController::~CodeController() { } const uint CodeController::s_lineRadius = 2; void CodeController::handleMessage(MessageActivateAll* message) { std::vector errors; std::vector snippets = getSnippetsForErrorLocations(&errors); StorageStats stats = m_storageAccess->getStorageStats(); CodeSnippetParams statsSnippet; statsSnippet.startLineNumber = 1; statsSnippet.endLineNumber = 1; statsSnippet.reduced = true; statsSnippet.locationFile = std::make_shared(FilePath()); statsSnippet.locationFile->isWholeCopy = true; std::vector description = getProjectDescription(statsSnippet.locationFile.get()); std::stringstream ss; ss << "\n"; ss << "\tProject: " + ProjectSettings::getInstance()->getFilePath().withoutExtension().fileName() + "\n"; if (description.size()) { ss << "\n"; for (const std::string& line : description) { ss << line + "\n"; } } ss << "\n"; ss << "\t" + std::to_string(stats.fileCount) + " files\n"; ss << "\t" + std::to_string(stats.fileLOCCount) + " lines of code\n"; ss << "\n"; ss << "\t" + std::to_string(stats.nodeCount) + " symbols\n"; ss << "\t" + std::to_string(stats.edgeCount) + " relations\n"; ss << "\n"; ss << "\t" + std::to_string(stats.errorCount.total) + " errors (" + std::to_string(stats.errorCount.fatal) + " fatal)\n"; ss << "\n"; if (stats.errorCount.total > 0) { ss << "\tWarning: The analysis may be incomplete as long as it yields fatal errors.\n"; ss << "\tTry resolving them and refresh the project.\n"; ss << "\n"; } statsSnippet.code = ss.str(); snippets.insert(snippets.begin(), statsSnippet); CodeView* view = getView(); view->setErrorInfos(errors); view->showCodeSnippets(snippets, std::vector()); showContents(message); } void CodeController::handleMessage(MessageActivateLocalSymbols* message) { CodeView* view = getView(); view->showActiveLocalSymbolIds(message->symbolIds); } void CodeController::handleMessage(MessageActivateTokens* message) { CodeView* view = getView(); view->setErrorInfos(std::vector()); std::vector activeTokenIds = message->tokenIds; Id declarationId = 0; // 0 means that no token is found. if (!message->isAggregation) { if (activeTokenIds.size() != 1) { view->clear(); return; } activeTokenIds = m_storageAccess->getActiveTokenIdsForId(activeTokenIds[0], &declarationId); } if (message->isEdge) { view->showFirstActiveSnippet(activeTokenIds, message->isLast()); } if (message->keepContent()) { view->showActiveTokenIds(activeTokenIds); } else { std::shared_ptr collection = m_storageAccess->getTokenLocationsForTokenIds(activeTokenIds); view->showCodeSnippets(getSnippetsForActiveTokenLocations(collection.get(), declarationId), activeTokenIds); size_t fileCount = collection->getTokenLocationFileCount(); size_t referenceCount = collection->getTokenLocationCount(); std::stringstream ss; ss << message->tokenIds.size() << ' '; ss << (message->tokenIds.size() == 1 ? "result" : "results"); if (fileCount > 0) { ss << " with " << referenceCount << ' '; ss << (referenceCount == 1 ? "reference" : "references"); ss << " in " << fileCount << ' '; ss << (fileCount == 1 ? "file" : "files"); } MessageStatus(ss.str()).dispatch(); } showContents(message); } void CodeController::handleMessage(MessageChangeFileView* message) { CodeView* view = getView(); switch (message->state) { case MessageChangeFileView::FILE_MINIMIZED: view->setFileState(message->filePath, CodeView::FILE_MINIMIZED); break; case MessageChangeFileView::FILE_SNIPPETS: if (message->needsData) { if (message->showErrors) { view->addCodeSnippets(getSnippetsForFile(message->locationFile), false); } else { view->addCodeSnippets(getSnippetsForActiveTokenLocationsInFile(message->locationFile), false); } } view->setFileState(message->filePath, CodeView::FILE_SNIPPETS); break; case MessageChangeFileView::FILE_MAXIMIZED: if (message->needsData) { CodeSnippetParams params; params.startLineNumber = 1; params.refCount = -1; std::shared_ptr textAccess = m_storageAccess->getFileContent(message->filePath); params.code = textAccess->getText(); params.modificationTime = m_storageAccess->getFileModificationTime(message->filePath); if (message->showErrors) { std::vector errors; TokenLocationCollection errorCollection = m_storageAccess->getErrorTokenLocations(&errors); params.locationFile = std::make_shared(*errorCollection.findTokenLocationFileByPath(message->filePath)); params.locationFile->isWholeCopy = true; } else { params.locationFile = m_storageAccess->getTokenLocationsForFile(message->filePath.str()); } getView()->showCodeFile(params); } view->setFileState(message->filePath, CodeView::FILE_MAXIMIZED); break; } showContents(message); } void CodeController::handleMessage(MessageFlushUpdates* message) { showContents(message); } void CodeController::handleMessage(MessageFocusIn* message) { getView()->focusTokenIds(message->tokenIds); } void CodeController::handleMessage(MessageFocusOut* message) { getView()->defocusTokenIds(); } void CodeController::handleMessage(MessageScrollCode* message) { if (message->isReplayed()) { getView()->scrollToValue(message->value); } } void CodeController::handleMessage(MessageShowErrors* message) { std::vector errors; std::vector snippets = getSnippetsForErrorLocations(&errors); CodeView* view = getView(); view->setErrorInfos(errors); view->showCodeSnippets(snippets, std::vector()); showContents(message); } void CodeController::handleMessage(MessageShowScope* message) { std::shared_ptr collection = m_storageAccess->getTokenLocationsForLocationIds(std::vector(1, message->scopeLocationId)); TokenLocation* location = collection->findTokenLocationById(message->scopeLocationId); if (!location || !location->isScopeTokenLocation() || !location->getOtherTokenLocation()) { LOG_ERROR("MessageShowScope did not contain a valid scope location id"); return; } std::vector snippets = getSnippetsForActiveTokenLocations(collection.get(), 0); if (snippets.size() != 1) { LOG_ERROR("MessageShowScope didn't result in one single snippet to be created"); return; } if (message->showErrors) { std::vector errors; std::vector errorSnippets = getSnippetsForErrorLocations(&errors); for (const CodeSnippetParams& error : errorSnippets) { if (error.locationFile->getFilePath() == snippets[0].locationFile->getFilePath()) { snippets[0].locationFile = error.locationFile; } } } getView()->addCodeSnippets(snippets, true); showContents(message); } CodeView* CodeController::getView() { return Controller::getView(); } void CodeController::showContents(MessageBase* message) { if (!message->isReplayed()) { getView()->showContents(); } } std::vector CodeController::getSnippetsForActiveTokenLocations( const TokenLocationCollection* collection, Id declarationId ) const { std::vector snippets; collection->forEachTokenLocationFile( [&](std::shared_ptr file) -> void { bool isDeclarationFile = false; file->forEachTokenLocation( [&](TokenLocation* location) { if (location->getTokenId() == declarationId) { isDeclarationFile = true; } } ); if (snippets.size() < 10 && (isDeclarationFile || collection->getTokenLocationFileCount() < 5 || file->isWholeCopy)) { std::vector fileSnippets = getSnippetsForActiveTokenLocationsInFile(file); for (CodeSnippetParams& snippet : fileSnippets) { snippet.isDeclaration = isDeclarationFile; } utility::append(snippets, fileSnippets); } else { CodeSnippetParams params; params.locationFile = file; params.refCount = file->getUnscopedStartTokenLocationCount(); params.modificationTime = m_storageAccess->getFileModificationTime(file->getFilePath()); params.isCollapsed = true; snippets.push_back(params); } } ); std::sort(snippets.begin(), snippets.end(), CodeSnippetParams::sort); return snippets; } std::vector CodeController::getSnippetsForActiveTokenLocationsInFile( std::shared_ptr activeTokenLocations ) const { std::shared_ptr fileLocations = m_storageAccess->getTokenLocationsForFile(activeTokenLocations->getFilePath().str()); std::vector fileSnippets = getSnippetsForFile(activeTokenLocations, fileLocations); if (!activeTokenLocations->isWholeCopy) { for (CodeSnippetParams& params : fileSnippets) { params.locationFile = fileLocations->getFilteredByLines(params.startLineNumber, params.endLineNumber); } } return fileSnippets; } std::vector CodeController::getSnippetsForFile(std::shared_ptr activeTokenLocations) const { return getSnippetsForFile(activeTokenLocations, m_storageAccess->getTokenLocationsForFile(activeTokenLocations->getFilePath().str())); } std::vector CodeController::getSnippetsForFile(std::shared_ptr activeTokenLocations, const std::shared_ptr fileLocations) const { std::shared_ptr textAccess = m_storageAccess->getFileContent(activeTokenLocations->getFilePath()); std::shared_ptr scopeLocations = std::make_shared(activeTokenLocations->getFilePath().str()); fileLocations->forEachStartTokenLocation( [&](TokenLocation* startLoc) -> void { if (startLoc->getType() == LOCATION_SCOPE) { TokenLocation* endLoc = startLoc->getOtherTokenLocation(); TokenLocation* scopeLoc = scopeLocations->addTokenLocation( startLoc->getId(), startLoc->getTokenId(), startLoc->getLineNumber(), startLoc->getColumnNumber(), endLoc->getLineNumber(), endLoc->getColumnNumber()); scopeLoc->setType(LOCATION_SCOPE); } } ); std::deque ranges; if (activeTokenLocations->isWholeCopy) { ranges.push_back(SnippetMerger::Range( SnippetMerger::Border(1, true), SnippetMerger::Border(textAccess->getLineCount(), true) )); } else { SnippetMerger fileScopedMerger(1, textAccess->getLineCount()); std::map> mergers; activeTokenLocations->forEachStartTokenLocation( [&](TokenLocation* location) { buildMergerHierarchy(location, scopeLocations, fileScopedMerger, mergers); } ); std::vector atomicRanges; m_storageAccess->getCommentLocationsInFile(activeTokenLocations->getFilePath())->forEachStartTokenLocation( [&](TokenLocation* location) { atomicRanges.push_back(SnippetMerger::Range( SnippetMerger::Border(location->getLineNumber(), false), SnippetMerger::Border(location->getOtherTokenLocation()->getLineNumber(), false) )); } ); atomicRanges = SnippetMerger::Range::mergeAdjacent(atomicRanges); ranges = fileScopedMerger.merge(atomicRanges); } const int snippetExpandRange = ApplicationSettings::getInstance()->getCodeSnippetExpandRange(); TimePoint fileModificationTime = m_storageAccess->getFileModificationTime(activeTokenLocations->getFilePath()); std::vector snippets; for (const SnippetMerger::Range& range: ranges) { CodeSnippetParams params; params.locationFile = activeTokenLocations; params.refCount = activeTokenLocations->getUnscopedStartTokenLocationCount(); params.startLineNumber = std::max(1, range.start.row - (range.start.strong ? 0 : snippetExpandRange)); params.endLineNumber = std::min(textAccess->getLineCount(), range.end.row + (range.end.strong ? 0 : snippetExpandRange)); params.modificationTime = fileModificationTime; std::shared_ptr tempFile = m_storageAccess->getTokenLocationsForLinesInFile(activeTokenLocations->getFilePath().str(), params.startLineNumber, params.endLineNumber); TokenLocationLine* firstUsedLine = nullptr; for (size_t i = params.startLineNumber; i <= params.endLineNumber && firstUsedLine == nullptr; i++) { firstUsedLine = tempFile->findTokenLocationLineByNumber(i); } params.titleId = 0; if (firstUsedLine && firstUsedLine->getTokenLocations().size()) { getTokenLocationOfParentScope( firstUsedLine->getTokenLocations().begin()->second.get(), scopeLocations )->forEachStartTokenLocation( // this TokenLocationFile only contains a single StartTokenLocation. [&](TokenLocation* location) { params.title = m_storageAccess->getNameHierarchyForNodeWithId(location->getTokenId()).getQualifiedName(); params.titleId = location->getId(); } ); } if (!activeTokenLocations->isWholeCopy && params.titleId == 0) { params.title = activeTokenLocations->getFilePath().str(); } TokenLocationLine* lastUsedLine = nullptr; for (size_t i = params.endLineNumber; i >= params.startLineNumber && lastUsedLine == nullptr; i--) { lastUsedLine = tempFile->findTokenLocationLineByNumber(i); } params.footerId = 0; if (lastUsedLine && lastUsedLine->getTokenLocations().size()) { getTokenLocationOfParentScope( lastUsedLine->getTokenLocations().begin()->second.get(), scopeLocations )->forEachStartTokenLocation( // this TokenLocationFile only contains a single StartTokenLocation. [&](TokenLocation* location) { params.footer = m_storageAccess->getNameHierarchyForNodeWithId(location->getTokenId()).getQualifiedName(); params.footerId = location->getId(); } ); } for (const std::string& line: textAccess->getLines(params.startLineNumber, params.endLineNumber)) { params.code += line; } snippets.push_back(params); } return snippets; } std::shared_ptr CodeController::buildMergerHierarchy( TokenLocation* location, std::shared_ptr scopeLocations, SnippetMerger& fileScopedMerger, std::map>& mergers) const { const TokenLocation* currentLocation = location; std::shared_ptr currentMerger = std::make_shared( currentLocation->getStartTokenLocation()->getLineNumber(), currentLocation->getEndTokenLocation()->getLineNumber() ); std::shared_ptr locationFile = getTokenLocationOfParentScope(currentLocation, scopeLocations); if (locationFile->getTokenLocationLineCount() == 0) { fileScopedMerger.addChild(currentMerger); return currentMerger; } std::shared_ptr nextMerger; locationFile->forEachStartTokenLocation( // contains just 1 start location [&](TokenLocation* scopeLocation) { std::map>::iterator it = mergers.find(scopeLocation->getId()); if (it == mergers.end()) { nextMerger = buildMergerHierarchy(scopeLocation, scopeLocations, fileScopedMerger, mergers); mergers[scopeLocation->getId()] = nextMerger; } else { nextMerger = it->second; } } ); nextMerger->addChild(currentMerger); return currentMerger; } std::shared_ptr CodeController::getTokenLocationOfParentScope(const TokenLocation* location, std::shared_ptr scopeLocations) const { const TokenLocation* parent = location; const FilePath filePath = location->getFilePath(); scopeLocations->forEachStartTokenLocation( [&](TokenLocation* tokenLocation) -> void { if ((*tokenLocation) < *(location->getStartTokenLocation()) && (*tokenLocation->getEndTokenLocation()) > *(location->getEndTokenLocation())) { if (parent == location) { parent = tokenLocation; } // since tokenLocation is a start location the > location indicates the scope that is closer to the child. else if ((*tokenLocation) > *parent) { parent = tokenLocation; } } } ); std::shared_ptr file = std::make_shared(filePath); if (parent != location) { file->addTokenLocationAsPlainCopy(parent); file->addTokenLocationAsPlainCopy(parent->getOtherTokenLocation()); } return file; } std::vector CodeController::getSnippetsForErrorLocations( std::vector* errors) const { TokenLocationCollection errorCollection = m_storageAccess->getErrorTokenLocations(errors); std::vector snippets; errorCollection.forEachTokenLocationFile( [&](std::shared_ptr file) -> void { if (snippets.size() < 10) { std::vector fileSnippets = getSnippetsForFile(file); snippets.insert(snippets.end(), fileSnippets.begin(), fileSnippets.end()); } else { CodeSnippetParams params; params.locationFile = file; params.refCount = file->getUnscopedStartTokenLocationCount(); params.modificationTime = m_storageAccess->getFileModificationTime(file->getFilePath()); params.isCollapsed = true; snippets.push_back(params); } } ); return snippets; } std::vector CodeController::getProjectDescription(TokenLocationFile* locationFile) const { std::string description = ProjectSettings::getInstance()->getDescription(); if (!description.size()) { return std::vector(); } // todo fixme: this split currently prevents the next step from recognizing multi level name hierarchies. std::vector lines = utility::splitToVector(description, "\\n"); size_t startLineNumber = 4; for (size_t i = 0; i < lines.size(); i++) { std::string line = "\t" + lines[i]; line = utility::replace(line, "\\t", "\t"); line = utility::replace(line, "\\r", "\r"); size_t pos = 0; while (pos != std::string::npos) { size_t posA = line.find('[', pos); size_t posB = line.find(']', posA); if (posA == std::string::npos || posB == std::string::npos) { break; } std::string serializedName = line.substr(posA + 1, posB - posA - 1); NameHierarchy nameHierarchy = NameHierarchy::deserialize(serializedName); Id tokenId = m_storageAccess->getIdForNodeWithNameHierarchy(nameHierarchy); if (tokenId > 0) { std::string nameString = nameHierarchy.getQualifiedName(); line.replace(posA, posB - posA + 1, nameString); locationFile->addTokenLocation( 0, tokenId, startLineNumber + i, posA + 1, startLineNumber + i, posA + nameString.size() ); } pos = posA + serializedName.size(); } lines[i] = line; } return lines; }