Skip to content

Commit

Permalink
separate highlighting from tagging
Browse files Browse the repository at this point in the history
  • Loading branch information
breandan committed Mar 18, 2020
1 parent 394f1f9 commit 1c9d208
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 70 deletions.
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

Adds support for Chinese [#314](https://github.com/acejump/AceJump/issues/314).

Fixes constantly loading settings page [#303](https://github.com/acejump/AceJump/issues/303).

Honor camel humps [#315](https://github.com/acejump/AceJump/issues/305). Thanks to @clojj.

Speed up tagging on very large files.

### 3.5.9

Fix a build configuration error affecting plugins which depend on AceJump. Fixes [#305](https://github.com/acejump/AceJump/issues/305).
Expand Down
16 changes: 8 additions & 8 deletions src/main/kotlin/org/acejump/search/Finder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ object Finder : Resettable {

fun search(model: AceFindModel = AceFindModel(query)) {
measureTimeMillis {
results = Scanner.findMatchingSites(model, results)
results = Scanner.find(model, results)
}.let { logger.info("Found ${results.size} matching sites in $it ms") }

markResults(results, model)
Expand All @@ -115,30 +115,30 @@ object Finder : Resettable {
fun markResults(results: SortedSet<Int>,
model: AceFindModel = AceFindModel("", true)
) {
markup(results, model.isRegularExpressions)
if (!skim) tag(model, results)
markup(Tagger.markers, model)
}

/**
* Paints text highlights beneath each query result to the editor using the
* [com.intellij.openapi.editor.markup.MarkupModel].
*/

fun markup(markers: List<Marker>, model: AceFindModel = AceFindModel(query)) =
fun markup(markers: Set<Int> = results, isRegexQuery: Boolean = false) =
runLater {
if (markers.isEmpty()) return@runLater

textHighlights.forEach { markup.removeHighlighter(it) }
textHighlights = markers.map {
val start = it.index - if (it.index == editorText.length) 1 else 0
val end = start + if (model.isRegularExpressions) 1 else query.length
createTextHighlight(it, max(start, 0), min(end, editorText.length - 1))
val start = it - if (it == editorText.length) 1 else 0
val end = start + if (isRegexQuery) 1 else query.length
createTextHighlight(max(start, 0), min(end, editorText.length - 1))
}
}

private fun createTextHighlight(marker: Marker, start: Int, end: Int) =
private fun createTextHighlight(start: Int, end: Int) =
markup.addRangeHighlighter(start, end, HIGHLIGHT_LAYER, null, EXACT_RANGE)
.apply { customRenderer = marker }
.apply { customRenderer = Marker.Companion }

private fun tag(model: AceFindModel, results: SortedSet<Int>) {
synchronized(this) { Tagger.markOrJump(model, results) }
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/org/acejump/search/JumpMode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal enum class JumpMode {
else -> AceConfig.jumpModeColor
})

Finder.markup(Tagger.markers)
Finder.markup()
Canvas.repaint()
}

Expand Down
45 changes: 22 additions & 23 deletions src/main/kotlin/org/acejump/search/Scanner.kt
Original file line number Diff line number Diff line change
@@ -1,47 +1,49 @@
package org.acejump.search

import com.intellij.openapi.diagnostic.Logger
import org.acejump.search.Scanner
import org.acejump.view.Boundary.FULL_FILE_BOUNDARY
import org.acejump.view.Model.LONG_DOCUMENT
import org.acejump.view.Model.boundaries
import org.acejump.view.Model.editorText
import java.util.*
import kotlin.streams.toList

/**
* Returns a list of indices where the query begins, within the given range.
* These are full indices, ie. are not offset to the beginning of the range.
* Returns a set of indices indicating query matches, within the given range.
* These are full indices, i.e. are not offset to the beginning of the range.
*/

internal object Scanner {
val cores = Runtime.getRuntime().availableProcessors() - 1
private val logger = Logger.getInstance(Scanner::class.java)

/**
* Returns indices of locations matching the [model]. Provide a [cache] in
* order to filter prior results that have been returned by this method.
* Returns [SortedSet] of indices matching the [model]. Providing a [cache]
* will filter prior results instead of searching the editor contents.
*/

fun findMatchingSites(model: AceFindModel, cache: Set<Int> = emptySet()) =
fun find(model: AceFindModel, cache: Set<Int> = emptySet()): SortedSet<Int> =
if (!LONG_DOCUMENT || cache.size != 0 || boundaries != FULL_FILE_BOUNDARY)
editorText.search(model, cache, boundaries.intRange()).toSortedSet()
else
editorText.chunk().parallelStream().map { chunk ->
editorText.search(model, cache, chunk)
}.toList().flatten().toSortedSet()
else editorText.chunk().parallelStream().map { chunk ->
editorText.search(model, cache, chunk)
}.toList().flatten().toSortedSet()

/**
* Breaks up text to search into [chunkSize] chunks for parallelized search.
* Divides lines of text into equally-sized chunks for parallelized search.
*/

private fun String.chunk(chunkSize: Int = count { it == '\n' } / cores + 1) =
splitToSequence("\n", "\r").toList().run {
logger.info("Parallelizing query across $cores cores")
var offset = 0
chunked(chunkSize).map {
val len = it.joinToString("\n").length
(offset..(offset + len)).also { offset += len + 1 }
}
private fun String.chunk(): List<IntRange> {
val lines = splitToSequence("\n", "\r").toList()
val chunkSize = lines.size / cores + 1
logger.info("Parallelizing ${lines.size}-line search across $cores cores")
var offset = 0
return lines.chunked(chunkSize).map {
val len = it.joinToString("\n").length
(offset..(offset + len)).also { offset += len + 1 }
}
}

/**
* Searches the [cache] (if it is populated), or else the whole document.
Expand Down Expand Up @@ -72,9 +74,6 @@ internal object Scanner {
nextFunction = { result -> filterNext(result, chunk) }
).map { it.range.first }.toList()

fun filterNext(result: MatchResult, chunk: IntRange): MatchResult? {
val next = result.next() ?: return null
val offset = next.range.first
return if (offset !in chunk) null else next
}
fun filterNext(result: MatchResult, chunk: IntRange): MatchResult? =
result.next()?.let { if(it.range.first !in chunk) null else it }
}
67 changes: 33 additions & 34 deletions src/main/kotlin/org/acejump/view/Marker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import org.acejump.view.Model.editorText as text
* TODO: Clean up this class.
*/

class Marker : CustomHighlighterRenderer {
class Marker {
val index: Int
private val query: String
val tag: String?
Expand Down Expand Up @@ -101,44 +101,43 @@ class Marker : CustomHighlighterRenderer {
* Called by IntelliJ as a [CustomHighlighterRenderer]. Paints [highlight]s.
*/

override fun paint(editor: Editor, highlight: RangeHighlighter, g: Graphics) =
(g as Graphics2D).run {
val tagX = start.x + fontWidth
val tagWidth = tag?.length?.times(fontWidth) ?: 0

fun highlightRegex() {
composite = getInstance(SRC_OVER, 0.40.toFloat())
val xPos = if (alignment == RIGHT) tagX - fontWidth else start.x
fillRoundRect(xPos, startY, fontWidth, rectHeight, arcD, arcD)
}

setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON)

color = AceConfig.textHighlightColor
if (regex) highlightRegex()
else {
fillRoundRect(start.x, startY, searchWidth, rectHeight, arcD, arcD)
if (JumpMode.equals(TARGET)) surroundTargetWord()
}
companion object: CustomHighlighterRenderer {
override fun paint(editor: Editor, highlighter: RangeHighlighter, g: Graphics) =
(g as Graphics2D).run {
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 (index == Selector.nearestVisibleMatches().firstOrNull())
indicateAsNearestMatch()
}
if (regex) highlightRegex()
else {
fillRoundRect(start.x, startY, searchWidth, rectHeight, arcD, arcD)
if (JumpMode.equals(TARGET)) surroundTargetWord(highlighter.startOffset, startY)
}

private fun Graphics2D.indicateAsNearestMatch() {
color = naturalCaretColor
drawLine(start.x, startY, start.x, startY + rectHeight)
}
if (highlighter.startOffset == Selector.nearestVisibleMatches().firstOrNull()) {
color = naturalCaretColor
drawLine(start.x, startY, start.x, startY + rectHeight)
}
}

private fun Graphics2D.surroundTargetWord() {
color = AceConfig.targetModeColor
val (wordStart, wordEnd) = text.wordBounds(index)
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
val xPos = editor.getPoint(wordStart).x
val wordWidth = (wordEnd - wordStart) * fontWidth

if (text[index].isLetterOrDigit())
drawRoundRect(xPos, startY, wordWidth, rectHeight, arcD, arcD)
if (text[index].isLetterOrDigit())
drawRoundRect(xPos, startY, wordWidth, rectHeight, arcD, arcD)
}
}

private fun Graphics2D.drawTagForeground(tagPosition: Point?) {
Expand Down
8 changes: 4 additions & 4 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@
description="Searches for all words on the screen in AceJump"/>
<action id="AceWordForwardAction"
class="org.acejump.control.AceWordForwardAction"
text="Start in Word Mode"
description="Searches for all words after the caret in AceJump"/>
text="Start in Word Forward Mode"
description="Searches for all visible words after the caret in AceJump"/>
<action id="AceWordBackwardsAction"
class="org.acejump.control.AceWordBackwardsAction"
text="Start in Word Mode"
description="Searches for all words before the caret in AceJump"/>
text="Start in Word Backward Mode"
description="Searches for all visible words before the caret in AceJump"/>
<action id="AceDeclarationAction"
class="org.acejump.control.AceDefinitionAction"
text="Start in Declaration Mode"
Expand Down

0 comments on commit 1c9d208

Please sign in to comment.