mirror of
https://github.com/chylex/IntelliJ-AceJump.git
synced 2025-09-15 05:32:10 +02:00
Compare commits
2 Commits
vim
...
broken-ide
Author | SHA1 | Date | |
---|---|---|---|
82de69d53c
|
|||
ffb4573eda
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
|
@@ -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))
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
},
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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 }
|
||||
|
@@ -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,
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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())
|
||||
}
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
||||
|
@@ -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))
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user