From 71b3bdb26b702354ad59eefa8635914e9987f8e4 Mon Sep 17 00:00:00 2001 From: Eberhard Graether Date: Mon, 5 Jun 2017 23:34:20 +0200 Subject: [PATCH] logic: Improved code view performance * Don't set stylesheet for every token activation * Streamlined code area creation steps * Delay most syntax highlighting until lines become visible --- bin/app/data/gui/code_view/code_view.css | 5 - .../component/controller/CodeController.cpp | 4 + src/lib_gui/qt/element/QtCodeArea.cpp | 81 +++++----- src/lib_gui/qt/element/QtCodeArea.h | 3 +- src/lib_gui/qt/element/QtCodeFile.cpp | 18 ++- src/lib_gui/qt/element/QtCodeNavigateable.cpp | 36 +++-- src/lib_gui/qt/element/QtCodeNavigator.cpp | 4 +- src/lib_gui/qt/element/QtCodeSnippet.cpp | 3 +- src/lib_gui/qt/utility/QtHighlighter.cpp | 153 +++++++++++++----- src/lib_gui/qt/utility/QtHighlighter.h | 19 ++- src/lib_gui/qt/view/QtCodeView.cpp | 12 +- src/lib_gui/qt/view/QtCodeView.h | 2 - 12 files changed, 217 insertions(+), 123 deletions(-) diff --git a/bin/app/data/gui/code_view/code_view.css b/bin/app/data/gui/code_view/code_view.css index ceea5e3e..a66141b8 100644 --- a/bin/app/data/gui/code_view/code_view.css +++ b/bin/app/data/gui/code_view/code_view.css @@ -141,11 +141,6 @@ font-size: px; } -#code_file #code_snippet[isFirst=true] { - border: none; - padding-top: 0px; -} - #code_file #code_snippet #scope_name, #code_file #code_snippet #dots { background-color: ; border: none; diff --git a/src/lib/component/controller/CodeController.cpp b/src/lib/component/controller/CodeController.cpp index 72095b78..24c8ccae 100644 --- a/src/lib/component/controller/CodeController.cpp +++ b/src/lib/component/controller/CodeController.cpp @@ -400,6 +400,8 @@ void CodeController::clear() void CodeController::expandVisibleSnippets(std::vector* snippets) const { + TRACE(); + bool inListMode = getView()->isInListMode(); size_t filesToExpand = inListMode ? std::min(int(snippets->size()), 3) : 1; @@ -549,6 +551,8 @@ std::vector CodeController::getSnippetsForCollection( std::shared_ptr collection, bool addSourceLocations ) const { + TRACE(); + std::vector snippets; collection->forEachSourceLocationFile( diff --git a/src/lib_gui/qt/element/QtCodeArea.cpp b/src/lib_gui/qt/element/QtCodeArea.cpp index 8d9ed283..7e32e47f 100644 --- a/src/lib_gui/qt/element/QtCodeArea.cpp +++ b/src/lib_gui/qt/element/QtCodeArea.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "utility/messaging/type/MessageActivateLocalSymbols.h" @@ -110,22 +111,7 @@ QtCodeArea::QtCodeArea( viewport()->setCursor(Qt::ArrowCursor); - FilePath path = m_locationFile->getFilePath(); - LanguageType language = LANGUAGE_UNKNOWN; - if (!path.empty()) - { - if (path.extension() == ".java") - { - language = LANGUAGE_JAVA; - } - else - { - language = LANGUAGE_CPP; - } - } - m_lineNumberArea = new LineNumberArea(this); - m_highlighter = new QtHighlighter(document(), language); std::string displayCode = m_code; if (!displayCode.empty() && *displayCode.rbegin() == '\n') @@ -149,10 +135,18 @@ QtCodeArea::QtCodeArea( m_scrollSpeedChangeListener.setScrollBar(horizontalScrollBar()); createActions(); - - m_highlighter->highlightDocument(); - createAnnotations(locationFile); + + + FilePath path = m_locationFile->getFilePath(); + LanguageType language = LANGUAGE_UNKNOWN; + if (!path.empty()) + { + language = (path.extension() == ".java" ? LANGUAGE_JAVA : LANGUAGE_CPP); + } + + m_highlighter = new QtHighlighter(document(), language); + m_highlighter->highlightDocument(); } QtCodeArea::~QtCodeArea() @@ -406,15 +400,25 @@ void QtCodeArea::paintEvent(QPaintEvent* event) { if (firstVisibleLine < 0 && bottom >= event->rect().top()) { - firstVisibleLine = block.blockNumber();; + firstVisibleLine = block.blockNumber(); } - lastVisibleLine = block.blockNumber();; + lastVisibleLine = block.blockNumber(); } block = block.next(); top = bottom; bottom = top + static_cast(blockBoundingRect(block).height()); } + + std::vector> ranges; + for (size_t i : m_colorChangedAnnotationIndices) + { + Annotation& annotation = m_annotations[i]; + ranges.push_back(std::pair(annotation.start, annotation.end)); + } + + m_highlighter->highlightRange(firstVisibleLine, lastVisibleLine, ranges); + firstVisibleLine += m_startLineNumber; lastVisibleLine += m_startLineNumber; @@ -834,9 +838,13 @@ void QtCodeArea::annotateText() const std::set& activeLocalSymbolIds = m_navigator->getActiveLocalSymbolIds(); const std::set& focusIds = m_navigator->getFocusedTokenIds(); + std::vector linesToRehighlight; + bool needsUpdate = false; - for (Annotation& annotation: m_annotations) + for (size_t i = 0; i < m_annotations.size(); i++) { + Annotation& annotation = m_annotations[i]; + bool wasActive = annotation.isActive; bool wasFocused = annotation.isFocused; const AnnotationColor& oldColor = getAnnotationColorForAnnotation(annotation); @@ -860,31 +868,21 @@ void QtCodeArea::annotateText() { if (newColor.text.size() > 0 && newColor.text != "transparent") { - bool isDuplicateAnnotation = false; - for (Annotation* a : m_colorChangedAnnotations) + if (!annotation.oldTextColor.isValid()) { - if (a->start == annotation.start && a->end == annotation.end && a->locationId != annotation.locationId) - { - isDuplicateAnnotation = true; - break; - } + annotation.oldTextColor = m_highlighter->getFormat(annotation.start, annotation.end).foreground().color(); } - if (!isDuplicateAnnotation) - { - if (!annotation.oldTextColor.isValid()) - { - annotation.oldTextColor = m_highlighter->getFormat(annotation.start, annotation.end).foreground().color(); - } - - setTextColorForAnnotation(annotation, QColor(newColor.text.c_str())); - m_colorChangedAnnotations.push_back(&annotation); - } + setTextColorForAnnotation(annotation, QColor(newColor.text.c_str())); + m_colorChangedAnnotationIndices.insert(i); } else if (annotation.oldTextColor.isValid()) { setTextColorForAnnotation(annotation, annotation.oldTextColor); annotation.oldTextColor = QColor(); + + m_colorChangedAnnotationIndices.erase(i); + linesToRehighlight.push_back(annotation.startLine - 1); } } @@ -894,7 +892,12 @@ void QtCodeArea::annotateText() } } - if (needsUpdate) + if (linesToRehighlight.size()) + { + m_highlighter->rehighlightLines(linesToRehighlight); + } + + if (m_wasAnnotated && needsUpdate) { m_lineNumberArea->update(); viewport()->update(); diff --git a/src/lib_gui/qt/element/QtCodeArea.h b/src/lib_gui/qt/element/QtCodeArea.h index 3dc659c2..8ce21c64 100644 --- a/src/lib_gui/qt/element/QtCodeArea.h +++ b/src/lib_gui/qt/element/QtCodeArea.h @@ -188,8 +188,7 @@ private: std::vector m_annotations; std::vector m_hoveredAnnotations; - // Remove when annotations become unique regarding start and end, and store multiple tokenIds - std::vector m_colorChangedAnnotations; + std::set m_colorChangedAnnotationIndices; int m_digits; diff --git a/src/lib_gui/qt/element/QtCodeFile.cpp b/src/lib_gui/qt/element/QtCodeFile.cpp index e77667e9..5e26671f 100644 --- a/src/lib_gui/qt/element/QtCodeFile.cpp +++ b/src/lib_gui/qt/element/QtCodeFile.cpp @@ -30,6 +30,7 @@ QtCodeFile::QtCodeFile(const FilePath& filePath, QtCodeNavigator* navigator) m_titleBar = new QPushButton(this); m_titleBar->setObjectName("title_widget"); + m_titleBar->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac layout->addWidget(m_titleBar); QHBoxLayout* titleLayout = new QHBoxLayout(); @@ -83,6 +84,8 @@ QtCodeFile::QtCodeFile(const FilePath& filePath, QtCodeNavigator* navigator) connect(m_maximizeButton, SIGNAL(clicked()), this, SLOT(clickedMaximizeButton())); m_snippetLayout = new QVBoxLayout(); + m_snippetLayout->setContentsMargins(0, 0, 0, 0); + m_snippetLayout->setSpacing(0); layout->addLayout(m_snippetLayout); update(); @@ -122,8 +125,7 @@ QtCodeSnippet* QtCodeFile::addCodeSnippet(const CodeSnippetParams& params) if (params.locationFile->isWhole()) { - snippet->setProperty("isFirst", true); - snippet->setProperty("isLast", true); + snippet->setStyleSheet("#code_snippet { border: none; }"); m_fileSnippet = snippet; if (!m_snippets.size()) @@ -359,8 +361,10 @@ void QtCodeFile::updateSnippets() int maxDigits = 1; for (std::shared_ptr snippet : m_snippets) { - snippet->setProperty("isFirst", false); - snippet->setProperty("isLast", false); + if (snippet != m_snippets.front() && snippet->styleSheet().size()) + { + snippet->setStyleSheet(""); + } maxDigits = qMax(maxDigits, snippet->lineNumberDigits()); } @@ -370,8 +374,10 @@ void QtCodeFile::updateSnippets() snippet->updateLineNumberAreaWidthForDigits(maxDigits); } - m_snippets.front()->setProperty("isFirst", true); - m_snippets.back()->setProperty("isLast", true); + if (!m_snippets.front()->styleSheet().size()) + { + m_snippets.front()->setStyleSheet("#code_snippet { border: none; }"); + } } void QtCodeFile::updateTitleBar() diff --git a/src/lib_gui/qt/element/QtCodeNavigateable.cpp b/src/lib_gui/qt/element/QtCodeNavigateable.cpp index 84efec1f..78b12dfb 100644 --- a/src/lib_gui/qt/element/QtCodeNavigateable.cpp +++ b/src/lib_gui/qt/element/QtCodeNavigateable.cpp @@ -29,15 +29,19 @@ void QtCodeNavigateable::ensureWidgetVisibleAnimated( if (rect.height() > 0) { focusRect = QRect(childWidget->mapTo(parentWidget, rect.topLeft().toPoint()), rect.size().toSize()); - focusRect.adjust(0, 0, 0, 100); + + if (focusRect.height() < 100) + { + focusRect.adjust(0, 0, 0, 100); + } } QScrollBar* scrollBar = area->verticalScrollBar(); int value = focusRect.center().y() - visibleRect.center().y(); - if (onTop) + if (onTop || focusRect.height() > visibleRect.height()) { - value = focusRect.top() - visibleRect.top(); + value = focusRect.top() - visibleRect.top() - 20; } if (scrollBar && (value > 50 || value < -50)) @@ -113,17 +117,21 @@ void QtCodeNavigateable::ensurePercentVisibleAnimated(double percentA, double pe int value = scrollHeight * scrollFactor; - if (animated && ApplicationSettings::getInstance()->getUseAnimations()) + int diff = value - scrollBar->value(); + if (diff > 5 || diff < -5) { - QPropertyAnimation* anim = new QPropertyAnimation(scrollBar, "value"); - anim->setDuration(300); - anim->setStartValue(scrollBar->value()); - anim->setEndValue(value); - anim->setEasingCurve(QEasingCurve::InOutQuad); - anim->start(); - } - else - { - scrollBar->setValue(value); + if (animated && ApplicationSettings::getInstance()->getUseAnimations()) + { + QPropertyAnimation* anim = new QPropertyAnimation(scrollBar, "value"); + anim->setDuration(300); + anim->setStartValue(scrollBar->value()); + anim->setEndValue(value); + anim->setEasingCurve(QEasingCurve::InOutQuad); + anim->start(); + } + else + { + scrollBar->setValue(value); + } } } diff --git a/src/lib_gui/qt/element/QtCodeNavigator.cpp b/src/lib_gui/qt/element/QtCodeNavigator.cpp index bd14ddab..ecd0a782 100644 --- a/src/lib_gui/qt/element/QtCodeNavigator.cpp +++ b/src/lib_gui/qt/element/QtCodeNavigator.cpp @@ -20,7 +20,6 @@ #include "qt/utility/utilityQt.h" #include "settings/ApplicationSettings.h" - QtCodeNavigator::QtCodeNavigator(QWidget* parent) : QWidget(parent) , m_mode(MODE_NONE) @@ -586,7 +585,7 @@ void QtCodeNavigator::scrollToDefinition(bool animated, bool ignoreActiveReferen if (!m_activeTokenId) { - if (m_mode == MODE_SINGLE && m_references.size() && m_references.front().locationType != LOCATION_TOKEN) + if (m_references.size() && m_references.front().locationType != LOCATION_TOKEN) { requestScroll(m_references.front().filePath, 0, m_references.front().locationId, false, false); emit scrollRequest(); @@ -596,7 +595,6 @@ void QtCodeNavigator::scrollToDefinition(bool animated, bool ignoreActiveReferen if (m_mode == MODE_LIST) { - std::cout << animated << std::endl; std::pair result = m_list->getFirstSnippetWithActiveLocationId(m_activeTokenId); if (result.first != nullptr) { diff --git a/src/lib_gui/qt/element/QtCodeSnippet.cpp b/src/lib_gui/qt/element/QtCodeSnippet.cpp index f8963dc7..ac46290f 100644 --- a/src/lib_gui/qt/element/QtCodeSnippet.cpp +++ b/src/lib_gui/qt/element/QtCodeSnippet.cpp @@ -19,7 +19,8 @@ std::shared_ptr QtCodeSnippet::merged( SourceLocationFile* aFile = a->m_codeArea->getSourceLocationFile().get(); SourceLocationFile* bFile = b->m_codeArea->getSourceLocationFile().get(); - std::shared_ptr locationFile = std::make_shared(aFile->getFilePath(), aFile->isWhole(), aFile->isWhole()); + std::shared_ptr locationFile = + std::make_shared(aFile->getFilePath(), aFile->isWhole(), aFile->isWhole()); aFile->forEachSourceLocation( [&locationFile](SourceLocation* loc) diff --git a/src/lib_gui/qt/utility/QtHighlighter.cpp b/src/lib_gui/qt/utility/QtHighlighter.cpp index 015b8e1d..ae6f4453 100644 --- a/src/lib_gui/qt/utility/QtHighlighter.cpp +++ b/src/lib_gui/qt/utility/QtHighlighter.cpp @@ -1,5 +1,6 @@ #include "qt/utility/QtHighlighter.h" +#include #include #include @@ -105,17 +106,9 @@ void QtHighlighter::clearHighlightingRules() s_highlightingRules.clear(); } -QtHighlighter::QtHighlighter(QTextDocument *parent, LanguageType language) - : QSyntaxHighlighter(parent) +QtHighlighter::QtHighlighter(QTextDocument *document, LanguageType language) + : m_document(document) , m_language(language) -{ -} - -void QtHighlighter::highlightBlock(const QString& text) -{ -} - -void QtHighlighter::highlightDocument() { if (m_language == LANGUAGE_UNKNOWN) { @@ -127,6 +120,25 @@ void QtHighlighter::highlightDocument() createHighlightingRules(); } + m_highlightingRules = s_highlightingRules; + + if (m_language == LANGUAGE_JAVA) + { + m_highlightingRules.append(s_highlightingRulesJava); + } + else + { + m_highlightingRules.append(s_highlightingRulesCpp); + } +} + +void QtHighlighter::highlightDocument() +{ + if (m_language == LANGUAGE_UNKNOWN) + { + return; + } + QTextDocument* doc = document(); int docStart = 0; @@ -138,37 +150,106 @@ void QtHighlighter::highlightDocument() docEnd -= 1; applyFormat(docStart, docEnd, s_textFormat); - std::vector> ranges; + m_ranges.clear(); + + m_highlightedLines.clear(); + m_highlightedLines.resize(document()->blockCount(), false); for (QTextBlock it = doc->begin(); it != doc->end(); it = it.next()) { - formatBlock(it, s_quotationRule, &ranges, true); + formatBlock(it, s_quotationRule, &m_ranges, true); } - QVector highlightingRules = s_highlightingRules; - if (m_language == LANGUAGE_JAVA) + highlightMultiLineComments(&m_ranges); +} + +void QtHighlighter::highlightRange(int startLine, int endLine, std::vector> ranges) +{ + if (m_language == LANGUAGE_UNKNOWN) { - highlightingRules.append(s_highlightingRulesJava); - } - else - { - highlightingRules.append(s_highlightingRulesCpp); + return; } - for (QTextBlock it = doc->begin(); it != doc->end(); it = it.next()) + if (startLine < 0 || endLine < 0 || startLine > endLine || endLine > int(m_highlightedLines.size())) { - foreach (const HighlightingRule &rule, highlightingRules) + return; + } + + bool hasUnhighlightedLines = false; + for (int i = startLine; i <= endLine; i++) + { + if (!m_highlightedLines[i]) { - formatBlock(it, rule, &ranges, false); + hasUnhighlightedLines = true; + break; } } - highlightMultiLineComments(&ranges); - - for (QTextBlock it = doc->begin(); it != doc->end(); it = it.next()) + if (!hasUnhighlightedLines) { - formatBlock(it, s_commentRule, &ranges, true); + return; } + + + QTextDocument* doc = document(); + QTextBlock start = doc->findBlockByLineNumber(startLine); + QTextBlock end = doc->findBlockByLineNumber(endLine + 1); + + ranges.insert(ranges.end(), m_ranges.begin(), m_ranges.end()); + + int index = startLine; + for (QTextBlock it = start; it != end; it = it.next()) + { + if (!m_highlightedLines[index]) + { + foreach (const HighlightingRule &rule, m_highlightingRules) + { + formatBlock(it, rule, &ranges, false); + } + } + index++; + } + + index = startLine; + for (QTextBlock it = start; it != end; it = it.next()) + { + if (!m_highlightedLines[index]) + { + formatBlock(it, s_commentRule, &ranges, false); + } + index++; + } + + for (int i = startLine; i <= endLine; i++) + { + m_highlightedLines[i] = true; + } +} + +void QtHighlighter::rehighlightLines(const std::vector& lines) +{ + for (int line : lines) + { + if (line >= 0 && line < int(m_highlightedLines.size())) + { + m_highlightedLines[line] = false; + } + } +} + +void QtHighlighter::applyFormat(int startPosition, int endPosition, const QTextCharFormat& format) +{ + QTextCursor cursor(document()); + cursor.setPosition(startPosition); + cursor.setPosition(endPosition, QTextCursor::KeepAnchor); + cursor.setCharFormat(format); +} + +QTextCharFormat QtHighlighter::getFormat(int startPosition, int endPosition) const +{ + QTextCursor cursor(document()); + cursor.setPosition(endPosition); + return cursor.charFormat(); } void QtHighlighter::highlightMultiLineComments(std::vector>* ranges) @@ -251,7 +332,11 @@ void QtHighlighter::formatBlock( applyFormat(pos + index, pos + index + length, rule.format); } - newRanges.push_back(std::pair(pos + index, pos + index + length)); + if (saveRange) + { + newRanges.push_back(std::pair(pos + index, pos + index + length)); + } + index = expression.indexIn(block.text(), index + length); } @@ -261,17 +346,7 @@ void QtHighlighter::formatBlock( } } -void QtHighlighter::applyFormat(int startPosition, int endPosition, const QTextCharFormat& format) +QTextDocument* QtHighlighter::document() const { - QTextCursor cursor(document()); - cursor.setPosition(startPosition); - cursor.setPosition(endPosition, QTextCursor::KeepAnchor); - cursor.setCharFormat(format); -} - -QTextCharFormat QtHighlighter::getFormat(int startPosition, int endPosition) const -{ - QTextCursor cursor(document()); - cursor.setPosition(endPosition); - return cursor.charFormat(); + return m_document; } diff --git a/src/lib_gui/qt/utility/QtHighlighter.h b/src/lib_gui/qt/utility/QtHighlighter.h index 78767402..09127ce6 100644 --- a/src/lib_gui/qt/utility/QtHighlighter.h +++ b/src/lib_gui/qt/utility/QtHighlighter.h @@ -1,31 +1,28 @@ #ifndef QT_HIGHLIGHTER_H #define QT_HIGHLIGHTER_H -#include #include #include "settings/LanguageType.h" +class QTextBlock; class QTextDocument; class QtHighlighter - : public QSyntaxHighlighter { - Q_OBJECT - public: static void createHighlightingRules(); static void clearHighlightingRules(); QtHighlighter(QTextDocument *parent, LanguageType language); void highlightDocument(); + void highlightRange(int startLine, int endLine, std::vector> ranges); + + void rehighlightLines(const std::vector& lines); void applyFormat(int startPosition, int endPosition, const QTextCharFormat& format); QTextCharFormat getFormat(int startPosition, int endPosition) const; -protected: - void highlightBlock(const QString& text); - private: struct HighlightingRule { @@ -41,6 +38,8 @@ private: bool isInRange(int index, const std::vector>& ranges) const; void formatBlock(const QTextBlock& block, const HighlightingRule& rule, std::vector>* ranges, bool saveRange); + QTextDocument* document() const; + static QVector s_highlightingRules; static QVector s_highlightingRulesCpp; static QVector s_highlightingRulesJava; @@ -48,7 +47,13 @@ private: static HighlightingRule s_commentRule; static QTextCharFormat s_textFormat; + QTextDocument* m_document; + LanguageType m_language; + + QVector m_highlightingRules; + std::vector> m_ranges; + std::vector m_highlightedLines; }; #endif // QT_HIGHLIGHTER_H diff --git a/src/lib_gui/qt/view/QtCodeView.cpp b/src/lib_gui/qt/view/QtCodeView.cpp index 8ca07649..02edacf9 100644 --- a/src/lib_gui/qt/view/QtCodeView.cpp +++ b/src/lib_gui/qt/view/QtCodeView.cpp @@ -1,6 +1,7 @@ #include "qt/view/QtCodeView.h" #include "utility/ResourcePaths.h" +#include "utility/tracing.h" #include "qt/element/QtCodeArea.h" #include "qt/element/QtCodeNavigator.h" @@ -33,6 +34,8 @@ void QtCodeView::refreshView() { m_onQtThread([=]() { + TRACE("refresh"); + setStyleSheet(); m_widget->refreshStyle(); @@ -61,6 +64,8 @@ void QtCodeView::showCodeSnippets(const std::vector& snippets { m_onQtThread([=]() { + TRACE("show code snippets"); + if (params.clearSnippets) { m_widget->clearCodeSnippets(); @@ -92,11 +97,6 @@ void QtCodeView::showCodeSnippets(const std::vector& snippets m_widget->updateFiles(); - if (m_widget->isInListMode()) - { - setStyleSheet(); // so property "isLast" of QtCodeSnippet is computed correctly - } - if (params.showContents) { m_widget->showContents(); @@ -134,6 +134,7 @@ void QtCodeView::showActiveSnippet( { m_onQtThread([=]() { + TRACE("show active snippet"); m_widget->showActiveSnippet(activeTokenIds, collection, scrollTo); }); } @@ -178,6 +179,7 @@ void QtCodeView::showContents() { m_onQtThread([=]() { + TRACE("show contents"); m_widget->showContents(); performScroll(); }); diff --git a/src/lib_gui/qt/view/QtCodeView.h b/src/lib_gui/qt/view/QtCodeView.h index c5fd4297..359f86d8 100644 --- a/src/lib_gui/qt/view/QtCodeView.h +++ b/src/lib_gui/qt/view/QtCodeView.h @@ -41,8 +41,6 @@ public: virtual bool isInListMode() const; virtual bool hasSingleFileCached(const FilePath& filePath) const; - - private: void performScroll(); void setStyleSheet() const;