1
0
mirror of https://github.com/chylex/IntelliJ-AceJump.git synced 2025-09-16 16:24:48 +02:00

2 Commits

Author SHA1 Message Date
82de69d53c WIP 2024-09-03 22:16:35 +02:00
ffb4573eda Redo tag generation (eliminate explicit prefix chars) 2024-09-03 21:50:03 +02:00
11 changed files with 91 additions and 171 deletions

View File

@@ -8,7 +8,7 @@ plugins {
} }
group = "org.acejump" group = "org.acejump"
version = "chylex-25" version = "chylex-21"
repositories { repositories {
mavenCentral() mavenCentral()
@@ -18,7 +18,7 @@ intellij {
version.set("2024.2") version.set("2024.2")
updateSinceUntilBuild.set(false) updateSinceUntilBuild.set(false)
plugins.add("IdeaVIM:chylex-41") plugins.add("IdeaVIM:chylex-40")
plugins.add("com.intellij.classic.ui:242.20224.159") plugins.add("com.intellij.classic.ui:242.20224.159")
pluginsRepositories { pluginsRepositories {

View File

@@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.group.visual.vimSetSelection
import com.maddyhome.idea.vim.helper.inVisualMode import com.maddyhome.idea.vim.helper.inVisualMode
import com.maddyhome.idea.vim.helper.vimSelectionStart import com.maddyhome.idea.vim.helper.vimSelectionStart
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import org.acejump.boundaries.StandardBoundaries.AFTER_CARET import org.acejump.boundaries.StandardBoundaries.AFTER_CARET
import org.acejump.boundaries.StandardBoundaries.BEFORE_CARET import org.acejump.boundaries.StandardBoundaries.BEFORE_CARET
@@ -52,8 +52,8 @@ sealed class AceVimAction : DumbAwareAction() {
} }
else { else {
val vim = editor.vim val vim = editor.vim
if (vim.mode is OP_PENDING) { val keyHandler = KeyHandler.getInstance()
val keyHandler = KeyHandler.getInstance() if (keyHandler.isOperatorPending(vim.mode, keyHandler.keyHandlerState)) {
val key = keyHandler.keyHandlerState.commandBuilder.keys.singleOrNull()?.keyChar val key = keyHandler.keyHandlerState.commandBuilder.keys.singleOrNull()?.keyChar
keyHandler.fullReset(vim) keyHandler.fullReset(vim)
@@ -71,10 +71,10 @@ sealed class AceVimAction : DumbAwareAction() {
if (action != null) { if (action != null) {
ApplicationManager.getApplication().invokeLater { ApplicationManager.getApplication().invokeLater {
WriteAction.run<Nothing> { WriteAction.run<Nothing> {
keyHandler.keyHandlerState.commandBuilder.addAction(action) keyHandler.keyHandlerState.commandBuilder.pushCommandPart(action)
val cmd = keyHandler.keyHandlerState.commandBuilder.buildCommand() val cmd = keyHandler.keyHandlerState.commandBuilder.buildCommand()
val operatorArguments = OperatorArguments(vim.mode is OP_PENDING, cmd.rawCount, injector.vimState.mode) val operatorArguments = OperatorArguments(vim.mode is Mode.OP_PENDING, cmd.rawCount, injector.vimState.mode)
injector.vimState.executingCommand = cmd injector.vimState.executingCommand = cmd
injector.actionExecutor.executeVimAction(vim, action, context, operatorArguments) injector.actionExecutor.executeVimAction(vim, action, context, operatorArguments)

View File

@@ -18,11 +18,6 @@ sealed class EditorOffsetCache {
*/ */
abstract fun visibleArea(editor: Editor): Pair<Point, Point> 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. * Returns the editor offset at the provided pixel coordinate.
*/ */
@@ -41,7 +36,6 @@ sealed class EditorOffsetCache {
private class Cache : EditorOffsetCache() { private class Cache : EditorOffsetCache() {
private var visibleArea: Pair<Point, Point>? = null private var visibleArea: Pair<Point, Point>? = null
private val lineToVisibleOffsetRange = Int2ObjectOpenHashMap<IntRange>()
private val pointToOffset = Object2IntOpenHashMap<Point>().apply { defaultReturnValue(-1) } private val pointToOffset = Object2IntOpenHashMap<Point>().apply { defaultReturnValue(-1) }
private val offsetToPoint = Int2ObjectOpenHashMap<Point>() private val offsetToPoint = Int2ObjectOpenHashMap<Point>()
@@ -49,24 +43,6 @@ sealed class EditorOffsetCache {
return visibleArea ?: Uncached.visibleArea(editor).also { visibleArea = it } return 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 { override fun xyToOffset(editor: Editor, pos: Point): Int {
val offset = pointToOffset.getInt(pos) val offset = pointToOffset.getInt(pos)
@@ -75,6 +51,7 @@ sealed class EditorOffsetCache {
} }
return Uncached.xyToOffset(editor, pos).also { return Uncached.xyToOffset(editor, pos).also {
@Suppress("ReplacePutWithAssignment")
pointToOffset.put(pos, it) pointToOffset.put(pos, it)
} }
} }
@@ -87,6 +64,7 @@ sealed class EditorOffsetCache {
} }
return Uncached.offsetToXY(editor, offset).also { return Uncached.offsetToXY(editor, offset).also {
@Suppress("ReplacePutWithAssignment")
offsetToPoint.put(offset, it) offsetToPoint.put(offset, it)
} }
} }
@@ -102,15 +80,6 @@ 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 { override fun xyToOffset(editor: Editor, pos: Point): Int {
return editor.logicalPositionToOffset(editor.xyToLogicalPosition(pos)) return editor.logicalPositionToOffset(editor.xyToLogicalPosition(pos))
} }

View File

@@ -13,7 +13,23 @@ enum class StandardBoundaries : Boundaries {
} }
override fun isOffsetInside(editor: Editor, offset: Int, cache: EditorOffsetCache): Boolean { override fun isOffsetInside(editor: Editor, offset: Int, cache: EditorOffsetCache): Boolean {
return cache.isVisible(editor, offset)
// 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
} }
}, },

View File

@@ -11,7 +11,6 @@ import com.intellij.ui.dsl.builder.columns
import com.intellij.ui.dsl.builder.panel import com.intellij.ui.dsl.builder.panel
import org.acejump.input.KeyLayout import org.acejump.input.KeyLayout
import java.awt.Color import java.awt.Color
import java.awt.Dimension
import java.awt.Font import java.awt.Font
import java.util.Hashtable import java.util.Hashtable
import javax.swing.JCheckBox import javax.swing.JCheckBox
@@ -37,7 +36,6 @@ internal class AceSettingsPanel {
paintTicks = true paintTicks = true
minorTickSpacing = 1 minorTickSpacing = 1
majorTickSpacing = 1 majorTickSpacing = 1
minimumSize = Dimension(275, minimumSize.height)
} }
private val jumpModeColorWheel = ColorPanel() private val jumpModeColorWheel = ColorPanel()
private val tagForeground1ColorWheel = ColorPanel() private val tagForeground1ColorWheel = ColorPanel()

View File

@@ -10,6 +10,7 @@ import com.intellij.openapi.editor.actionSystem.TypedActionHandler
* sessions' own handlers. * sessions' own handlers.
*/ */
internal object EditorKeyListener : TypedActionHandler { internal object EditorKeyListener : TypedActionHandler {
private val action = TypedAction.getInstance()
private val attached = mutableMapOf<Editor, TypedActionHandler>() private val attached = mutableMapOf<Editor, TypedActionHandler>()
private var originalHandler: TypedActionHandler? = null private var originalHandler: TypedActionHandler? = null
@@ -19,9 +20,8 @@ internal object EditorKeyListener : TypedActionHandler {
fun attach(editor: Editor, callback: TypedActionHandler) { fun attach(editor: Editor, callback: TypedActionHandler) {
if (attached.isEmpty()) { if (attached.isEmpty()) {
val typedAction = TypedAction.getInstance() originalHandler = action.rawHandler
originalHandler = typedAction.rawHandler action.setupRawHandler(this)
typedAction.setupRawHandler(this)
} }
attached[editor] = callback attached[editor] = callback
@@ -31,7 +31,7 @@ internal object EditorKeyListener : TypedActionHandler {
attached.remove(editor) attached.remove(editor)
if (attached.isEmpty()) { if (attached.isEmpty()) {
originalHandler?.let(TypedAction.getInstance()::setupRawHandler) originalHandler?.let(action::setupRawHandler)
originalHandler = null originalHandler = null
} }
} }

View File

@@ -14,20 +14,25 @@ enum class KeyLayout(
COLEMK(arrayOf("1234567890", "qwfpgjluy", "arstdhneio", "zxcvbkm"), priority = "tndhseriaovkcmbxzgjplfuwyq5849673210"), COLEMK(arrayOf("1234567890", "qwfpgjluy", "arstdhneio", "zxcvbkm"), priority = "tndhseriaovkcmbxzgjplfuwyq5849673210"),
WORKMN(arrayOf("1234567890", "qdrwbjfup", "ashtgyneoi", "zxmcvkl"), priority = "tnhegysoaiclvkmxzwfrubjdpq5849673210"), WORKMN(arrayOf("1234567890", "qdrwbjfup", "ashtgyneoi", "zxmcvkl"), priority = "tnhegysoaiclvkmxzwfrubjdpq5849673210"),
DVORAK(arrayOf("1234567890", "pyfgcrl", "aoeuidhtns", "qjkxbmwvz"), priority = "uhetidonasxkbjmqwvzgfycprl5849673210"), DVORAK(arrayOf("1234567890", "pyfgcrl", "aoeuidhtns", "qjkxbmwvz"), priority = "uhetidonasxkbjmqwvzgfycprl5849673210"),
QWERTY(arrayOf("1234567890", "qwertyuiop", "asdfghjkl", "zxcvbnm"), priority = "fjghdkslavncmbxzrutyeiwoqp5849673210", characterSides = sides(listOf("123456", "qwert", "asdfg", "zxcvb"), listOf("7890", "yuiop", "hjkl", "nm"))), QWERTY(arrayOf("1234567890", "qwertyuiop", "asdfghjkl", "zxcvbnm"), priority = "fjghdkslavncmbxzrutyeiwoqp5849673210"),
QWERTZ(arrayOf("1234567890", "qwertzuiop", "asdfghjkl", "yxcvbnm"), priority = "fjghdkslavncmbxyrutzeiwoqp5849673210", characterSides = sides(listOf("123456", "qwert", "asdfg", "yxcvb"), listOf("7890", "zuiop", "hjkl", "nm"))), QWERTZ(arrayOf("1234567890", "qwertzuiop", "asdfghjkl", "yxcvbnm"), priority = "fjghdkslavncmbxyrutzeiwoqp5849673210"),
QWERTZ_CZ(arrayOf("1234567890", "qwertzuiop", "asdfghjkl", "yxcvbnm"), priority = "fjghdkslavncmbxyrutzeiwoqp5849673210", characterSides = sides(listOf("123456", "qwert", "asdfg", "yxcvb"), listOf("7890", "zuiop", "hjkl", "nm")), characterRemapping = mapOf( QWERTZ_CZ(
'+' to '1', arrayOf("1234567890", "qwertzuiop", "asdfghjkl", "yxcvbnm"),
'ě' to '2', priority = "fjghdkslavncmbxyrutzeiwoqp5849673210",
'š' to '3', characterSides = sides("", ""),
'č' to '4', characterRemapping = mapOf(
'ř' to '5', '+' to '1',
'ž' to '6', 'ě' to '2',
'ý' to '7', 'š' to '3',
'á' to '8', 'č' to '4',
'í' to '9', 'ř' to '5',
'é' to '0' 'ž' to '6',
)), 'ý' to '7',
'á' to '8',
'í' to '9',
'é' to '0'
)
),
QGMLWY(arrayOf("1234567890", "qgmlwyfub", "dstnriaeoh", "zxcvjkp"), priority = "naterisodhvkcpjxzlfmuwygbq5849673210"), QGMLWY(arrayOf("1234567890", "qgmlwyfub", "dstnriaeoh", "zxcvjkp"), priority = "naterisodhvkcpjxzlfmuwygbq5849673210"),
QGMLWB(arrayOf("1234567890", "qgmlwbyuv", "dstnriaeoh", "zxcfjkp"), priority = "naterisodhfkcpjxzlymuwbgvq5849673210"), QGMLWB(arrayOf("1234567890", "qgmlwbyuv", "dstnriaeoh", "zxcfjkp"), priority = "naterisodhfkcpjxzlymuwbgvq5849673210"),
NORMAN(arrayOf("1234567890", "qwdfkjurl", "asetgynioh", "zxcvbpm"), priority = "tneigysoahbvpcmxzjkufrdlwq5849673210"); NORMAN(arrayOf("1234567890", "qwdfkjurl", "asetgynioh", "zxcvbpm"), priority = "tneigysoahbvpcmxzjkufrdlwq5849673210");
@@ -35,18 +40,11 @@ enum class KeyLayout(
internal val allChars = rows.joinToString("").toCharArray().apply(CharArray::sort).joinToString("") internal val allChars = rows.joinToString("").toCharArray().apply(CharArray::sort).joinToString("")
private val allPriorities = priority.mapIndexed { index, char -> char to index }.toMap() private val allPriorities = priority.mapIndexed { index, char -> char to index }.toMap()
fun priority(char: Char): Int { internal fun priority(): (Char) -> Int {
return allPriorities[char] ?: allChars.length return { allPriorities.getOrDefault(it, Int.MAX_VALUE) }
}
fun areOnSameSide(c1: Char, c2: Char): Boolean {
return (c1 in characterSides.first && c2 in characterSides.first) || (c1 in characterSides.second && c2 in characterSides.second)
} }
} }
private fun sides(left: List<String>, right: List<String>): Pair<Set<Char>, Set<Char>> { private fun sides(left: String, right: String): Pair<Set<Char>, Set<Char>> {
return Pair( return Pair(left.toCharArray().toSet(), right.toCharArray().toSet())
left.flatMapTo(mutableSetOf()) { it.toCharArray().toSet() },
right.flatMapTo(mutableSetOf()) { it.toCharArray().toSet() }
)
} }

View File

@@ -1,22 +1,20 @@
package org.acejump.input package org.acejump.input
import org.acejump.config.AceSettings import org.acejump.config.AceSettings
import kotlin.math.pow
import kotlin.math.roundToInt
/** /**
* Stores data specific to the selected keyboard layout. We want to assign tags with easily reachable keys first, and ideally have tags * Stores data specific to the selected keyboard layout. We want to assign tags with easily reachable keys first, and ideally have tags
* with repeated keys (ex. FF, JJ) or adjacent keys (ex. GH, UJ). * with repeated keys (ex. FF, JJ) or adjacent keys (ex. GH, UJ).
*/ */
internal object KeyLayoutCache { internal object KeyLayoutCache {
lateinit var allowedTagsSorted: List<String> lateinit var allowedCharsSorted: List<Char>
private set private set
/** /**
* Called before any lazily initialized properties are used, to ensure that they are initialized even if the settings are missing. * Called before any lazily initialized properties are used, to ensure that they are initialized even if the settings are missing.
*/ */
fun ensureInitialized(settings: AceSettings) { fun ensureInitialized(settings: AceSettings) {
if (!::allowedTagsSorted.isInitialized) { if (!::allowedCharsSorted.isInitialized) {
reset(settings) reset(settings)
} }
} }
@@ -25,38 +23,17 @@ internal object KeyLayoutCache {
* Re-initializes cached data according to updated settings. * Re-initializes cached data according to updated settings.
*/ */
fun reset(settings: AceSettings) { fun reset(settings: AceSettings) {
val allowedChars = processCharList(settings.allowedChars).ifEmpty { processCharList(settings.layout.allChars) } val allowedCharList = processCharList(settings.allowedChars)
val allowedTags = mutableSetOf<String>()
for (c1 in allowedChars) { allowedCharsSorted = if (allowedCharList.isEmpty()) {
allowedTags.add("$c1") processCharList(settings.layout.allChars)
}
for (c2 in allowedChars) { else {
if (c1 != c2) { allowedCharList.sortedWith(compareBy(settings.layout.priority()))
allowedTags.add("$c1$c2")
}
}
} }
allowedTagsSorted = allowedTags.sortedBy { rankPriority(settings.layout, it) }
} }
private fun processCharList(charList: String): List<Char> { private fun processCharList(charList: String): List<Char> {
return charList.toCharArray().map(Char::lowercaseChar).distinct() return charList.toCharArray().map(Char::lowercaseChar).distinct()
} }
private fun rankPriority(layout: KeyLayout, tag: String): Int {
val c1 = tag.first()
val p1 = (1.0 + layout.priority(c1)).pow(3)
if (tag.length == 1) {
return p1.roundToInt()
}
val c2 = tag.last()
val p2 = (1.0 + layout.priority(c2)).pow(3)
val multiplier = if (layout.areOnSameSide(c1, c2)) 2 else 1
return (((p1 * 50) + p2 + 1000) * multiplier).roundToInt()
}
} }

View File

@@ -3,7 +3,6 @@ package org.acejump.search
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import it.unimi.dsi.fastutil.ints.IntArrayList import it.unimi.dsi.fastutil.ints.IntArrayList
import org.acejump.boundaries.Boundaries import org.acejump.boundaries.Boundaries
import org.acejump.boundaries.EditorOffsetCache
import org.acejump.clone import org.acejump.clone
import org.acejump.config.AceConfig import org.acejump.config.AceConfig
import org.acejump.immutableText import org.acejump.immutableText
@@ -18,10 +17,9 @@ class SearchProcessor private constructor(query: SearchQuery, val boundaries: Bo
if (regex != null) { if (regex != null) {
for (editor in editors) { for (editor in editors) {
val cache = EditorOffsetCache.new()
val offsets = IntArrayList() val offsets = IntArrayList()
val offsetRange = boundaries.getOffsetRange(editor, cache) val offsetRange = boundaries.getOffsetRange(editor)
var result = regex.find(editor.immutableText, offsetRange.first) var result = regex.find(editor.immutableText, offsetRange.first)
while (result != null) { while (result != null) {
@@ -31,7 +29,7 @@ class SearchProcessor private constructor(query: SearchQuery, val boundaries: Bo
if (highlightEnd > offsetRange.last) { if (highlightEnd > offsetRange.last) {
break break
} }
else if (boundaries.isOffsetInside(editor, index, cache) && !editor.foldingModel.isOffsetCollapsed(index)) { else if (boundaries.isOffsetInside(editor, index) && !editor.foldingModel.isOffsetCollapsed(index)) {
offsets.add(index) offsets.add(index)
} }

View File

@@ -3,7 +3,6 @@ package org.acejump.search
import com.google.common.collect.ArrayListMultimap import com.google.common.collect.ArrayListMultimap
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import it.unimi.dsi.fastutil.ints.IntList import it.unimi.dsi.fastutil.ints.IntList
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import org.acejump.boundaries.EditorOffsetCache import org.acejump.boundaries.EditorOffsetCache
import org.acejump.boundaries.StandardBoundaries.VISIBLE_ON_SCREEN import org.acejump.boundaries.StandardBoundaries.VISIBLE_ON_SCREEN
import org.acejump.input.KeyLayoutCache import org.acejump.input.KeyLayoutCache
@@ -41,7 +40,7 @@ class Tagger(private val editors: List<Editor>, results: Map<Editor, IntList>) {
.flatMap { (editor, sites) -> sites.map { site -> Tag(editor, site) } } .flatMap { (editor, sites) -> sites.map { site -> Tag(editor, site) } }
.sortedWith(siteOrder(editors, caches)) .sortedWith(siteOrder(editors, caches))
tagMap = generateTags(tagSites.size).zip(tagSites).toMap() tagMap = generateTags(tagSites).zip(tagSites).toMap()
} }
internal fun type(char: Char): TaggingResult { internal fun type(char: Char): TaggingResult {
@@ -62,63 +61,30 @@ class Tagger(private val editors: List<Editor>, results: Map<Editor, IntList>) {
} }
private companion object { private companion object {
private fun generateTags(tagCount: Int): List<String> { private fun generateTags(tagSites: List<Tag>): List<String> {
val allowedTagsSorted = KeyLayoutCache.allowedTagsSorted val allowedChars = KeyLayoutCache.allowedCharsSorted
val tags = mutableListOf<String>() val tags = mutableListOf<String>()
var remainingTagCount = tagSites.size
val containedSingleCharTags = mutableSetOf<Char>() outer@ for (i in allowedChars.indices) {
val blockedSingleCharTags = mutableSetOf<Char>() val c1 = allowedChars[i]
val doubleCharTagCountsByFirstChar = Object2IntOpenHashMap<Char>()
for (tag in allowedTagsSorted) {
val firstChar = tag.first()
if (tag.length == 1) { if (remainingTagCount <= allowedChars.size - i) {
if (firstChar in blockedSingleCharTags) { tags.add(c1.toString())
continue if (--remainingTagCount <= 0) {
break@outer
} }
containedSingleCharTags.add(firstChar)
} }
else { else {
if (containedSingleCharTags.remove(firstChar)) { for (c2 in allowedChars) {
tags.remove(firstChar.toString()) tags.add("$c1$c2")
}
if (--remainingTagCount <= 0) {
blockedSingleCharTags.add(firstChar) break@outer
doubleCharTagCountsByFirstChar.addTo(firstChar, 1) }
}
tags.add(tag)
if (tags.size >= tagCount) {
break
}
}
// In rare cases, the final tag list may contain a double character tag that is the only tag starting with its first character,
// so we replace it with the single character tag.
for (entry in doubleCharTagCountsByFirstChar.object2IntEntrySet()) {
if (entry.intValue != 1) {
continue
}
tags.removeAt(tags.indexOfFirst { it.first() == entry.key })
val tag = entry.key.toString()
var previousTagIndex = -1
// The implementation of searching where to place the single character tag is theoretically slow,
// but getting here is so rare it doesn't matter.
for (i in allowedTagsSorted.indexOf(tag) - 1 downTo 0) {
previousTagIndex = tags.indexOf(allowedTagsSorted[i])
if (previousTagIndex != -1) {
break
} }
} }
tags.add(previousTagIndex + 1, tag)
} }
return tags return tags
@@ -162,11 +128,12 @@ class Tagger(private val editors: List<Editor>, results: Map<Editor, IntList>) {
return@Comparator if (aIsVisible) -1 else 1 return@Comparator if (aIsVisible) -1 else 1
} }
val firstEditor = editorPriority[0] val aPosition = aCaches.offsetToXY(aEditor, a.offset)
val caretPosition = caches.getValue(firstEditor).offsetToXY(firstEditor, firstEditor.caretModel.offset) val bPosition = bCaches.offsetToXY(bEditor, b.offset)
val aDistance = aCaches.offsetToXY(aEditor, a.offset).distanceSq(caretPosition) val caretPosition = editorPriority[0].offsetToXY(editorPriority[0].caretModel.offset)
val bDistance = bCaches.offsetToXY(bEditor, b.offset).distanceSq(caretPosition) val aDistance = aPosition.distanceSq(caretPosition)
val bDistance = bPosition.distanceSq(caretPosition)
return@Comparator aDistance.compareTo(bDistance) return@Comparator aDistance.compareTo(bDistance)
} }

View File

@@ -12,13 +12,10 @@ import java.awt.Rectangle
* Describes a 1 or 2 character shortcut that points to a specific character in the editor. * Describes a 1 or 2 character shortcut that points to a specific character in the editor.
*/ */
internal class TagMarker( internal class TagMarker(
private val firstChar: String, private val tag: CharArray,
private val secondChar: String,
val offset: Int val offset: Int
) { ) {
private constructor(tag: String, offset: Int) : this(tag.first().toString(), tag.drop(1), offset) private val length = tag.size
private val length = firstChar.length + secondChar.length
companion object { companion object {
/** /**
@@ -31,7 +28,7 @@ internal class TagMarker(
* character ([typedTag]) matches the first [tag] character, only the second [tag] character is displayed. * character ([typedTag]) matches the first [tag] character, only the second [tag] character is displayed.
*/ */
fun create(tag: String, offset: Int, typedTag: String): TagMarker { fun create(tag: String, offset: Int, typedTag: String): TagMarker {
return TagMarker(tag.drop(typedTag.length), offset) return TagMarker(tag.drop(typedTag.length).toCharArray(), offset)
} }
} }
@@ -68,11 +65,11 @@ internal class TagMarker(
g.font = font.tagFont g.font = font.tagFont
g.color = font.foregroundColor1 g.color = font.foregroundColor1
g.drawString(firstChar, x, y) g.drawChars(tag, 0, 1, x, y)
if (secondChar.isNotEmpty()) { if (tag.size > 1) {
g.color = font.foregroundColor2 g.color = font.foregroundColor2
g.drawString(secondChar, x + font.tagCharWidth, y) g.drawChars(tag, 1, length - 1, x + font.tagCharWidth, y)
} }
} }