mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-15 18:02:58 +08:00
feat: support keymap sync
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shortcuts.add(keymap)
|
||||
val keymaps = mutableListOf<Keymap>()
|
||||
for (text in array.iterator()) {
|
||||
keymaps.add(Keymap.fromJSON(text) ?: continue)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ enum class SyncRange {
|
||||
KeyPairs,
|
||||
KeywordHighlights,
|
||||
Macros,
|
||||
Keymap,
|
||||
}
|
||||
|
||||
data class SyncConfig(
|
||||
|
||||
Reference in New Issue
Block a user