1
0
mirror of https://github.com/chylex/IntelliJ-AceJump.git synced 2024-11-25 17:42:46 +01:00

Compare commits

...

8 Commits

12 changed files with 66 additions and 89 deletions

View File

@ -8,7 +8,7 @@ plugins {
} }
group = "org.acejump" group = "org.acejump"
version = "chylex-16" version = "chylex-18"
repositories { repositories {
mavenCentral() mavenCentral()

View File

@ -7,8 +7,6 @@ import com.intellij.openapi.project.Project
import com.intellij.util.IncorrectOperationException import com.intellij.util.IncorrectOperationException
import it.unimi.dsi.fastutil.ints.IntArrayList import it.unimi.dsi.fastutil.ints.IntArrayList
annotation class ExternalUsage
/** /**
* Returns an immutable version of the currently edited document. * Returns an immutable version of the currently edited document.
*/ */
@ -52,57 +50,6 @@ fun CharSequence.countMatchingCharacters(selfOffset: Int, otherText: String): In
return i return i
} }
/**
* Determines which characters form a "word" for the purposes of functions below.
*/
val Char.isWordPart
get() = this in 'a'..'z' || this.isJavaIdentifierPart()
/**
* Finds index of the first character in a word.
*/
inline fun CharSequence.wordStart(pos: Int, isPartOfWord: (Char) -> Boolean = Char::isWordPart): Int {
var start = pos
while (start > 0 && isPartOfWord(this[start - 1])) {
--start
}
return start
}
/**
* Finds index of the last character in a word.
*/
inline fun CharSequence.wordEnd(pos: Int, isPartOfWord: (Char) -> Boolean = Char::isWordPart): Int {
var end = pos
val limit = length - 1
while (end < limit && isPartOfWord(this[end + 1])) {
++end
}
return end
}
/**
* Finds index of the first word character following a sequence of non-word characters following the end of a word.
*/
inline fun CharSequence.wordEndPlus(pos: Int, isPartOfWord: (Char) -> Boolean = Char::isWordPart): Int {
var end = this.wordEnd(pos, isPartOfWord)
val limit = length - 1
while (end < limit && !isPartOfWord(this[end + 1])) {
++end
}
if (end < limit && isPartOfWord(this[end + 1])) {
++end
}
return end
}
fun MutableMap<Editor, IntArrayList>.clone(): MutableMap<Editor, IntArrayList> { fun MutableMap<Editor, IntArrayList>.clone(): MutableMap<Editor, IntArrayList> {
val clone = HashMap<Editor, IntArrayList>(size) val clone = HashMap<Editor, IntArrayList>(size)

View File

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

View File

@ -16,6 +16,7 @@ class AceConfigurable : Configurable {
panel.prefixChars != settings.prefixChars || panel.prefixChars != settings.prefixChars ||
panel.keyboardLayout != settings.layout || panel.keyboardLayout != settings.layout ||
panel.minQueryLengthInt != settings.minQueryLength || panel.minQueryLengthInt != settings.minQueryLength ||
panel.editorFadeOpacityPercent != settings.editorFadeOpacity ||
panel.jumpModeColor != settings.jumpModeColor || panel.jumpModeColor != settings.jumpModeColor ||
panel.tagForegroundColor1 != settings.tagForegroundColor1 || panel.tagForegroundColor1 != settings.tagForegroundColor1 ||
panel.tagForegroundColor2 != settings.tagForegroundColor2 || panel.tagForegroundColor2 != settings.tagForegroundColor2 ||
@ -26,6 +27,7 @@ class AceConfigurable : Configurable {
settings.prefixChars = panel.prefixChars settings.prefixChars = panel.prefixChars
settings.layout = panel.keyboardLayout settings.layout = panel.keyboardLayout
settings.minQueryLength = panel.minQueryLengthInt ?: settings.minQueryLength settings.minQueryLength = panel.minQueryLengthInt ?: settings.minQueryLength
settings.editorFadeOpacity = panel.editorFadeOpacityPercent
panel.jumpModeColor?.let { settings.jumpModeColor = it } panel.jumpModeColor?.let { settings.jumpModeColor = it }
panel.tagForegroundColor1?.let { settings.tagForegroundColor1 = it } panel.tagForegroundColor1?.let { settings.tagForegroundColor1 = it }
panel.tagForegroundColor2?.let { settings.tagForegroundColor2 = it } panel.tagForegroundColor2?.let { settings.tagForegroundColor2 = it }

View File

@ -10,6 +10,7 @@ data class AceSettings(
var allowedChars: String = layout.allChars, var allowedChars: String = layout.allChars,
var prefixChars: String = ";", var prefixChars: String = ";",
var minQueryLength: Int = 1, var minQueryLength: Int = 1,
var editorFadeOpacity: Int = 70,
@OptionTag("jumpModeRGB", converter = ColorConverter::class) @OptionTag("jumpModeRGB", converter = ColorConverter::class)
var jumpModeColor: Color = Color(0xFFFFFF), var jumpModeColor: Color = Color(0xFFFFFF),

View File

@ -2,6 +2,7 @@ package org.acejump.config
import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.ComboBox
import com.intellij.ui.ColorPanel import com.intellij.ui.ColorPanel
import com.intellij.ui.components.JBSlider
import com.intellij.ui.components.JBTextArea import com.intellij.ui.components.JBTextArea
import com.intellij.ui.components.JBTextField import com.intellij.ui.components.JBTextField
import com.intellij.ui.layout.Cell import com.intellij.ui.layout.Cell
@ -11,9 +12,12 @@ import com.intellij.ui.layout.panel
import org.acejump.input.KeyLayout import org.acejump.input.KeyLayout
import java.awt.Color import java.awt.Color
import java.awt.Font import java.awt.Font
import java.util.Hashtable
import javax.swing.JCheckBox import javax.swing.JCheckBox
import javax.swing.JComponent import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.JPanel import javax.swing.JPanel
import javax.swing.JSlider
import javax.swing.text.JTextComponent import javax.swing.text.JTextComponent
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
@ -27,6 +31,14 @@ internal class AceSettingsPanel {
private val keyboardLayoutCombo = ComboBox<KeyLayout>() private val keyboardLayoutCombo = ComboBox<KeyLayout>()
private val keyboardLayoutArea = JBTextArea().apply { isEditable = false } private val keyboardLayoutArea = JBTextArea().apply { isEditable = false }
private val minQueryLengthField = JBTextField() private val minQueryLengthField = JBTextField()
private val editorFadeOpacitySlider = JBSlider(0, 10).apply {
labelTable = Hashtable((0..10).associateWith { JLabel("${it * 10}") })
paintTrack = true
paintLabels = true
paintTicks = true
minorTickSpacing = 1
majorTickSpacing = 1
}
private val jumpModeColorWheel = ColorPanel() private val jumpModeColorWheel = ColorPanel()
private val tagForeground1ColorWheel = ColorPanel() private val tagForeground1ColorWheel = ColorPanel()
private val tagForeground2ColorWheel = ColorPanel() private val tagForeground2ColorWheel = ColorPanel()
@ -71,6 +83,9 @@ internal class AceSettingsPanel {
component(searchHighlightColorWheel) component(searchHighlightColorWheel)
} }
} }
row("Editor fade opacity (%):") {
medium(editorFadeOpacitySlider)
}
} }
} }
@ -80,6 +95,7 @@ internal class AceSettingsPanel {
internal var keyboardLayout by keyboardLayoutCombo internal var keyboardLayout by keyboardLayoutCombo
internal var keyChars by keyboardLayoutArea internal var keyChars by keyboardLayoutArea
internal var minQueryLength by minQueryLengthField internal var minQueryLength by minQueryLengthField
internal var editorFadeOpacity by editorFadeOpacitySlider
internal var jumpModeColor by jumpModeColorWheel internal var jumpModeColor by jumpModeColorWheel
internal var tagForegroundColor1 by tagForeground1ColorWheel internal var tagForegroundColor1 by tagForeground1ColorWheel
internal var tagForegroundColor2 by tagForeground2ColorWheel internal var tagForegroundColor2 by tagForeground2ColorWheel
@ -89,11 +105,16 @@ internal class AceSettingsPanel {
get() = minQueryLength.toIntOrNull()?.coerceIn(1, 10) get() = minQueryLength.toIntOrNull()?.coerceIn(1, 10)
set(value) { minQueryLength = value.toString() } set(value) { minQueryLength = value.toString() }
internal var editorFadeOpacityPercent
get() = editorFadeOpacity * 10
set(value) { editorFadeOpacity = value / 10 }
fun reset(settings: AceSettings) { fun reset(settings: AceSettings) {
allowedChars = settings.allowedChars allowedChars = settings.allowedChars
prefixChars = settings.prefixChars prefixChars = settings.prefixChars
keyboardLayout = settings.layout keyboardLayout = settings.layout
minQueryLength = settings.minQueryLength.toString() minQueryLength = settings.minQueryLength.toString()
editorFadeOpacityPercent = settings.editorFadeOpacity
jumpModeColor = settings.jumpModeColor jumpModeColor = settings.jumpModeColor
tagForegroundColor1 = settings.tagForegroundColor1 tagForegroundColor1 = settings.tagForegroundColor1
tagForegroundColor2 = settings.tagForegroundColor2 tagForegroundColor2 = settings.tagForegroundColor2
@ -111,6 +132,9 @@ internal class AceSettingsPanel {
private operator fun JCheckBox.getValue(a: AceSettingsPanel, p: KProperty<*>) = isSelected private operator fun JCheckBox.getValue(a: AceSettingsPanel, p: KProperty<*>) = isSelected
private operator fun JCheckBox.setValue(a: AceSettingsPanel, p: KProperty<*>, selected: Boolean) = setSelected(selected) private operator fun JCheckBox.setValue(a: AceSettingsPanel, p: KProperty<*>, selected: Boolean) = setSelected(selected)
private operator fun JSlider.getValue(a: AceSettingsPanel, p: KProperty<*>) = value
private operator fun JSlider.setValue(a: AceSettingsPanel, p: KProperty<*>, value: Int) = setValue(value)
private operator fun <T> ComboBox<T>.getValue(a: AceSettingsPanel, p: KProperty<*>) = selectedItem as T private operator fun <T> ComboBox<T>.getValue(a: AceSettingsPanel, p: KProperty<*>) = selectedItem as T
private operator fun <T> ComboBox<T>.setValue(a: AceSettingsPanel, p: KProperty<*>, item: T) = setSelectedItem(item) private operator fun <T> ComboBox<T>.setValue(a: AceSettingsPanel, p: KProperty<*>, item: T) = setSelectedItem(item)

View File

@ -4,13 +4,25 @@ package org.acejump.input
* Defines common keyboard layouts. Each layout has a key priority order, based on each key's distance from the home row and how * Defines common keyboard layouts. Each layout has a key priority order, based on each key's distance from the home row and how
* ergonomically difficult they are to press. * ergonomically difficult they are to press.
*/ */
@Suppress("unused") @Suppress("unused", "SpellCheckingInspection")
enum class KeyLayout(internal val rows: Array<String>, priority: String) { enum class KeyLayout(internal val rows: Array<String>, priority: String, internal val characterRemapping: Map<Char, Char> = emptyMap()) {
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"), QWERTY(arrayOf("1234567890", "qwertyuiop", "asdfghjkl", "zxcvbnm"), priority = "fjghdkslavncmbxzrutyeiwoqp5849673210"),
QWERTZ(arrayOf("1234567890", "qwertzuiop", "asdfghjkl", "yxcvbnm"), priority = "fjghdkslavncmbxyrutzeiwoqp5849673210"), QWERTZ(arrayOf("1234567890", "qwertzuiop", "asdfghjkl", "yxcvbnm"), priority = "fjghdkslavncmbxyrutzeiwoqp5849673210"),
QWERTZ_CZ(arrayOf("1234567890", "qwertzuiop", "asdfghjkl", "yxcvbnm"), priority = "fjghdkslavncmbxyrutzeiwoqp5849673210", 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"), 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");

View File

@ -7,13 +7,6 @@ import org.acejump.config.AceSettings
* 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 {
/**
* Sorts tags according to current keyboard layout settings, and some predefined rules that force tags with digits, and tags with two
* keys far apart, to be sorted after other (easier to type) tags.
*/
lateinit var tagOrder: Comparator<String>
private set
/** /**
* Returns all possible two key tags, pre-sorted according to [tagOrder]. * Returns all possible two key tags, pre-sorted according to [tagOrder].
*/ */
@ -24,7 +17,7 @@ internal object KeyLayoutCache {
* 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 (!::tagOrder.isInitialized) { if (!::allPossibleTagsLowercase.isInitialized) {
reset(settings) reset(settings)
} }
} }
@ -33,15 +26,16 @@ 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) {
tagOrder = compareBy(
String::length,
settings.layout.priority(String::last)
)
@Suppress("ConvertLambdaToReference") @Suppress("ConvertLambdaToReference")
val allSuffixChars = processCharList(settings.allowedChars).ifEmpty { processCharList(settings.layout.allChars).toList() } val allSuffixChars = processCharList(settings.allowedChars).ifEmpty { processCharList(settings.layout.allChars).toList() }
val allPrefixChars = processCharList(settings.prefixChars).filterNot(allSuffixChars::contains).plus("") val allPrefixChars = processCharList(settings.prefixChars).filterNot(allSuffixChars::contains).plus("")
val tagOrder = compareBy(
String::length,
{ if (it.length == 1) Int.MIN_VALUE else allPrefixChars.indexOf(it.first().toString()) },
settings.layout.priority(String::last)
)
allPossibleTagsLowercase = allSuffixChars allPossibleTagsLowercase = allSuffixChars
.flatMap { suffix -> allPrefixChars.map { prefix -> "$prefix$suffix" } } .flatMap { suffix -> allPrefixChars.map { prefix -> "$prefix$suffix" } }
.sortedWith(tagOrder) .sortedWith(tagOrder)

View File

@ -39,7 +39,7 @@ class SearchProcessor private constructor(query: SearchQuery, private val result
if (highlightEnd > offsetRange.last) { if (highlightEnd > offsetRange.last) {
break break
} }
else if (boundaries.isOffsetInside(editor, index)) { else if (boundaries.isOffsetInside(editor, index) && !editor.foldingModel.isOffsetCollapsed(index)) {
offsets.add(index) offsets.add(index)
} }

View File

@ -5,13 +5,10 @@ import com.intellij.openapi.editor.Editor
import it.unimi.dsi.fastutil.ints.IntList import it.unimi.dsi.fastutil.ints.IntList
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.immutableText
import org.acejump.input.KeyLayoutCache import org.acejump.input.KeyLayoutCache
import org.acejump.isWordPart
import org.acejump.view.TagMarker import org.acejump.view.TagMarker
import kotlin.collections.component1 import kotlin.collections.component1
import kotlin.collections.component2 import kotlin.collections.component2
import kotlin.math.max
import kotlin.math.min import kotlin.math.min
/** /**
@ -92,26 +89,24 @@ class Tagger(private val editors: List<Editor>, results: Map<Editor, IntList>) {
return@Comparator if (aEditorIndex < bEditorIndex) -1 else 1 return@Comparator if (aEditorIndex < bEditorIndex) -1 else 1
} }
val aIsVisible = VISIBLE_ON_SCREEN.isOffsetInside(aEditor, a.offset, caches.getValue(aEditor)) val aCaches = caches.getValue(aEditor)
val bIsVisible = VISIBLE_ON_SCREEN.isOffsetInside(bEditor, b.offset, caches.getValue(bEditor)) val bCaches = caches.getValue(bEditor)
val aIsVisible = VISIBLE_ON_SCREEN.isOffsetInside(aEditor, a.offset, aCaches)
val bIsVisible = VISIBLE_ON_SCREEN.isOffsetInside(bEditor, b.offset, bCaches)
if (aIsVisible != bIsVisible) { if (aIsVisible != bIsVisible) {
// Sites in immediate view should come first. // Sites in immediate view should come first.
return@Comparator if (aIsVisible) -1 else 1 return@Comparator if (aIsVisible) -1 else 1
} }
val aIsNotWordStart = aEditor.immutableText[max(0, a.offset - 1)].isWordPart val aPosition = aCaches.offsetToXY(aEditor, a.offset)
val bIsNotWordStart = bEditor.immutableText[max(0, b.offset - 1)].isWordPart val bPosition = bCaches.offsetToXY(bEditor, b.offset)
if (aIsNotWordStart != bIsNotWordStart) {
// Ensure that the first letter of a word is prioritized for tagging.
return@Comparator if (bIsNotWordStart) -1 else 1
}
when { val caretPosition = editorPriority[0].offsetToXY(editorPriority[0].caretModel.offset)
a.offset < b.offset -> -1 val aDistance = aPosition.distanceSq(caretPosition)
a.offset > b.offset -> 1 val bDistance = bPosition.distanceSq(caretPosition)
else -> 0
} return@Comparator aDistance.compareTo(bDistance)
} }
} }
} }

View File

@ -75,7 +75,7 @@ class Session(private val mainEditor: Editor, private val jumpEditors: List<Edit
val state = state ?: return val state = state ?: return
editorSettings.startEditing(editor) editorSettings.startEditing(editor)
val result = mode.type(state, charTyped, acceptedTag) val result = mode.type(state, AceConfig.layout.characterRemapping.getOrDefault(charTyped, charTyped), acceptedTag)
editorSettings.stopEditing(editor) editorSettings.stopEditing(editor)
when (result) { when (result) {

View File

@ -5,6 +5,7 @@ import com.intellij.openapi.editor.Editor
import com.intellij.ui.ColorUtil import com.intellij.ui.ColorUtil
import org.acejump.boundaries.EditorOffsetCache import org.acejump.boundaries.EditorOffsetCache
import org.acejump.boundaries.StandardBoundaries import org.acejump.boundaries.StandardBoundaries
import org.acejump.config.AceConfig
import java.awt.Graphics import java.awt.Graphics
import java.awt.Graphics2D import java.awt.Graphics2D
import java.awt.Rectangle import java.awt.Rectangle
@ -53,7 +54,7 @@ internal class TagCanvas(private val editor: Editor) : JComponent() {
return return
} }
g.color = ColorUtil.withAlpha(editor.colorsScheme.defaultBackground, 0.6) g.color = ColorUtil.withAlpha(editor.colorsScheme.defaultBackground, (AceConfig.editorFadeOpacity * 0.01).coerceIn(0.0, 1.0))
g.fillRect(0, 0, width - 1, height - 1) g.fillRect(0, 0, width - 1, height - 1)
if (markers.isEmpty()) { if (markers.isEmpty()) {