From f7c49cde0c2d09b0149fe71fd0270b49d128e700 Mon Sep 17 00:00:00 2001 From: hstyi Date: Wed, 12 Feb 2025 16:33:37 +0800 Subject: [PATCH] feat: supports custom editing of SFTP command (#210) --- src/main/kotlin/app/termora/Database.kt | 15 +++++ .../kotlin/app/termora/SettingsOptionsPane.kt | 58 +++++++++++++++++++ .../app/termora/transport/FileSystemPanel.kt | 48 ++++++++++++--- src/main/resources/i18n/messages.properties | 3 + .../resources/i18n/messages_zh_CN.properties | 4 ++ .../resources/i18n/messages_zh_TW.properties | 2 + 6 files changed, 123 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/app/termora/Database.kt b/src/main/kotlin/app/termora/Database.kt index 6a86842..f1a1229 100644 --- a/src/main/kotlin/app/termora/Database.kt +++ b/src/main/kotlin/app/termora/Database.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import org.apache.commons.io.IOUtils +import org.apache.commons.lang3.StringUtils import org.slf4j.LoggerFactory import java.io.File import java.util.* @@ -55,6 +56,7 @@ class Database private constructor(private val env: Environment) : Disposable { val safetyProperties by lazy { SafetyProperties("Setting.SafetyProperties") } val terminal by lazy { Terminal() } val appearance by lazy { Appearance() } + val sftp by lazy { SFTP() } val sync by lazy { Sync() } private val doorman get() = Doorman.getInstance() @@ -573,6 +575,19 @@ class Database private constructor(private val env: Environment) : Disposable { } + /** + * SFTP + */ + inner class SFTP : Property("Setting.SFTP") { + + + /** + * 编辑命令 + */ + var editCommand by StringPropertyDelegate(StringUtils.EMPTY) + + } + /** * 同步配置 */ diff --git a/src/main/kotlin/app/termora/SettingsOptionsPane.kt b/src/main/kotlin/app/termora/SettingsOptionsPane.kt index 66cc3fb..314a969 100644 --- a/src/main/kotlin/app/termora/SettingsOptionsPane.kt +++ b/src/main/kotlin/app/termora/SettingsOptionsPane.kt @@ -109,6 +109,7 @@ class SettingsOptionsPane : OptionsPane() { addOption(AppearanceOption()) addOption(TerminalOption()) addOption(KeyShortcutsOption()) + addOption(SFTPOption()) addOption(CloudSyncOption()) addOption(DoormanOption()) addOption(AboutOption()) @@ -1172,6 +1173,63 @@ class SettingsOptionsPane : OptionsPane() { } } + private inner class SFTPOption : JPanel(BorderLayout()), Option { + + val editCommandField = OutlineTextField(255) + private val sftp get() = database.sftp + + init { + initView() + initEvents() + add(getCenterComponent(), BorderLayout.CENTER) + } + + private fun initEvents() { + editCommandField.document.addDocumentListener(object : DocumentAdaptor() { + override fun changedUpdate(e: DocumentEvent) { + sftp.editCommand = editCommandField.text + } + }) + } + + + private fun initView() { + if (SystemInfo.isWindows || SystemInfo.isLinux) { + editCommandField.placeholderText = "notepad {0}" + } else if (SystemInfo.isMacOS) { + editCommandField.placeholderText = "open -a TextEdit {0}" + } + + editCommandField.text = sftp.editCommand + } + + override fun getIcon(isSelected: Boolean): Icon { + return Icons.folder + } + + override fun getTitle(): String { + return "SFTP" + } + + override fun getJComponent(): JComponent { + return this + } + + private fun getCenterComponent(): JComponent { + val layout = FormLayout( + "left:pref, $formMargin, default:grow, 30dlu", + "pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref" + ) + + val builder = FormBuilder.create().layout(layout).debug(false) + builder.add("${I18n.getString("termora.settings.sftp.edit-command")}:").xy(1, 1) + builder.add(editCommandField).xy(3, 1) + + return builder.build() + + } + } + private inner class AboutOption : JPanel(BorderLayout()), Option { init { diff --git a/src/main/kotlin/app/termora/transport/FileSystemPanel.kt b/src/main/kotlin/app/termora/transport/FileSystemPanel.kt index 0f464fc..d805996 100644 --- a/src/main/kotlin/app/termora/transport/FileSystemPanel.kt +++ b/src/main/kotlin/app/termora/transport/FileSystemPanel.kt @@ -33,7 +33,9 @@ import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import java.io.File import java.nio.file.* +import java.text.MessageFormat import java.util.* +import java.util.regex.Pattern import javax.swing.* import javax.swing.table.DefaultTableCellRenderer import kotlin.io.path.absolutePathString @@ -489,8 +491,8 @@ class FileSystemPanel( // 编辑 val edit = popupMenu.add(I18n.getString("termora.transport.table.contextmenu.edit")) - // 不是 Linux & 不是本地文件系统 & 包含文件 - edit.isEnabled = !SystemInfo.isLinux && !tableModel.isLocalFileSystem && paths.any { !it.isDirectory } + // 不是本地文件系统 & 包含文件 + edit.isEnabled = !tableModel.isLocalFileSystem && paths.any { !it.isDirectory } edit.addActionListener { val files = paths.filter { !it.isDirectory } if (files.isNotEmpty()) { @@ -615,20 +617,38 @@ class FileSystemPanel( private fun editFileTransportListener(source: Path, localPath: Path): TransportListener { return object : TransportListener { + private val sftp get() = Database.getDatabase().sftp override fun onTransportChanged(transport: Transport) { // 传输成功 if (transport.state == TransportState.Done) { val transportManager = evt.getData(TransportDataProviders.TransportManager) ?: return var lastModifiedTime = localPath.getLastModifiedTime().toMillis() - if (SystemInfo.isMacOS) { - ProcessBuilder("open", "-a", "TextEdit", localPath.absolutePathString()).start() - } else if (SystemInfo.isWindows) { - ProcessBuilder("notepad", localPath.absolutePathString()).start() - } else { + try { + if (sftp.editCommand.isNotBlank()) { + ProcessBuilder( + parseCommand( + MessageFormat.format( + sftp.editCommand, + localPath.absolutePathString() + ) + ) + ).start() + } else if (SystemInfo.isMacOS) { + ProcessBuilder("open", "-a", "TextEdit", localPath.absolutePathString()).start() + } else if (SystemInfo.isWindows) { + ProcessBuilder("notepad", localPath.absolutePathString()).start() + } else { + return + } + } catch (e: Exception) { + if (log.isErrorEnabled) { + log.error(e.message, e) + } return } + coroutineScope.launch(Dispatchers.IO) { while (coroutineScope.isActive) { try { @@ -657,6 +677,20 @@ class FileSystemPanel( } } } + + fun parseCommand(command: String): List { + val result = mutableListOf() + val matcher = Pattern.compile("\"([^\"]*)\"|(\\S+)").matcher(command) + + while (matcher.find()) { + if (matcher.group(1) != null) { + result.add(matcher.group(1)) // 处理双引号部分 + } else { + result.add(matcher.group(2).replace("\\\\ ", " ")) + } + } + return result + } } } diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index 618cfb0..280678a 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -106,6 +106,9 @@ termora.settings.keymap.action=Action termora.settings.keymap.already-exists=The shortcut [{0}] is already in use by [{1}] +termora.settings.sftp.edit-command=Edit Command + + termora.settings.restart.title=Restart termora.settings.restart.message=Changes will take effect after restarting the application diff --git a/src/main/resources/i18n/messages_zh_CN.properties b/src/main/resources/i18n/messages_zh_CN.properties index 8b84678..6ca8239 100644 --- a/src/main/resources/i18n/messages_zh_CN.properties +++ b/src/main/resources/i18n/messages_zh_CN.properties @@ -110,6 +110,10 @@ termora.settings.keymap.shortcut=快捷键 termora.settings.keymap.action=操作 termora.settings.keymap.already-exists=快捷键 [{0}] 已经被 [{1}] 占用 + +termora.settings.sftp.edit-command=编辑命令 + + # Welcome termora.welcome.my-hosts=我的主机 termora.welcome.contextmenu.open=打开 diff --git a/src/main/resources/i18n/messages_zh_TW.properties b/src/main/resources/i18n/messages_zh_TW.properties index cf92110..4af91ca 100644 --- a/src/main/resources/i18n/messages_zh_TW.properties +++ b/src/main/resources/i18n/messages_zh_TW.properties @@ -62,6 +62,8 @@ termora.settings.keymap.shortcut=快捷鍵 termora.settings.keymap.action=操作 termora.settings.keymap.already-exists=快捷鍵 [{0}] 已經被 [{1}] 占用 +termora.settings.sftp.edit-command=編輯命令 + # Find everywhere termora.find-everywhere=尋找