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

Compare commits

...

3 Commits

8 changed files with 101 additions and 41 deletions

View File

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

View File

@ -13,7 +13,6 @@ class AceConfigurable : Configurable {
override fun isModified() = override fun isModified() =
panel.allowedChars != settings.allowedChars || panel.allowedChars != settings.allowedChars ||
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.editorFadeOpacityPercent != settings.editorFadeOpacity ||
@ -24,7 +23,6 @@ class AceConfigurable : Configurable {
override fun apply() { override fun apply() {
settings.allowedChars = panel.allowedChars settings.allowedChars = panel.allowedChars
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 settings.editorFadeOpacity = panel.editorFadeOpacityPercent

View File

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

View File

@ -26,7 +26,6 @@ import kotlin.reflect.KProperty
@Suppress("UsePropertyAccessSyntax") @Suppress("UsePropertyAccessSyntax")
internal class AceSettingsPanel { internal class AceSettingsPanel {
private val tagAllowedCharsField = JBTextField() private val tagAllowedCharsField = JBTextField()
private val tagPrefixCharsField = JBTextField()
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()
@ -45,7 +44,6 @@ internal class AceSettingsPanel {
init { init {
tagAllowedCharsField.apply { font = Font("monospaced", font.style, font.size) } 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) } keyboardLayoutArea.apply { font = Font("monospaced", font.style, font.size) }
keyboardLayoutCombo.setupEnumItems { keyChars = it.rows.joinToString("\n") } keyboardLayoutCombo.setupEnumItems { keyChars = it.rows.joinToString("\n") }
} }
@ -53,7 +51,6 @@ internal class AceSettingsPanel {
internal val rootPanel: JPanel = panel { internal val rootPanel: JPanel = panel {
group("Characters and Layout") { group("Characters and Layout") {
row("Allowed characters in tags:") { cell(tagAllowedCharsField).columns(COLUMNS_LARGE) } 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 layout:") { cell(keyboardLayoutCombo).columns(COLUMNS_SHORT) }
row("Keyboard design:") { cell(keyboardLayoutArea).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 // Property-to-property delegation: https://stackoverflow.com/q/45074596/1772342
internal var allowedChars by tagAllowedCharsField internal var allowedChars by tagAllowedCharsField
internal var prefixChars by tagPrefixCharsField
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
@ -101,7 +97,6 @@ internal class AceSettingsPanel {
fun reset(settings: AceSettings) { fun reset(settings: AceSettings) {
allowedChars = settings.allowedChars allowedChars = settings.allowedChars
prefixChars = settings.prefixChars
keyboardLayout = settings.layout keyboardLayout = settings.layout
minQueryLength = settings.minQueryLength.toString() minQueryLength = settings.minQueryLength.toString()
editorFadeOpacityPercent = settings.editorFadeOpacity editorFadeOpacityPercent = settings.editorFadeOpacity

View File

@ -5,13 +5,18 @@ package org.acejump.input
* ergonomically difficult they are to press. * ergonomically difficult they are to press.
*/ */
@Suppress("unused", "SpellCheckingInspection") @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"), 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", characterSides = sides(listOf("123456", "qwert", "asdfg", "zxcvb"), listOf("7890", "yuiop", "hjkl", "nm"))),
QWERTZ(arrayOf("1234567890", "qwertzuiop", "asdfghjkl", "yxcvbnm"), priority = "fjghdkslavncmbxyrutzeiwoqp5849673210"), 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", characterRemapping = mapOf( 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 '1',
'ě' to '2', 'ě' to '2',
'š' to '3', 'š' 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"); NORMAN(arrayOf("1234567890", "qwdfkjurl", "asetgynioh", "zxcvbpm"), priority = "tneigysoahbvpcmxzjkufrdlwq5849673210");
internal val allChars = rows.joinToString("").toCharArray().apply(CharArray::sort).joinToString("") 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 { fun priority(char: Char): Int {
return { allPriorities.getOrDefault(tagToChar(it), Int.MAX_VALUE) } 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() }
)
}

View File

@ -1,23 +1,22 @@
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>
* Returns all possible two key tags, pre-sorted according to [tagOrder].
*/
lateinit var allPossibleTagsLowercase: List<String>
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 (!::allPossibleTagsLowercase.isInitialized) { if (!::allowedTagsSorted.isInitialized) {
reset(settings) reset(settings)
} }
} }
@ -26,22 +25,38 @@ 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) {
@Suppress("ConvertLambdaToReference") val allowedChars = processCharList(settings.allowedChars).ifEmpty { processCharList(settings.layout.allChars) }
val allSuffixChars = processCharList(settings.allowedChars).ifEmpty { processCharList(settings.layout.allChars).toList() } val allowedTags = mutableSetOf<String>()
val allPrefixChars = processCharList(settings.prefixChars).filterNot(allSuffixChars::contains).plus("")
val tagOrder = compareBy( for (c1 in allowedChars) {
String::length, allowedTags.add("$c1")
{ if (it.length == 1) Int.MIN_VALUE else allPrefixChars.indexOf(it.first().toString()) },
settings.layout.priority(String::last) for (c2 in allowedChars) {
) if (c1 != c2) {
allowedTags.add("$c1$c2")
}
}
}
allPossibleTagsLowercase = allSuffixChars allowedTagsSorted = allowedTags.sortedBy { rankPriority(settings.layout, it) }
.flatMap { suffix -> allPrefixChars.map { prefix -> "$prefix$suffix" } }
.sortedWith(tagOrder)
} }
private fun processCharList(charList: String): Set<String> { private fun processCharList(charList: String): List<Char> {
return charList.toCharArray().map(Char::lowercase).toSet() 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

@ -40,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 = KeyLayoutCache.allPossibleTagsLowercase.zip(tagSites).toMap() tagMap = generateTags(tagSites).zip(tagSites).toMap()
} }
internal fun type(char: Char): TaggingResult { 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 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>) { private fun sortResults(results: Map<Editor, IntList>, caches: Map<Editor, EditorOffsetCache>) {
for ((editor, offsets) in results) { for ((editor, offsets) in results) {
val cache = caches.getValue(editor) val cache = caches.getValue(editor)

View File

@ -12,10 +12,13 @@ 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 tag: CharArray, private val firstChar: String,
private val secondChar: String,
val offset: Int 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 { companion object {
/** /**
@ -28,7 +31,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).toCharArray(), offset) return TagMarker(tag.drop(typedTag.length), offset)
} }
} }
@ -65,11 +68,11 @@ internal class TagMarker(
g.font = font.tagFont g.font = font.tagFont
g.color = font.foregroundColor1 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.color = font.foregroundColor2
g.drawChars(tag, 1, length - 1, x + font.tagCharWidth, y) g.drawString(secondChar, x + font.tagCharWidth, y)
} }
} }