diff --git a/src/main/kotlin/org/acejump/boundaries/EditorOffsetCache.kt b/src/main/kotlin/org/acejump/boundaries/EditorOffsetCache.kt index ecb3e3b..be2a9c2 100644 --- a/src/main/kotlin/org/acejump/boundaries/EditorOffsetCache.kt +++ b/src/main/kotlin/org/acejump/boundaries/EditorOffsetCache.kt @@ -21,6 +21,11 @@ sealed class EditorOffsetCache { */ abstract fun visibleArea(editor: Editor): Pair<Point, Point> + /** + * Returns whether the offset is in the visible area rectangle. + */ + abstract fun isVisible(editor: Editor, offset: Int): Boolean + /** * Returns the editor offset at the provided pixel coordinate. */ @@ -35,6 +40,7 @@ sealed class EditorOffsetCache { private class Cache: EditorOffsetCache() { private var visibleArea: Pair<Point, Point>? = null + private val lineToVisibleOffsetRange = Int2ObjectOpenHashMap<IntRange>() private val pointToOffset = Object2IntOpenHashMap<Point>().apply { defaultReturnValue(-1) } private val offsetToPoint = Int2ObjectOpenHashMap<Point>() @@ -42,6 +48,24 @@ sealed class EditorOffsetCache { override fun visibleArea(editor: Editor): Pair<Point, Point> = visibleArea ?: Uncached.visibleArea(editor).also { visibleArea = it } + override fun isVisible(editor: Editor, offset: Int): Boolean { + val visualLine = editor.offsetToVisualLine(offset, false) + + var visibleRange = lineToVisibleOffsetRange.get(visualLine) + if (visibleRange == null) { + val (topLeft, bottomRight) = visibleArea(editor) + val lineY = editor.visualLineToY(visualLine) + + val firstVisibleOffset = xyToOffset(editor, Point(topLeft.x, lineY)) + val lastVisibleOffset = xyToOffset(editor, Point(bottomRight.x, lineY)) + + visibleRange = firstVisibleOffset..lastVisibleOffset + lineToVisibleOffsetRange.put(visualLine, visibleRange) + } + + return offset in visibleRange + } + override fun xyToOffset(editor: Editor, pos: Point): Int = pointToOffset.getInt(pos).let { offset -> if (offset != -1) offset @@ -64,6 +88,15 @@ sealed class EditorOffsetCache { ) } + override fun isVisible(editor: Editor, offset: Int): Boolean { + val (topLeft, bottomRight) = visibleArea(editor) + val pos = offsetToXY(editor, offset) + val x = pos.x + val y = pos.y + + return x >= topLeft.x && y >= topLeft.y && x <= bottomRight.x && y <= bottomRight.y + } + override fun xyToOffset(editor: Editor, pos: Point): Int = read { editor.logicalPositionToOffset(editor.xyToLogicalPosition(pos)) } diff --git a/src/main/kotlin/org/acejump/boundaries/StandardBoundaries.kt b/src/main/kotlin/org/acejump/boundaries/StandardBoundaries.kt index 1a47935..da034e1 100644 --- a/src/main/kotlin/org/acejump/boundaries/StandardBoundaries.kt +++ b/src/main/kotlin/org/acejump/boundaries/StandardBoundaries.kt @@ -21,22 +21,7 @@ enum class StandardBoundaries : Boundaries { } override fun isOffsetInside(editor: Editor, offset: Int, cache: EditorOffsetCache): Boolean { - // If we are not using a cache, calling getOffsetRange will cause - // additional 1-2 pixel coordinate -> offset lookups, which is a lot - // more expensive than one lookup compared against the visible area. - - // However, if we are using a cache, it's likely that the topmost and - // bottommost positions are already cached whereas the provided offset - // isn't, so we save a lookup for every offset outside the range. - - if (cache !== EditorOffsetCache.Uncached && offset !in getOffsetRange(editor, cache)) return false - - val (topLeft, bottomRight) = cache.visibleArea(editor) - val pos = cache.offsetToXY(editor, offset) - val x = pos.x - val y = pos.y - - return x >= topLeft.x && y >= topLeft.y && x <= bottomRight.x && y <= bottomRight.y + return cache.isVisible(editor, offset) } }, diff --git a/src/main/kotlin/org/acejump/search/SearchProcessor.kt b/src/main/kotlin/org/acejump/search/SearchProcessor.kt index bc9cfca..c7fdd56 100644 --- a/src/main/kotlin/org/acejump/search/SearchProcessor.kt +++ b/src/main/kotlin/org/acejump/search/SearchProcessor.kt @@ -3,6 +3,7 @@ package org.acejump.search import com.intellij.openapi.editor.Editor import it.unimi.dsi.fastutil.ints.IntArrayList import org.acejump.boundaries.Boundaries +import org.acejump.boundaries.EditorOffsetCache import org.acejump.immutableText import org.acejump.isWordPart import org.acejump.matchesAt @@ -12,7 +13,6 @@ import org.acejump.matchesAt * previous results when the user [type]s a character. */ internal class SearchProcessor private constructor( - private val editors: List<Editor>, query: SearchQuery, results: MutableMap<Editor, IntArrayList> ) { @@ -24,14 +24,15 @@ internal class SearchProcessor private constructor( SearchProcessor(editors, SearchQuery.RegularExpression(pattern), boundaries) } - private constructor(editors: List<Editor>, query: SearchQuery, boundaries: Boundaries) : this(editors, query, mutableMapOf()) { + private constructor(editors: List<Editor>, query: SearchQuery, boundaries: Boundaries) : this(query, mutableMapOf()) { val regex = query.toRegex() if (regex != null) { for (editor in editors) { + val cache = EditorOffsetCache.new() val offsets = IntArrayList() - val offsetRange = boundaries.getOffsetRange(editor) + val offsetRange = boundaries.getOffsetRange(editor, cache) var result = regex.find(editor.immutableText, offsetRange.first) while (result != null) { @@ -43,7 +44,7 @@ internal class SearchProcessor private constructor( if (highlightEnd > offsetRange.last) { break } - else if (boundaries.isOffsetInside(editor, index)) { + else if (boundaries.isOffsetInside(editor, index, cache)) { offsets.add(index) }