1
0
mirror of https://github.com/chylex/IntelliJ-AceJump.git synced 2025-09-15 05:32:10 +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
17 changed files with 103 additions and 197 deletions

View File

@@ -8,7 +8,7 @@ plugins {
}
group = "org.acejump"
version = "chylex-26"
version = "chylex-21"
repositories {
mavenCentral()
@@ -18,7 +18,7 @@ intellij {
version.set("2024.2")
updateSinceUntilBuild.set(false)
plugins.add("IdeaVIM:chylex-41")
plugins.add("IdeaVIM:chylex-40")
plugins.add("com.intellij.classic.ui:242.20224.159")
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.vimSelectionStart
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 org.acejump.boundaries.StandardBoundaries.AFTER_CARET
import org.acejump.boundaries.StandardBoundaries.BEFORE_CARET
@@ -52,8 +52,8 @@ sealed class AceVimAction : DumbAwareAction() {
}
else {
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
keyHandler.fullReset(vim)
@@ -71,10 +71,10 @@ sealed class AceVimAction : DumbAwareAction() {
if (action != null) {
ApplicationManager.getApplication().invokeLater {
WriteAction.run<Nothing> {
keyHandler.keyHandlerState.commandBuilder.addAction(action)
keyHandler.keyHandlerState.commandBuilder.pushCommandPart(action)
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.actionExecutor.executeVimAction(vim, action, context, operatorArguments)

View File

@@ -18,11 +18,6 @@ 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.
*/
@@ -41,7 +36,6 @@ 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>()
@@ -49,24 +43,6 @@ sealed class EditorOffsetCache {
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 {
val offset = pointToOffset.getInt(pos)
@@ -75,6 +51,7 @@ sealed class EditorOffsetCache {
}
return Uncached.xyToOffset(editor, pos).also {
@Suppress("ReplacePutWithAssignment")
pointToOffset.put(pos, it)
}
}
@@ -87,6 +64,7 @@ sealed class EditorOffsetCache {
}
return Uncached.offsetToXY(editor, offset).also {
@Suppress("ReplacePutWithAssignment")
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 {
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 {
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

@@ -20,7 +20,6 @@ class AceConfig : PersistentStateComponent<AceSettings> {
val layout get() = settings.layout
val minQueryLength get() = settings.minQueryLength
val invertUppercaseMode get() = settings.invertUppercaseMode
val editorFadeOpacity get() = settings.editorFadeOpacity
val jumpModeColor get() = settings.jumpModeColor
val tagForegroundColor1 get() = settings.tagForegroundColor1

View File

@@ -15,7 +15,6 @@ class AceConfigurable : Configurable {
panel.allowedChars != settings.allowedChars ||
panel.keyboardLayout != settings.layout ||
panel.minQueryLengthInt != settings.minQueryLength ||
panel.invertUppercaseMode != settings.invertUppercaseMode ||
panel.editorFadeOpacityPercent != settings.editorFadeOpacity ||
panel.jumpModeColor != settings.jumpModeColor ||
panel.tagForegroundColor1 != settings.tagForegroundColor1 ||
@@ -26,7 +25,6 @@ class AceConfigurable : Configurable {
settings.allowedChars = panel.allowedChars
settings.layout = panel.keyboardLayout
settings.minQueryLength = panel.minQueryLengthInt ?: settings.minQueryLength
settings.invertUppercaseMode = panel.invertUppercaseMode
settings.editorFadeOpacity = panel.editorFadeOpacityPercent
panel.jumpModeColor?.let { settings.jumpModeColor = it }
panel.tagForegroundColor1?.let { settings.tagForegroundColor1 = it }

View File

@@ -8,7 +8,6 @@ import java.awt.Color
data class AceSettings(
var layout: KeyLayout = QWERTY,
var allowedChars: String = layout.allChars,
var invertUppercaseMode: Boolean = false,
var minQueryLength: Int = 1,
var editorFadeOpacity: Int = 70,

View File

@@ -2,7 +2,6 @@ package org.acejump.config
import com.intellij.openapi.ui.ComboBox
import com.intellij.ui.ColorPanel
import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.components.JBSlider
import com.intellij.ui.components.JBTextArea
import com.intellij.ui.components.JBTextField
@@ -12,7 +11,6 @@ import com.intellij.ui.dsl.builder.columns
import com.intellij.ui.dsl.builder.panel
import org.acejump.input.KeyLayout
import java.awt.Color
import java.awt.Dimension
import java.awt.Font
import java.util.Hashtable
import javax.swing.JCheckBox
@@ -31,7 +29,6 @@ internal class AceSettingsPanel {
private val keyboardLayoutCombo = ComboBox<KeyLayout>()
private val keyboardLayoutArea = JBTextArea().apply { isEditable = false }
private val minQueryLengthField = JBTextField()
private val invertUppercaseModeCheckBox = JBCheckBox("Invert uppercase mode")
private val editorFadeOpacitySlider = JBSlider(0, 10).apply {
labelTable = Hashtable((0..10).associateWith { JLabel("${it * 10}") })
paintTrack = true
@@ -39,7 +36,6 @@ internal class AceSettingsPanel {
paintTicks = true
minorTickSpacing = 1
majorTickSpacing = 1
minimumSize = Dimension(275, minimumSize.height)
}
private val jumpModeColorWheel = ColorPanel()
private val tagForeground1ColorWheel = ColorPanel()
@@ -61,7 +57,6 @@ internal class AceSettingsPanel {
group("Behavior") {
row("Minimum typed characters (1-10):") { cell(minQueryLengthField).columns(COLUMNS_SHORT) }
row { cell(invertUppercaseModeCheckBox) }
}
group("Colors") {
@@ -86,7 +81,6 @@ internal class AceSettingsPanel {
internal var keyboardLayout by keyboardLayoutCombo
internal var keyChars by keyboardLayoutArea
internal var minQueryLength by minQueryLengthField
internal var invertUppercaseMode by invertUppercaseModeCheckBox
internal var editorFadeOpacity by editorFadeOpacitySlider
internal var jumpModeColor by jumpModeColorWheel
internal var tagForegroundColor1 by tagForeground1ColorWheel
@@ -105,7 +99,6 @@ internal class AceSettingsPanel {
allowedChars = settings.allowedChars
keyboardLayout = settings.layout
minQueryLength = settings.minQueryLength.toString()
invertUppercaseMode = settings.invertUppercaseMode
editorFadeOpacityPercent = settings.editorFadeOpacity
jumpModeColor = settings.jumpModeColor
tagForegroundColor1 = settings.tagForegroundColor1

View File

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

View File

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

View File

@@ -1,22 +1,20 @@
package org.acejump.input
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
* with repeated keys (ex. FF, JJ) or adjacent keys (ex. GH, UJ).
*/
internal object KeyLayoutCache {
lateinit var allowedTagsSorted: List<String>
lateinit var allowedCharsSorted: List<Char>
private set
/**
* Called before any lazily initialized properties are used, to ensure that they are initialized even if the settings are missing.
*/
fun ensureInitialized(settings: AceSettings) {
if (!::allowedTagsSorted.isInitialized) {
if (!::allowedCharsSorted.isInitialized) {
reset(settings)
}
}
@@ -25,38 +23,17 @@ internal object KeyLayoutCache {
* Re-initializes cached data according to updated settings.
*/
fun reset(settings: AceSettings) {
val allowedChars = processCharList(settings.allowedChars).ifEmpty { processCharList(settings.layout.allChars) }
val allowedTags = mutableSetOf<String>()
val allowedCharList = processCharList(settings.allowedChars)
for (c1 in allowedChars) {
allowedTags.add("$c1")
for (c2 in allowedChars) {
if (c1 != c2) {
allowedTags.add("$c1$c2")
}
}
allowedCharsSorted = if (allowedCharList.isEmpty()) {
processCharList(settings.layout.allChars)
}
else {
allowedCharList.sortedWith(compareBy(settings.layout.priority()))
}
allowedTagsSorted = allowedTags.sortedBy { rankPriority(settings.layout, it) }
}
private fun processCharList(charList: String): List<Char> {
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 it.unimi.dsi.fastutil.ints.IntArrayList
import org.acejump.boundaries.Boundaries
import org.acejump.boundaries.EditorOffsetCache
import org.acejump.clone
import org.acejump.config.AceConfig
import org.acejump.immutableText
@@ -12,16 +11,15 @@ import org.acejump.matchesAt
/**
* Searches editor text for matches of a [SearchQuery], and updates previous results when the user [refineQuery]s a character.
*/
class SearchProcessor private constructor(query: SearchQuery, val boundaries: Boundaries, val invertUppercaseMode: Boolean, private val results: MutableMap<Editor, IntArrayList>) {
internal constructor(editors: List<Editor>, query: SearchQuery, boundaries: Boundaries, invertUppercaseMode: Boolean) : this(query, boundaries, invertUppercaseMode, mutableMapOf()) {
val regex = query.toRegex(invertUppercaseMode)
class SearchProcessor private constructor(query: SearchQuery, val boundaries: Boundaries, private val results: MutableMap<Editor, IntArrayList>) {
internal constructor(editors: List<Editor>, query: SearchQuery, boundaries: Boundaries) : this(query, boundaries, mutableMapOf()) {
val regex = query.toRegex()
if (regex != null) {
for (editor in editors) {
val cache = EditorOffsetCache.new()
val offsets = IntArrayList()
val offsetRange = boundaries.getOffsetRange(editor, cache)
val offsetRange = boundaries.getOffsetRange(editor)
var result = regex.find(editor.immutableText, offsetRange.first)
while (result != null) {
@@ -31,7 +29,7 @@ class SearchProcessor private constructor(query: SearchQuery, val boundaries: Bo
if (highlightEnd > offsetRange.last) {
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)
}

View File

@@ -21,7 +21,7 @@ internal sealed class SearchQuery {
/**
* Converts the query into a regular expression to find the initial matches.
*/
abstract fun toRegex(invertUppercaseMode: Boolean): Regex?
abstract fun toRegex(): Regex?
/**
* Searches for all occurrences of a literal text query.
@@ -41,14 +41,14 @@ internal sealed class SearchQuery {
return text.countMatchingCharacters(offset, rawText)
}
override fun toRegex(invertUppercaseMode: Boolean): Regex {
override fun toRegex(): Regex {
val firstChar = rawText.first()
val pattern = if (firstChar.isLowerCase() xor invertUppercaseMode) {
val pattern = if (firstChar.isLowerCase()) {
val fullPattern = Regex.escape(rawText)
"(?i)$fullPattern"
}
else {
val firstCharUppercasePattern = Regex.escape(firstChar.uppercase())
val firstCharUppercasePattern = Regex.escape(firstChar.toString())
val firstCharLowercasePattern = Regex.escape(firstChar.lowercase())
val remainingPattern = if (rawText.length > 1) Regex.escape(rawText.drop(1)) else ""
"(?:$firstCharUppercasePattern|(?<![a-zA-Z])$firstCharLowercasePattern)$remainingPattern"
@@ -72,7 +72,7 @@ internal sealed class SearchQuery {
return 1
}
override fun toRegex(invertUppercaseMode: Boolean): Regex {
override fun toRegex(): Regex {
return Regex(pattern, setOf(RegexOption.MULTILINE, RegexOption.IGNORE_CASE))
}
}

View File

@@ -3,7 +3,6 @@ package org.acejump.search
import com.google.common.collect.ArrayListMultimap
import com.intellij.openapi.editor.Editor
import it.unimi.dsi.fastutil.ints.IntList
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import org.acejump.boundaries.EditorOffsetCache
import org.acejump.boundaries.StandardBoundaries.VISIBLE_ON_SCREEN
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) } }
.sortedWith(siteOrder(editors, caches))
tagMap = generateTags(tagSites.size).zip(tagSites).toMap()
tagMap = generateTags(tagSites).zip(tagSites).toMap()
}
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 fun generateTags(tagCount: Int): List<String> {
val allowedTagsSorted = KeyLayoutCache.allowedTagsSorted
private fun generateTags(tagSites: List<Tag>): List<String> {
val allowedChars = KeyLayoutCache.allowedCharsSorted
val tags = mutableListOf<String>()
var remainingTagCount = tagSites.size
val containedSingleCharTags = mutableSetOf<Char>()
val blockedSingleCharTags = mutableSetOf<Char>()
val doubleCharTagCountsByFirstChar = Object2IntOpenHashMap<Char>()
for (tag in allowedTagsSorted) {
val firstChar = tag.first()
outer@ for (i in allowedChars.indices) {
val c1 = allowedChars[i]
if (tag.length == 1) {
if (firstChar in blockedSingleCharTags) {
continue
if (remainingTagCount <= allowedChars.size - i) {
tags.add(c1.toString())
if (--remainingTagCount <= 0) {
break@outer
}
containedSingleCharTags.add(firstChar)
}
else {
if (containedSingleCharTags.remove(firstChar)) {
tags.remove(firstChar.toString())
}
blockedSingleCharTags.add(firstChar)
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
for (c2 in allowedChars) {
tags.add("$c1$c2")
if (--remainingTagCount <= 0) {
break@outer
}
}
}
tags.add(previousTagIndex + 1, tag)
}
return tags
@@ -162,11 +128,12 @@ class Tagger(private val editors: List<Editor>, results: Map<Editor, IntList>) {
return@Comparator if (aIsVisible) -1 else 1
}
val firstEditor = editorPriority[0]
val caretPosition = caches.getValue(firstEditor).offsetToXY(firstEditor, firstEditor.caretModel.offset)
val aPosition = aCaches.offsetToXY(aEditor, a.offset)
val bPosition = bCaches.offsetToXY(bEditor, b.offset)
val aDistance = aCaches.offsetToXY(aEditor, a.offset).distanceSq(caretPosition)
val bDistance = bCaches.offsetToXY(bEditor, b.offset).distanceSq(caretPosition)
val caretPosition = editorPriority[0].offsetToXY(editorPriority[0].caretModel.offset)
val aDistance = aPosition.distanceSq(caretPosition)
val bDistance = bPosition.distanceSq(caretPosition)
return@Comparator aDistance.compareTo(bDistance)
}

View File

@@ -113,7 +113,7 @@ class Session(private val mainEditor: Editor, private val jumpEditors: List<Edit
}
setMode(mode())
state = SessionState.WaitForKey(actions, jumpEditors, defaultBoundary, AceConfig.invertUppercaseMode)
state = SessionState.WaitForKey(actions, jumpEditors, defaultBoundary)
}
/**
@@ -128,7 +128,7 @@ class Session(private val mainEditor: Editor, private val jumpEditors: List<Edit
canvas.setMarkers(emptyList())
}
val processor = SearchProcessor(jumpEditors, SearchQuery.RegularExpression(pattern.regex), defaultBoundary, AceConfig.invertUppercaseMode)
val processor = SearchProcessor(jumpEditors, SearchQuery.RegularExpression(pattern.regex), defaultBoundary)
textHighlighter.renderOccurrences(processor.resultsCopy, processor.query)
state = SessionState.SelectTag(actions, jumpEditors, processor)

View File

@@ -16,10 +16,9 @@ sealed interface SessionState {
private val actions: SessionActions,
private val jumpEditors: List<Editor>,
private val defaultBoundary: Boundaries,
private val invertUppercaseMode: Boolean,
) : SessionState {
override fun type(char: Char): TypeResult {
val searchProcessor = SearchProcessor(jumpEditors, SearchQuery.Literal(char.toString()), defaultBoundary, invertUppercaseMode)
val searchProcessor = SearchProcessor(jumpEditors, SearchQuery.Literal(char.toString()), defaultBoundary)
return if (searchProcessor.isQueryFinished) {
TypeResult.ChangeState(SelectTag(actions, jumpEditors, searchProcessor))
@@ -73,14 +72,10 @@ sealed interface SessionState {
else -> searchProcessor.boundaries
}
val newSearchProcessor = SearchProcessor(jumpEditors, query, newBoundaries, searchProcessor.invertUppercaseMode)
val newSearchProcessor = SearchProcessor(jumpEditors, query, newBoundaries)
return TypeResult.ChangeState(SelectTag(actions, jumpEditors, newSearchProcessor))
}
}
else if (char == '\n') {
val newSearchProcessor = SearchProcessor(jumpEditors, searchProcessor.query, searchProcessor.boundaries, !searchProcessor.invertUppercaseMode)
return TypeResult.ChangeState(SelectTag(actions, jumpEditors, newSearchProcessor))
}
return when (val result = tagger.type(AceConfig.layout.characterRemapping.getOrDefault(char, char))) {
is TaggingResult.Nothing -> TypeResult.Nothing

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.
*/
internal class TagMarker(
private val firstChar: String,
private val secondChar: String,
private val tag: CharArray,
val offset: Int
) {
private constructor(tag: String, offset: Int) : this(tag.first().toString(), tag.drop(1), offset)
private val length = firstChar.length + secondChar.length
private val length = tag.size
companion object {
/**
@@ -31,7 +28,7 @@ internal class TagMarker(
* character ([typedTag]) matches the first [tag] character, only the second [tag] character is displayed.
*/
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.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.drawString(secondChar, x + font.tagCharWidth, y)
g.drawChars(tag, 1, length - 1, x + font.tagCharWidth, y)
}
}