Skip to content

Commit d0fd0a1

Browse files
authored
Merge pull request #36 from siropkin/develop
v14.3 Add support for IntelliJ IDEA 2024.3
2 parents e863c66 + 7e759b0 commit d0fd0a1

16 files changed

+166
-118
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
## [Unreleased]
44

5+
## [1.4.3]
6+
7+
### Changed
8+
9+
- Add support for IntelliJ IDEA 2024.3.
10+
511
## [1.4.2]
612

713
### Changed

gradle.properties

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ pluginGroup = com.github.siropkin.kursor
44
pluginName = Kursor
55
pluginRepositoryUrl = https://github.com/siropkin/kursor
66
# SemVer format -> https://semver.org
7-
pluginVersion = 1.4.2
7+
pluginVersion = 1.4.3
88

99
# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
1010
pluginSinceBuild = 232
11-
pluginUntilBuild = 242.*
11+
pluginUntilBuild = 243.*
1212

1313
# IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension
1414
platformType = IC

src/main/kotlin/com/github/siropkin/kursor/Kursor.kt

+18-21
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.github.siropkin.kursor
22

3-
import com.github.siropkin.kursor.keyboardlayout.KeyboardLayout
4-
import com.github.siropkin.kursor.settings.KursorSettings
3+
import com.github.siropkin.kursor.keyboard.Keyboard
54
import com.intellij.openapi.editor.Caret
65
import com.intellij.openapi.editor.CaretVisualAttributes
76
import com.intellij.openapi.editor.Editor
@@ -16,14 +15,8 @@ import java.awt.event.KeyEvent
1615
import javax.swing.JComponent
1716

1817

19-
object IndicatorPosition {
20-
const val TOP = "top"
21-
const val MIDDLE = "middle"
22-
const val BOTTOM = "bottom"
23-
}
24-
2518
class Kursor(private var editor: Editor): JComponent(), ComponentListener, CaretListener {
26-
private val keyboardLayout = KeyboardLayout()
19+
private val keyboard = Keyboard()
2720

2821
init {
2922
editor.contentComponent.add(this)
@@ -117,14 +110,14 @@ class Kursor(private var editor: Editor): JComponent(), ComponentListener, Caret
117110
return
118111
}
119112

120-
val keyboardLayoutString = keyboardLayout.getLayoutInfo().toString()
121-
if (keyboardLayoutString.isEmpty()) {
113+
val keyboardLayout = keyboard.getLayout()
114+
if (keyboardLayout.isEmpty()) {
122115
return
123116
}
124117

125118
val settings = getSettings()
126119
val caret = getPrimaryCaret()
127-
val caretColor = if (settings.changeColorOnNonDefaultLanguage && keyboardLayoutString.lowercase() != settings.defaultLanguage.lowercase()) {
120+
val caretColor = if (settings.changeColorOnNonDefaultLanguage && keyboardLayout.toString().lowercase() != settings.defaultLanguage.lowercase()) {
128121
settings.colorOnNonDefaultLanguage
129122
} else {
130123
null
@@ -134,31 +127,35 @@ class Kursor(private var editor: Editor): JComponent(), ComponentListener, Caret
134127
setCaretColor(caret, caretColor)
135128
}
136129

130+
if (!settings.showTextIndicator) {
131+
return
132+
}
133+
137134
val isCapsLockOn = settings.indicateCapsLock && getIsCapsLockOn()
138-
val showTextIndicator = settings.showTextIndicator && (settings.indicateDefaultLanguage || isCapsLockOn || keyboardLayoutString.lowercase() != settings.defaultLanguage.lowercase())
139-
if (!showTextIndicator) {
135+
val isDefaultLanguage = keyboardLayout.toString().lowercase() == settings.defaultLanguage.lowercase()
136+
if (!isCapsLockOn && isDefaultLanguage && !settings.indicateDefaultLanguage) {
140137
return
141138
}
142139

143-
val displayText = if (isCapsLockOn) {
144-
keyboardLayoutString.uppercase()
140+
val textIndicatorString = if (isCapsLockOn) {
141+
keyboardLayout.toString().uppercase()
145142
} else {
146-
keyboardLayoutString.lowercase()
143+
keyboardLayout.toString().lowercase()
147144
}
148145
val caretWidth = getCaretWidth(caret)
149146
val caretHeight = getCaretHeight(caret)
150147
val caretPosition = getCaretPosition(caret)
151148

152149
val indicatorOffsetX = caretWidth + settings.textIndicatorHorizontalOffset
153150
val indicatorOffsetY = when (settings.textIndicatorVerticalPosition) {
154-
IndicatorPosition.TOP -> (if (caret.visualPosition.line == 0) settings.textIndicatorFontSize else settings.textIndicatorFontSize / 2) - 1
155-
IndicatorPosition.MIDDLE -> caretHeight / 2 + settings.textIndicatorFontSize / 2 - 1
156-
IndicatorPosition.BOTTOM -> caretHeight + 3
151+
TextIndicatorVerticalPositions.TOP -> (if (caret.visualPosition.line == 0) settings.textIndicatorFontSize else settings.textIndicatorFontSize / 2) - 1
152+
TextIndicatorVerticalPositions.MIDDLE -> caretHeight / 2 + settings.textIndicatorFontSize / 2 - 1
153+
TextIndicatorVerticalPositions.BOTTOM -> caretHeight + 3
157154
else -> 0
158155
}
159156

160157
g.font = Font(settings.textIndicatorFontName, settings.textIndicatorFontStyle, settings.textIndicatorFontSize)
161158
g.color = getColorWithAlpha(caretColor ?: getDefaultCaretColor()!!, settings.textIndicatorFontAlpha)
162-
g.drawString(displayText, caretPosition.x + indicatorOffsetX, caretPosition.y + indicatorOffsetY)
159+
g.drawString(textIndicatorString, caretPosition.x + indicatorOffsetX, caretPosition.y + indicatorOffsetY)
163160
}
164161
}

src/main/kotlin/com/github/siropkin/kursor/settings/KursorSettings.kt renamed to src/main/kotlin/com/github/siropkin/kursor/KursorSettings.kt

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
package com.github.siropkin.kursor.settings
1+
package com.github.siropkin.kursor
22

3-
import com.github.siropkin.kursor.IndicatorPosition
43
import com.intellij.openapi.application.ApplicationManager
54
import com.intellij.openapi.components.PersistentStateComponent
65
import com.intellij.openapi.components.State
@@ -31,7 +30,7 @@ class KursorSettings : PersistentStateComponent<KursorSettings> {
3130
var textIndicatorFontSize: Int = 11
3231
var textIndicatorFontAlpha: Int = 180
3332

34-
var textIndicatorVerticalPosition: String = IndicatorPosition.TOP
33+
var textIndicatorVerticalPosition: String = TextIndicatorVerticalPositions.TOP
3534
var textIndicatorHorizontalOffset: Int = 4
3635

3736
var indicateCapsLock: Boolean = true

src/main/kotlin/com/github/siropkin/kursor/settings/KursorSettingsComponent.kt renamed to src/main/kotlin/com/github/siropkin/kursor/KursorSettingsComponent.kt

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
package com.github.siropkin.kursor.settings
1+
package com.github.siropkin.kursor
22

3-
import com.github.siropkin.kursor.IndicatorPosition
4-
import com.github.siropkin.kursor.keyboardlayout.KeyboardLayout
3+
import com.github.siropkin.kursor.keyboard.Keyboard
54
import com.intellij.openapi.ui.ComboBox
65
import com.intellij.ui.ColorPanel
76
import com.intellij.ui.components.JBCheckBox
@@ -20,7 +19,7 @@ private const val LABEL_SPACING = 10
2019
private const val COMPONENT_SPACING = 35
2120

2221
class KursorSettingsComponent {
23-
private val keyboardLayout = KeyboardLayout()
22+
private val keyboardLayout = Keyboard()
2423

2524
private val defaultLanguageComponent = JBTextField("", 5)
2625
private val detectKeyboardLayoutButton = JButton("Detect Keyboard Layout")
@@ -37,7 +36,7 @@ class KursorSettingsComponent {
3736
private val textIndicatorFontSizeComponent = JBTextField()
3837
private val textIndicatorFontAlphaComponent = JBTextField()
3938

40-
private val textIndicatorVerticalPositionComponent = ComboBox(arrayOf(IndicatorPosition.TOP, IndicatorPosition.MIDDLE, IndicatorPosition.BOTTOM))
39+
private val textIndicatorVerticalPositionComponent = ComboBox(arrayOf(TextIndicatorVerticalPositions.TOP, TextIndicatorVerticalPositions.MIDDLE, TextIndicatorVerticalPositions.BOTTOM))
4140
private val textIndicatorHorizontalOffsetComponent = JBTextField()
4241

4342
var panel: JPanel = FormBuilder.createFormBuilder()
@@ -148,7 +147,7 @@ class KursorSettingsComponent {
148147
languagePanel.add(detectKeyboardLayoutButton, createRbc(2, 0, 1.0, COMPONENT_SPACING))
149148

150149
detectKeyboardLayoutButton.addActionListener {
151-
defaultLanguageComponent.text = keyboardLayout.getLayoutInfo().toString().lowercase()
150+
defaultLanguageComponent.text = keyboardLayout.getLayout().toString().lowercase()
152151
}
153152

154153
return languagePanel

src/main/kotlin/com/github/siropkin/kursor/settings/KursorSettingsConfigurable.kt renamed to src/main/kotlin/com/github/siropkin/kursor/KursorSettingsConfigurable.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.github.siropkin.kursor.settings
1+
package com.github.siropkin.kursor
22

33
import com.intellij.openapi.options.Configurable
44
import javax.swing.JComponent
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.github.siropkin.kursor
2+
3+
object TextIndicatorVerticalPositions {
4+
const val TOP = "top"
5+
const val MIDDLE = "middle"
6+
const val BOTTOM = "bottom"
7+
}
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,72 @@
1-
package com.github.siropkin.kursor.keyboardlayout
1+
package com.github.siropkin.kursor.keyboard
22

33
import com.sun.jna.Platform
44
import com.sun.jna.platform.win32.User32
55
import com.sun.jna.platform.win32.WinDef
66
import com.sun.jna.platform.win32.WinDef.HKL
77
import java.awt.im.InputContext
8-
import java.io.BufferedReader
9-
import java.io.IOException
108

119

12-
class KeyboardLayout {
13-
private val unknown = "UNK"
14-
private var linuxDistribution: String = System.getenv("DESKTOP_SESSION")?.lowercase() ?: ""
15-
private var linuxDesktopGroup: String = System.getenv("XDG_SESSION_TYPE")?.lowercase() ?: ""
16-
private var linuxKeyboardLayoutsCache: List<String> = emptyList()
10+
private const val UNKNOWN = "UNK"
1711

18-
fun getLayoutInfo(): KeyboardLayoutInfo {
12+
class Keyboard {
13+
private var linuxConfig: LinuxConfig? = null
14+
15+
fun getLayout(): KeyboardLayout {
16+
if (Platform.isLinux() && linuxConfig == null) {
17+
linuxConfig = LinuxConfig()
18+
}
1919
return when {
20-
Platform.isLinux() -> getLinuxLayoutInfo()
21-
Platform.isMac() -> getMacLayoutInfo()
22-
Platform.isWindows() -> getWindowsLayoutInfo()
23-
else -> getUnknownLayoutInfo()
20+
Platform.isLinux() -> getLinuxLayout()
21+
Platform.isMac() -> getMacLayout()
22+
Platform.isWindows() -> getWindowsLayout()
23+
else -> getUnknownLayout()
2424
}
2525
}
2626

27-
private fun getUnknownLayoutInfo(): KeyboardLayoutInfo {
28-
return KeyboardLayoutInfo(unknown, unknown, unknown)
27+
private fun getUnknownLayout(): KeyboardLayout {
28+
return KeyboardLayout(UNKNOWN, UNKNOWN, UNKNOWN)
2929
}
3030

31-
private fun getLinuxLayoutInfo(): KeyboardLayoutInfo {
31+
private fun getLinuxLayout(): KeyboardLayout {
3232
// InputContext.getInstance().locale is not working on Linux: it always returns "en_US"
3333
// This is not the ideal solution because it involves executing a shell command to know the current keyboard layout
3434
// which might affect the performance. And we have different commands for different Linux distributions.
3535
// But it is the only solution I found that works on Linux.
3636
// For Linux we know only keyboard layout and do not know keyboard language
37+
val config = linuxConfig ?: return getUnknownLayout()
3738
return when {
38-
linuxDistribution == "ubuntu" -> getUbuntuLayoutInfo()
39-
linuxDesktopGroup == "wayland" -> getWaylandLayoutInfo()
40-
else -> getOtherLinuxLayoutInfo()
39+
config.distribution == "ubuntu" -> getUbuntuLayout()
40+
config.desktopGroup == "wayland" -> getWaylandLayout()
41+
else -> getOtherLinuxLayout()
4142
}
4243
}
4344

44-
private fun getUbuntuLayoutInfo(): KeyboardLayoutInfo {
45+
private fun getUbuntuLayout(): KeyboardLayout {
4546
// Output example: [('xkb', 'us'), ('xkb', 'ru'), ('xkb', 'ca+eng')]
46-
val split = executeNativeCommand(arrayOf("gsettings", "get", "org.gnome.desktop.input-sources", "mru-sources"))
47+
val split = Utils.executeNativeCommand(arrayOf("gsettings", "get", "org.gnome.desktop.input-sources", "mru-sources"))
4748
.substringAfter("('xkb', '")
4849
.substringBefore("')")
4950
.split("+")
5051
val language = if (split.size > 1) split[1] else ""
5152
val country = split[0]
52-
return KeyboardLayoutInfo(language, country, "")
53+
return KeyboardLayout(language, country, "")
5354
}
5455

55-
private fun getWaylandLayoutInfo(): KeyboardLayoutInfo {
56+
private fun getWaylandLayout(): KeyboardLayout {
5657
// FIXME: Other Linux distribution commands not working "Wayland",
5758
// see: https://github.com/siropkin/kursor/issues/3
58-
return getUnknownLayoutInfo()
59+
return getUnknownLayout()
5960
}
6061

61-
private fun getOtherLinuxLayoutInfo(): KeyboardLayoutInfo {
62-
if (linuxKeyboardLayoutsCache.isEmpty()) {
62+
private fun getOtherLinuxLayout(): KeyboardLayout {
63+
val config = linuxConfig ?: return getUnknownLayout()
64+
if (config.availableKeyboardLayouts.isEmpty()) {
6365
// Output example: rules: evdev
6466
//model: pc105
6567
//layout: us
6668
//options: grp:win_space_toggle,terminate:ctrl_alt_bksp
67-
linuxKeyboardLayoutsCache = executeNativeCommand(arrayOf("setxkbmap", "-query"))
69+
config.availableKeyboardLayouts = Utils.executeNativeCommand(arrayOf("setxkbmap", "-query"))
6870
.substringAfter("layout:")
6971
.substringBefore("\n")
7072
.trim()
@@ -98,46 +100,45 @@ class KeyboardLayout {
98100
// Standby: 0 Suspend: 0 Off: 0
99101
// DPMS is Enabled
100102
// Monitor is On
101-
val linuxCurrentKeyboardLayoutIndex = executeNativeCommand(arrayOf("xset", "-q"))
103+
val linuxCurrentKeyboardLayoutIndex = Utils.executeNativeCommand(arrayOf("xset", "-q"))
102104
.substringAfter("LED mask:")
103105
.substringBefore("\n")
104106
.trim()
105107
.substring(4, 5)
106108
.toInt(16)
107109

108110
// Additional check to avoid out-of-bounds exception
109-
if (linuxCurrentKeyboardLayoutIndex >= linuxKeyboardLayoutsCache.size) {
110-
return getUnknownLayoutInfo()
111+
if (linuxCurrentKeyboardLayoutIndex >= config.availableKeyboardLayouts.size) {
112+
return getUnknownLayout()
111113
}
112114

113115
// This is a bad solution because it returns 0 if it's a default layout and 1 in other cases,
114116
// and if user has more than two layouts, we do not know which one is really on
115-
if (linuxKeyboardLayoutsCache.size > 2 && linuxCurrentKeyboardLayoutIndex > 0) {
116-
return getUnknownLayoutInfo()
117+
if (config.availableKeyboardLayouts.size > 2 && linuxCurrentKeyboardLayoutIndex > 0) {
118+
return getUnknownLayout()
117119
}
118120

119-
val country = linuxKeyboardLayoutsCache[linuxCurrentKeyboardLayoutIndex]
120-
return KeyboardLayoutInfo("", country, "")
121+
val country = config.availableKeyboardLayouts[linuxCurrentKeyboardLayoutIndex]
122+
return KeyboardLayout("", country, "")
121123
}
122124

123-
private fun getMacLayoutInfo(): KeyboardLayoutInfo {
125+
private fun getMacLayout(): KeyboardLayout {
124126
val locale = InputContext.getInstance().locale
125-
// Variant example for US: UserDefined_252
126-
val variant = MacKeyboardVariants[locale.variant] ?: ""
127-
return KeyboardLayoutInfo(locale.language, locale.country, variant)
127+
val localeVariant = locale.variant.removePrefix("UserDefined_")
128+
val variant = MacStandardKeyboardVariants[localeVariant]
129+
?: MacSogouPinyinVariants[localeVariant]
130+
?: MacRimeSquirrelVariants[localeVariant]
131+
?: ""
132+
return KeyboardLayout(locale.language, locale.country, variant)
128133
}
129134

130-
private fun getWindowsLayoutInfo(): KeyboardLayoutInfo {
135+
private fun getWindowsLayout(): KeyboardLayout {
131136
val locale = InputContext.getInstance().locale
132137
// Standard locale object does not return correct info in case user set different keyboard inputs for one language
133138
// see: https://github.com/siropkin/kursor/issues/4
134139
val user32 = User32.INSTANCE
135-
val fgWindow: WinDef.HWND? = user32.GetForegroundWindow() // Get the handle of the foreground window
136-
137-
if (fgWindow == null) {
138-
return KeyboardLayoutInfo(locale.language, locale.country, "")
139-
}
140-
140+
val fgWindow: WinDef.HWND = user32.GetForegroundWindow()
141+
?: return KeyboardLayout(locale.language, locale.country, "") // Get the handle of the foreground window
141142
val threadId = user32.GetWindowThreadProcessId(fgWindow, null) // Get the thread ID of the foreground window
142143
val hkl: HKL = user32.GetKeyboardLayout(threadId) // Get the keyboard layout for the thread
143144
// FIXME: It should be a better way how to convert pointer to string
@@ -151,18 +152,7 @@ class KeyboardLayout {
151152
else -> layoutId.substring(2).padStart(8, '0')
152153
}
153154
val variant = WindowsKeyboardVariants[layoutId.uppercase()] ?: ""
154-
return KeyboardLayoutInfo(locale.language, locale.country, variant)
155-
}
156-
157-
private fun executeNativeCommand(command: Array<String>): String {
158-
return try {
159-
val process = Runtime.getRuntime().exec(command)
160-
process.waitFor()
161-
process.inputStream.bufferedReader().use(BufferedReader::readText)
162-
} catch (e: IOException) {
163-
e.printStackTrace()
164-
""
165-
}
155+
return KeyboardLayout(locale.language, locale.country, variant)
166156
}
167157
}
168158

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.github.siropkin.kursor.keyboard
2+
3+
4+
data class KeyboardLayout(val language: String, val country: String, val variant: String, val asciiMode: Boolean = false) {
5+
fun isEmpty(): Boolean = language.isEmpty() && country.isEmpty() && variant.isEmpty()
6+
7+
override fun toString(): String = listOf(variant, country, language).firstOrNull { it.isNotEmpty() } ?: ""
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.github.siropkin.kursor.keyboard
2+
3+
class LinuxConfig {
4+
var distribution: String = System.getenv("DESKTOP_SESSION")?.lowercase() ?: ""
5+
var desktopGroup: String = System.getenv("XDG_SESSION_TYPE")?.lowercase() ?: ""
6+
var availableKeyboardLayouts: List<String> = emptyList()
7+
}

0 commit comments

Comments
 (0)