From 32e7585a4871f6c0990291d780a887b39b79dfda Mon Sep 17 00:00:00 2001 From: chylex Date: Fri, 20 Nov 2020 08:59:03 +0100 Subject: [PATCH 1/2] Optimize rendering by removing unnecessary work and bulk-adding text highlights --- src/main/kotlin/org/acejump/search/Finder.kt | 17 ++++++-- src/main/kotlin/org/acejump/view/Marker.kt | 46 ++++++++++++-------- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/org/acejump/search/Finder.kt b/src/main/kotlin/org/acejump/search/Finder.kt index 18102e92..e945911a 100644 --- a/src/main/kotlin/org/acejump/search/Finder.kt +++ b/src/main/kotlin/org/acejump/search/Finder.kt @@ -13,6 +13,7 @@ import org.acejump.view.Boundary.FULL_FILE_BOUNDARY import org.acejump.view.Marker import org.acejump.view.Model.LONG_DOCUMENT import org.acejump.view.Model.boundaries +import org.acejump.view.Model.editor import org.acejump.view.Model.editorText import org.acejump.view.Model.markup import org.acejump.view.Model.viewBounds @@ -125,17 +126,25 @@ object Finder : Resettable { * [com.intellij.openapi.editor.markup.MarkupModel]. */ - fun markup(markers: Set = results, isRegexQuery: Boolean = false) = - runLater { - markers.ifEmpty { return@runLater } + fun markup(markers: Set = results, isRegexQuery: Boolean = false) { + if (markers.isEmpty()) { + return + } + runLater { textHighlights.forEach { markup.removeHighlighter(it) } + + val highlightLen = if (isRegexQuery) 1 else query.length + + editor.document.isInBulkUpdate = true textHighlights = markers.map { val start = it - if (it == editorText.length) 1 else 0 - val end = start + if (isRegexQuery) 1 else query.length + val end = start + highlightLen createTextHighlight(max(start, 0), min(end, editorText.length - 1)) } + editor.document.isInBulkUpdate = false } + } private fun createTextHighlight(start: Int, end: Int) = markup.addRangeHighlighter(start, end, HIGHLIGHT_LAYER, null, EXACT_RANGE) diff --git a/src/main/kotlin/org/acejump/view/Marker.kt b/src/main/kotlin/org/acejump/view/Marker.kt index 7b581371..f128f5a7 100644 --- a/src/main/kotlin/org/acejump/view/Marker.kt +++ b/src/main/kotlin/org/acejump/view/Marker.kt @@ -37,6 +37,7 @@ class Marker { val index: Int private val query: String val tag: String? + private val tagUpperCase: String? private var srcPoint: Point private var queryLength: Int private var trueOffset: Int @@ -52,6 +53,7 @@ class Marker { constructor(query: String, tag: String?, index: Int) { this.query = query this.tag = tag + this.tagUpperCase = tag?.toUpperCase() this.index = index val lastQueryChar = query.last() endsWith = tag != null && lastQueryChar == tag[0] @@ -62,7 +64,7 @@ class Marker { var i = 1 while (i < query.length && index + i < text.length && - query[i].toLowerCase() == text[index + i].toLowerCase()) i++ + query[i].equals(text[index + i], ignoreCase = true)) i++ trueOffset = i - 1 queryLength = i @@ -109,45 +111,53 @@ class Marker { setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON) color = AceConfig.textHighlightColor val start = editor.getPoint(highlighter.startOffset) - val queryLength = highlighter.endOffset - highlighter.startOffset - val searchWidth = if (regex) 0 else queryLength * fontWidth val startY = start.y + rectVOffset - fun highlightRegex() { - composite = getInstance(SRC_OVER, 0.40.toFloat()) - fillRoundRect(start.x, startY, fontWidth, rectHeight, arcD, arcD) + if (regex) { + highlightRegex(start.x, startY) } - - if (regex) highlightRegex() else { + val queryLength = highlighter.endOffset - highlighter.startOffset + val searchWidth = queryLength * fontWidth fillRoundRect(start.x, startY, searchWidth, rectHeight, arcD, arcD) - if (JumpMode.equals(TARGET)) surroundTargetWord(highlighter.startOffset, startY) + if (JumpMode.equals(TARGET)) { + surroundTargetWord(highlighter.startOffset, startY) + } } - if (highlighter.startOffset == Selector.nearestVisibleMatches().firstOrNull()) { - color = naturalCaretColor - drawLine(start.x, startY, start.x, startY + rectHeight) - } + // TODO this is a lag-fest, results should be cached before this gets turned back on + //if (highlighter.startOffset == Selector.nearestVisibleMatches().firstOrNull()) { + // color = naturalCaretColor + // drawLine(start.x, startY, start.x, startY + rectHeight) + //} + } + + private fun Graphics2D.highlightRegex(x: Int, y: Int) { + composite = getInstance(SRC_OVER, 0.40.toFloat()) + fillRoundRect(x, y, fontWidth, rectHeight, arcD, arcD) + } + + private fun Graphics2D.surroundTargetWord(index: Int, startY: Int) { + if (!text[index].isLetterOrDigit()) { + return } - fun Graphics2D.surroundTargetWord(index: Int, startY: Int) { color = AceConfig.targetModeColor val (wordStart, wordEnd) = text.wordBounds(index) val xPos = editor.getPoint(wordStart).x val wordWidth = (wordEnd - wordStart) * fontWidth - if (text[index].isLetterOrDigit()) - drawRoundRect(xPos, startY, wordWidth, rectHeight, arcD, arcD) + drawRoundRect(xPos, startY, wordWidth, rectHeight, arcD, arcD) } } - private fun Graphics2D.drawTagForeground(tagPosition: Point?) { + private fun Graphics2D.drawTagForeground(tagPosition: Point) { font = Model.font color = AceConfig.tagForegroundColor composite = getInstance(SRC_OVER, 1.toFloat()) - drawString(tag!!.toUpperCase(), tagPosition!!.x, tagPosition.y + fontHeight) + drawString(tagUpperCase!!, tagPosition.x, tagPosition.y + fontHeight) } // TODO: Fix tag alignment and visibility issues From 6ed4deb36e5f4a75ffe350bdca078281f87bc191 Mon Sep 17 00:00:00 2001 From: chylex Date: Fri, 20 Nov 2020 23:29:54 +0100 Subject: [PATCH 2/2] Use bulk editor updates when removing highlighters too --- src/main/kotlin/org/acejump/search/Finder.kt | 27 +++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/org/acejump/search/Finder.kt b/src/main/kotlin/org/acejump/search/Finder.kt index e945911a..034dd939 100644 --- a/src/main/kotlin/org/acejump/search/Finder.kt +++ b/src/main/kotlin/org/acejump/search/Finder.kt @@ -132,11 +132,11 @@ object Finder : Resettable { } runLater { - textHighlights.forEach { markup.removeHighlighter(it) } - val highlightLen = if (isRegexQuery) 1 else query.length editor.document.isInBulkUpdate = true + textHighlights.forEach { markup.removeHighlighter(it) } + textHighlights = markers.map { val start = it - if (it == editorText.length) 1 else 0 val end = start + highlightLen @@ -172,13 +172,18 @@ object Finder : Resettable { if (numDiscarded != 0) logger.info("Discarded $numDiscarded highlights") } - fun List.eraseIf(cond: RangeHighlighter.() -> Boolean) = - filter { - if (cond(it)) { - runLater { markup.removeHighlighter(it) } - false - } else true + fun List.eraseIf(cond: RangeHighlighter.() -> Boolean): List { + val (erased, kept) = partition(cond) + + if (erased.isNotEmpty()) { + runLater { + editor.document.isInBulkUpdate = true + erased.forEach { markup.removeHighlighter(it) } + editor.document.isInBulkUpdate = false + } } + return kept + } fun visibleResults() = results.filter { it in viewBounds } @@ -195,7 +200,11 @@ object Finder : Resettable { } override fun reset() { - runLater { markup.removeAllHighlighters() } + runLater { + editor.document.isInBulkUpdate = true + markup.removeAllHighlighters() + editor.document.isInBulkUpdate = false + } query = "" skim = false results = sortedSetOf()