mirror of
https://github.com/chylex/IntelliJ-AceJump.git
synced 2024-11-24 23:42:46 +01:00
Compare commits
3 Commits
01c38df82a
...
5979579042
Author | SHA1 | Date | |
---|---|---|---|
5979579042 | |||
ea61d49aa6 | |||
dbc6db108d |
@ -8,7 +8,7 @@ plugins {
|
||||
}
|
||||
|
||||
group = "org.acejump"
|
||||
version = "chylex-21"
|
||||
version = "chylex-22"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
@ -13,7 +13,6 @@ class AceConfigurable : Configurable {
|
||||
|
||||
override fun isModified() =
|
||||
panel.allowedChars != settings.allowedChars ||
|
||||
panel.prefixChars != settings.prefixChars ||
|
||||
panel.keyboardLayout != settings.layout ||
|
||||
panel.minQueryLengthInt != settings.minQueryLength ||
|
||||
panel.editorFadeOpacityPercent != settings.editorFadeOpacity ||
|
||||
@ -24,7 +23,6 @@ class AceConfigurable : Configurable {
|
||||
|
||||
override fun apply() {
|
||||
settings.allowedChars = panel.allowedChars
|
||||
settings.prefixChars = panel.prefixChars
|
||||
settings.layout = panel.keyboardLayout
|
||||
settings.minQueryLength = panel.minQueryLengthInt ?: settings.minQueryLength
|
||||
settings.editorFadeOpacity = panel.editorFadeOpacityPercent
|
||||
|
@ -8,7 +8,6 @@ import java.awt.Color
|
||||
data class AceSettings(
|
||||
var layout: KeyLayout = QWERTY,
|
||||
var allowedChars: String = layout.allChars,
|
||||
var prefixChars: String = ";",
|
||||
var minQueryLength: Int = 1,
|
||||
var editorFadeOpacity: Int = 70,
|
||||
|
||||
|
@ -26,7 +26,6 @@ import kotlin.reflect.KProperty
|
||||
@Suppress("UsePropertyAccessSyntax")
|
||||
internal class AceSettingsPanel {
|
||||
private val tagAllowedCharsField = JBTextField()
|
||||
private val tagPrefixCharsField = JBTextField()
|
||||
private val keyboardLayoutCombo = ComboBox<KeyLayout>()
|
||||
private val keyboardLayoutArea = JBTextArea().apply { isEditable = false }
|
||||
private val minQueryLengthField = JBTextField()
|
||||
@ -45,7 +44,6 @@ internal class AceSettingsPanel {
|
||||
|
||||
init {
|
||||
tagAllowedCharsField.apply { font = Font("monospaced", font.style, font.size) }
|
||||
tagPrefixCharsField.apply { font = Font("monospaced", font.style, font.size) }
|
||||
keyboardLayoutArea.apply { font = Font("monospaced", font.style, font.size) }
|
||||
keyboardLayoutCombo.setupEnumItems { keyChars = it.rows.joinToString("\n") }
|
||||
}
|
||||
@ -53,7 +51,6 @@ internal class AceSettingsPanel {
|
||||
internal val rootPanel: JPanel = panel {
|
||||
group("Characters and Layout") {
|
||||
row("Allowed characters in tags:") { cell(tagAllowedCharsField).columns(COLUMNS_LARGE) }
|
||||
row("Allowed prefix characters in tags:") { cell(tagPrefixCharsField).columns(COLUMNS_MEDIUM) }
|
||||
row("Keyboard layout:") { cell(keyboardLayoutCombo).columns(COLUMNS_SHORT) }
|
||||
row("Keyboard design:") { cell(keyboardLayoutArea).columns(COLUMNS_SHORT) }
|
||||
}
|
||||
@ -81,7 +78,6 @@ internal class AceSettingsPanel {
|
||||
|
||||
// Property-to-property delegation: https://stackoverflow.com/q/45074596/1772342
|
||||
internal var allowedChars by tagAllowedCharsField
|
||||
internal var prefixChars by tagPrefixCharsField
|
||||
internal var keyboardLayout by keyboardLayoutCombo
|
||||
internal var keyChars by keyboardLayoutArea
|
||||
internal var minQueryLength by minQueryLengthField
|
||||
@ -101,7 +97,6 @@ internal class AceSettingsPanel {
|
||||
|
||||
fun reset(settings: AceSettings) {
|
||||
allowedChars = settings.allowedChars
|
||||
prefixChars = settings.prefixChars
|
||||
keyboardLayout = settings.layout
|
||||
minQueryLength = settings.minQueryLength.toString()
|
||||
editorFadeOpacityPercent = settings.editorFadeOpacity
|
||||
|
@ -5,13 +5,18 @@ package org.acejump.input
|
||||
* ergonomically difficult they are to press.
|
||||
*/
|
||||
@Suppress("unused", "SpellCheckingInspection")
|
||||
enum class KeyLayout(internal val rows: Array<String>, priority: String, internal val characterRemapping: Map<Char, Char> = emptyMap()) {
|
||||
enum class KeyLayout(
|
||||
internal val rows: Array<String>,
|
||||
priority: String,
|
||||
private val characterSides: Pair<Set<Char>, Set<Char>> = Pair(emptySet(), emptySet()),
|
||||
internal val characterRemapping: Map<Char, Char> = emptyMap(),
|
||||
) {
|
||||
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"),
|
||||
QWERTZ(arrayOf("1234567890", "qwertzuiop", "asdfghjkl", "yxcvbnm"), priority = "fjghdkslavncmbxyrutzeiwoqp5849673210"),
|
||||
QWERTZ_CZ(arrayOf("1234567890", "qwertzuiop", "asdfghjkl", "yxcvbnm"), priority = "fjghdkslavncmbxyrutzeiwoqp5849673210", characterRemapping = mapOf(
|
||||
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',
|
||||
@ -28,9 +33,20 @@ enum class KeyLayout(internal val rows: Array<String>, priority: String, interna
|
||||
NORMAN(arrayOf("1234567890", "qwdfkjurl", "asetgynioh", "zxcvbpm"), priority = "tneigysoahbvpcmxzjkufrdlwq5849673210");
|
||||
|
||||
internal val allChars = rows.joinToString("").toCharArray().apply(CharArray::sort).joinToString("")
|
||||
internal val allPriorities = priority.mapIndexed { index, char -> char to index }.toMap()
|
||||
private val allPriorities = priority.mapIndexed { index, char -> char to index }.toMap()
|
||||
|
||||
internal inline fun priority(crossinline tagToChar: (String) -> Char): (String) -> Int {
|
||||
return { allPriorities.getOrDefault(tagToChar(it), Int.MAX_VALUE) }
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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() }
|
||||
)
|
||||
}
|
||||
|
@ -1,23 +1,22 @@
|
||||
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 {
|
||||
/**
|
||||
* Returns all possible two key tags, pre-sorted according to [tagOrder].
|
||||
*/
|
||||
lateinit var allPossibleTagsLowercase: List<String>
|
||||
lateinit var allowedTagsSorted: List<String>
|
||||
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 (!::allPossibleTagsLowercase.isInitialized) {
|
||||
if (!::allowedTagsSorted.isInitialized) {
|
||||
reset(settings)
|
||||
}
|
||||
}
|
||||
@ -26,22 +25,38 @@ internal object KeyLayoutCache {
|
||||
* Re-initializes cached data according to updated settings.
|
||||
*/
|
||||
fun reset(settings: AceSettings) {
|
||||
@Suppress("ConvertLambdaToReference")
|
||||
val allSuffixChars = processCharList(settings.allowedChars).ifEmpty { processCharList(settings.layout.allChars).toList() }
|
||||
val allPrefixChars = processCharList(settings.prefixChars).filterNot(allSuffixChars::contains).plus("")
|
||||
val allowedChars = processCharList(settings.allowedChars).ifEmpty { processCharList(settings.layout.allChars) }
|
||||
val allowedTags = mutableSetOf<String>()
|
||||
|
||||
val tagOrder = compareBy(
|
||||
String::length,
|
||||
{ if (it.length == 1) Int.MIN_VALUE else allPrefixChars.indexOf(it.first().toString()) },
|
||||
settings.layout.priority(String::last)
|
||||
)
|
||||
for (c1 in allowedChars) {
|
||||
allowedTags.add("$c1")
|
||||
|
||||
allPossibleTagsLowercase = allSuffixChars
|
||||
.flatMap { suffix -> allPrefixChars.map { prefix -> "$prefix$suffix" } }
|
||||
.sortedWith(tagOrder)
|
||||
for (c2 in allowedChars) {
|
||||
if (c1 != c2) {
|
||||
allowedTags.add("$c1$c2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allowedTagsSorted = allowedTags.sortedBy { rankPriority(settings.layout, it) }
|
||||
}
|
||||
|
||||
private fun processCharList(charList: String): Set<String> {
|
||||
return charList.toCharArray().map(Char::lowercase).toSet()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -40,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 = KeyLayoutCache.allPossibleTagsLowercase.zip(tagSites).toMap()
|
||||
tagMap = generateTags(tagSites).zip(tagSites).toMap()
|
||||
}
|
||||
|
||||
internal fun type(char: Char): TaggingResult {
|
||||
@ -61,6 +61,40 @@ class Tagger(private val editors: List<Editor>, results: Map<Editor, IntList>) {
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private fun generateTags(tagSites: List<Tag>): List<String> {
|
||||
val tags = mutableListOf<String>()
|
||||
|
||||
val containedSingleCharTags = mutableSetOf<Char>()
|
||||
val blockedSingleCharTags = mutableSetOf<Char>()
|
||||
|
||||
for (tag in KeyLayoutCache.allowedTagsSorted) {
|
||||
val firstChar = tag.first()
|
||||
|
||||
if (tag.length == 1) {
|
||||
if (firstChar in blockedSingleCharTags) {
|
||||
continue
|
||||
}
|
||||
|
||||
containedSingleCharTags.add(firstChar)
|
||||
}
|
||||
else {
|
||||
if (containedSingleCharTags.remove(firstChar)) {
|
||||
tags.remove(firstChar.toString())
|
||||
}
|
||||
|
||||
blockedSingleCharTags.add(firstChar)
|
||||
}
|
||||
|
||||
tags.add(tag)
|
||||
|
||||
if (tags.size >= tagSites.size) {
|
||||
return tags
|
||||
}
|
||||
}
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
private fun sortResults(results: Map<Editor, IntList>, caches: Map<Editor, EditorOffsetCache>) {
|
||||
for ((editor, offsets) in results) {
|
||||
val cache = caches.getValue(editor)
|
||||
|
@ -12,10 +12,13 @@ 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 tag: CharArray,
|
||||
private val firstChar: String,
|
||||
private val secondChar: String,
|
||||
val offset: Int
|
||||
) {
|
||||
private val length = tag.size
|
||||
private constructor(tag: String, offset: Int) : this(tag.first().toString(), tag.drop(1), offset)
|
||||
|
||||
private val length = firstChar.length + secondChar.length
|
||||
|
||||
companion object {
|
||||
/**
|
||||
@ -28,7 +31,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).toCharArray(), offset)
|
||||
return TagMarker(tag.drop(typedTag.length), offset)
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,11 +68,11 @@ internal class TagMarker(
|
||||
|
||||
g.font = font.tagFont
|
||||
g.color = font.foregroundColor1
|
||||
g.drawChars(tag, 0, 1, x, y)
|
||||
g.drawString(firstChar, x, y)
|
||||
|
||||
if (tag.size > 1) {
|
||||
if (secondChar.isNotEmpty()) {
|
||||
g.color = font.foregroundColor2
|
||||
g.drawChars(tag, 1, length - 1, x + font.tagCharWidth, y)
|
||||
g.drawString(secondChar, x + font.tagCharWidth, y)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user