feat: support keymap sync

This commit is contained in:
hstyi
2025-01-15 20:03:57 +08:00
committed by hstyi
parent d321e766b1
commit e30316eab3
6 changed files with 105 additions and 35 deletions

View File

@@ -2,7 +2,6 @@ package app.termora
import app.termora.Application.ohMyJson
import app.termora.highlight.KeywordHighlight
import app.termora.keymap.KeyShortcut
import app.termora.keymap.Keymap
import app.termora.keymgr.OhKeyPair
import app.termora.macro.Macro
@@ -14,12 +13,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.*
import org.apache.commons.io.IOUtils
import org.slf4j.LoggerFactory
import java.io.File
import java.util.*
import javax.swing.KeyStroke
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
@@ -65,36 +62,17 @@ class Database private constructor(private val env: Environment) : Disposable {
fun getKeymaps(): Collection<Keymap> {
val array = env.computeInTransaction { tx ->
openCursor<JsonObject>(tx, KEYMAP_STORE) { _, value ->
ohMyJson.decodeFromString<JsonObject>(value)
openCursor<String>(tx, KEYMAP_STORE) { _, value ->
value
}.values
}
val shortcuts = mutableListOf<Keymap>()
for (json in array.iterator()) {
val name = json["name"]?.jsonPrimitive?.content ?: continue
val readonly = json["readonly"]?.jsonPrimitive?.booleanOrNull ?: false
val keymap = Keymap(name, null, readonly)
for (shortcut in (json["shortcuts"]?.jsonArray ?: emptyList()).map { it.jsonObject }) {
val keyStroke = shortcut["keyStroke"]?.jsonPrimitive?.contentOrNull ?: continue
val keyboard = shortcut["keyboard"]?.jsonPrimitive?.booleanOrNull ?: true
val actionIds = ohMyJson.decodeFromJsonElement<List<String>>(
shortcut["actionIds"]?.jsonArray
?: continue
)
if (keyboard) {
val keyShortcut = KeyShortcut(KeyStroke.getKeyStroke(keyStroke))
for (actionId in actionIds) {
keymap.addShortcut(actionId, keyShortcut)
}
}
val keymaps = mutableListOf<Keymap>()
for (text in array.iterator()) {
keymaps.add(Keymap.fromJSON(text) ?: continue)
}
shortcuts.add(keymap)
}
return shortcuts
return keymaps
}
fun addKeymap(keymap: Keymap) {
@@ -599,6 +577,7 @@ class Database private constructor(private val env: Environment) : Disposable {
var rangeKeyPairs by BooleanPropertyDelegate(true)
var rangeKeywordHighlights by BooleanPropertyDelegate(true)
var rangeMacros by BooleanPropertyDelegate(true)
var rangeKeymap by BooleanPropertyDelegate(true)
/**
* Token

View File

@@ -402,6 +402,7 @@ class SettingsOptionsPane : OptionsPane() {
val keysCheckBox = JCheckBox(I18n.getString("termora.settings.sync.range.keys"))
val keywordHighlightsCheckBox = JCheckBox(I18n.getString("termora.settings.sync.range.keyword-highlights"))
val macrosCheckBox = JCheckBox(I18n.getString("termora.macro"))
val keymapCheckBox = JCheckBox(I18n.getString("termora.settings.keymap"))
val visitGistBtn = JButton(Icons.externalLink)
val getTokenBtn = JButton(Icons.externalLink)
@@ -593,6 +594,9 @@ class SettingsOptionsPane : OptionsPane() {
if (macrosCheckBox.isSelected) {
range.add(SyncRange.Macros)
}
if (keymapCheckBox.isSelected) {
range.add(SyncRange.Keymap)
}
return SyncConfig(
type = typeComboBox.selectedItem as SyncType,
token = String(tokenTextField.password),
@@ -664,6 +668,7 @@ class SettingsOptionsPane : OptionsPane() {
tokenTextField.isEnabled = false
keysCheckBox.isEnabled = false
macrosCheckBox.isEnabled = false
keymapCheckBox.isEnabled = false
keywordHighlightsCheckBox.isEnabled = false
hostsCheckBox.isEnabled = false
domainTextField.isEnabled = false
@@ -696,6 +701,7 @@ class SettingsOptionsPane : OptionsPane() {
hostsCheckBox.isEnabled = true
typeComboBox.isEnabled = true
macrosCheckBox.isEnabled = true
keymapCheckBox.isEnabled = true
gistTextField.isEnabled = true
tokenTextField.isEnabled = true
domainTextField.isEnabled = true
@@ -756,11 +762,13 @@ class SettingsOptionsPane : OptionsPane() {
keysCheckBox.isFocusable = false
keywordHighlightsCheckBox.isFocusable = false
macrosCheckBox.isFocusable = false
keymapCheckBox.isFocusable = false
hostsCheckBox.isSelected = sync.rangeHosts
keysCheckBox.isSelected = sync.rangeKeyPairs
keywordHighlightsCheckBox.isSelected = sync.rangeKeywordHighlights
macrosCheckBox.isSelected = sync.rangeMacros
keymapCheckBox.isSelected = sync.rangeKeymap
typeComboBox.selectedItem = sync.type
gistTextField.text = sync.gist
@@ -823,6 +831,7 @@ class SettingsOptionsPane : OptionsPane() {
.add(keysCheckBox).xy(3, 1)
.add(keywordHighlightsCheckBox).xy(5, 1)
.add(macrosCheckBox).xy(1, 3)
.add(keymapCheckBox).xy(3, 3)
.build()
var rows = 1

View File

@@ -1,5 +1,6 @@
package app.termora.actions
import app.termora.ApplicationScope
import app.termora.I18n
import app.termora.Icons
import app.termora.SettingsDialog
@@ -27,7 +28,8 @@ class SettingsAction : AnAction(
init {
FlatDesktop.setPreferencesHandler {
val owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().focusOwner
if (owner != null) {
// Doorman 的情况下不允许打开
if (owner != null && ApplicationScope.windowScopes().isNotEmpty()) {
actionPerformed(ActionEvent(owner, ActionEvent.ACTION_PERFORMED, StringUtils.EMPTY))
}
}

View File

@@ -2,10 +2,8 @@ package app.termora.keymap
import app.termora.Application.ohMyJson
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.encodeToJsonElement
import kotlinx.serialization.json.put
import kotlinx.serialization.json.*
import javax.swing.KeyStroke
open class Keymap(
val name: String,
@@ -16,6 +14,37 @@ open class Keymap(
val isReadonly: Boolean = false,
) {
companion object {
fun fromJSON(text: String): Keymap? {
return fromJSON(ohMyJson.decodeFromString<JsonObject>(text))
}
fun fromJSON(json: JsonObject): Keymap? {
val shortcuts = mutableListOf<Keymap>()
val name = json["name"]?.jsonPrimitive?.content ?: return null
val readonly = json["readonly"]?.jsonPrimitive?.booleanOrNull ?: return null
val keymap = Keymap(name, null, readonly)
for (shortcut in (json["shortcuts"]?.jsonArray ?: emptyList()).map { it.jsonObject }) {
val keyStroke = shortcut["keyStroke"]?.jsonPrimitive?.contentOrNull ?: continue
val keyboard = shortcut["keyboard"]?.jsonPrimitive?.booleanOrNull ?: true
val actionIds = ohMyJson.decodeFromJsonElement<List<String>>(
shortcut["actionIds"]?.jsonArray
?: continue
)
if (keyboard) {
val keyShortcut = KeyShortcut(KeyStroke.getKeyStroke(keyStroke))
for (actionId in actionIds) {
keymap.addShortcut(actionId, keyShortcut)
}
}
}
shortcuts.add(keymap)
return keymap
}
}
private val shortcuts = mutableMapOf<Shortcut, MutableList<String>>()
open fun addShortcut(actionId: String, shortcut: Shortcut) {
@@ -66,7 +95,11 @@ open class Keymap(
fun toJSON(): String {
return ohMyJson.encodeToString(buildJsonObject {
return ohMyJson.encodeToString(toJSONObject())
}
fun toJSONObject(): JsonObject {
return buildJsonObject {
put("name", name)
put("readonly", isReadonly)
parent?.let { put("parent", it.name) }
@@ -81,7 +114,7 @@ open class Keymap(
})
}
})
})
}
}
}

View File

@@ -8,11 +8,14 @@ import app.termora.AES.encodeBase64String
import app.termora.Application.ohMyJson
import app.termora.highlight.KeywordHighlight
import app.termora.highlight.KeywordHighlightManager
import app.termora.keymap.Keymap
import app.termora.keymap.KeymapManager
import app.termora.keymgr.KeyManager
import app.termora.keymgr.OhKeyPair
import app.termora.macro.Macro
import app.termora.macro.MacroManager
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Request
@@ -32,6 +35,7 @@ abstract class GitSyncer : Syncer {
protected val keyManager get() = KeyManager.getInstance()
protected val keywordHighlightManager get() = KeywordHighlightManager.getInstance()
protected val macroManager get() = MacroManager.getInstance()
protected val keymapManager get() = KeymapManager.getInstance()
override fun pull(config: SyncConfig): GistResponse {
@@ -74,6 +78,13 @@ abstract class GitSyncer : Syncer {
}
}
// decode keymaps
if (config.ranges.contains(SyncRange.Macros)) {
gistResponse.gists.findLast { it.filename == "Keymaps" }?.let {
decodeKeymaps(it.content, config)
}
}
if (log.isInfoEnabled) {
log.info("Type: ${config.type} , Gist: ${config.gistId} Pulled")
}
@@ -231,6 +242,17 @@ abstract class GitSyncer : Syncer {
}
}
private fun decodeKeymaps(text: String, config: SyncConfig) {
for (keymap in ohMyJson.decodeFromString<List<JsonObject>>(text).mapNotNull { Keymap.fromJSON(it) }) {
keymapManager.addKeymap(keymap)
}
if (log.isDebugEnabled) {
log.debug("Decode Keymaps: {}", text)
}
}
private fun getKey(config: SyncConfig): ByteArray {
return ArrayUtils.subarray(config.token.padEnd(16, '0').toByteArray(), 0, 16)
}
@@ -244,6 +266,7 @@ abstract class GitSyncer : Syncer {
// aes key
val key = ArrayUtils.subarray(config.token.padEnd(16, '0').toByteArray(), 0, 16)
// Hosts
if (config.ranges.contains(SyncRange.Hosts)) {
val encryptedHosts = mutableListOf<EncryptedHost>()
for (host in hostManager.hosts()) {
@@ -282,6 +305,7 @@ abstract class GitSyncer : Syncer {
}
// KeyPairs
if (config.ranges.contains(SyncRange.KeyPairs)) {
val encryptedKeys = mutableListOf<OhKeyPair>()
for (keyPair in keyManager.getOhKeyPairs()) {
@@ -306,6 +330,7 @@ abstract class GitSyncer : Syncer {
gistFiles.add(GistFile("KeyPairs", keysContent))
}
// Highlights
if (config.ranges.contains(SyncRange.KeywordHighlights)) {
val keywordHighlights = mutableListOf<KeywordHighlight>()
for (keywordHighlight in keywordHighlightManager.getKeywordHighlights()) {
@@ -324,6 +349,7 @@ abstract class GitSyncer : Syncer {
gistFiles.add(GistFile("KeywordHighlights", keywordHighlightsContent))
}
// Macros
if (config.ranges.contains(SyncRange.Macros)) {
val macros = mutableListOf<Macro>()
for (macro in macroManager.getMacros()) {
@@ -342,6 +368,26 @@ abstract class GitSyncer : Syncer {
gistFiles.add(GistFile("Macros", macrosContent))
}
// Keymap
if (config.ranges.contains(SyncRange.Keymap)) {
val keymaps = mutableListOf<JsonObject>()
for (keymap in keymapManager.getKeymaps()) {
// 只读的是内置的
if (keymap.isReadonly) {
continue
}
keymaps.add(keymap.toJSONObject())
}
if (keymaps.isNotEmpty()) {
val keymapsContent = ohMyJson.encodeToString(keymaps)
if (log.isDebugEnabled) {
log.debug("Push keymaps: {}", keymapsContent)
}
gistFiles.add(GistFile("Keymaps", keymapsContent))
}
}
if (gistFiles.isEmpty()) {
throw IllegalArgumentException("No gist files found")
}

View File

@@ -11,6 +11,7 @@ enum class SyncRange {
KeyPairs,
KeywordHighlights,
Macros,
Keymap,
}
data class SyncConfig(