From 5fdfe98f2672995f41668181d2ba1db1dc8ba27a Mon Sep 17 00:00:00 2001 From: hstyi Date: Sat, 15 Feb 2025 17:38:06 +0800 Subject: [PATCH] feat: OSC 1337 (#244) --- src/main/kotlin/app/termora/Host.kt | 11 ++++ .../kotlin/app/termora/HostTerminalTab.kt | 11 +++- .../kotlin/app/termora/PtyHostTerminalTab.kt | 9 ++++ .../kotlin/app/termora/SFTPPtyTerminalTab.kt | 9 +++- src/main/kotlin/app/termora/TerminalTabbed.kt | 51 +++++++++++++------ .../kotlin/app/termora/terminal/DataKey.kt | 7 +++ .../OperatingSystemCommandProcessor.kt | 15 ++++++ 7 files changed, 96 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/app/termora/Host.kt b/src/main/kotlin/app/termora/Host.kt index 2dbc946..f215fd3 100644 --- a/src/main/kotlin/app/termora/Host.kt +++ b/src/main/kotlin/app/termora/Host.kt @@ -5,6 +5,17 @@ import org.apache.commons.lang3.StringUtils import java.util.* +fun Map<*, *>.toPropertiesString(): String { + val env = StringBuilder() + for ((i, e) in entries.withIndex()) { + env.append(e.key).append('=').append(e.value) + if (i != size - 1) { + env.appendLine() + } + } + return env.toString() +} + fun UUID.toSimpleString(): String { return toString().replace("-", StringUtils.EMPTY) } diff --git a/src/main/kotlin/app/termora/HostTerminalTab.kt b/src/main/kotlin/app/termora/HostTerminalTab.kt index a3573f5..25afa35 100644 --- a/src/main/kotlin/app/termora/HostTerminalTab.kt +++ b/src/main/kotlin/app/termora/HostTerminalTab.kt @@ -1,5 +1,7 @@ package app.termora +import app.termora.actions.DataProvider +import app.termora.actions.DataProviders import app.termora.terminal.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -12,7 +14,7 @@ abstract class HostTerminalTab( val windowScope: WindowScope, val host: Host, protected val terminal: Terminal = TerminalFactory.getInstance(windowScope).createTerminal() -) : PropertyTerminalTab() { +) : PropertyTerminalTab(), DataProvider { companion object { val Host = DataKey(app.termora.Host::class) } @@ -69,4 +71,11 @@ abstract class HostTerminalTab( unread = false } + @Suppress("UNCHECKED_CAST") + override fun getData(dataKey: DataKey): T? { + if (dataKey == DataProviders.Terminal) { + return terminal as T? + } + return null + } } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/PtyHostTerminalTab.kt b/src/main/kotlin/app/termora/PtyHostTerminalTab.kt index 7d138d7..9e9b6cc 100644 --- a/src/main/kotlin/app/termora/PtyHostTerminalTab.kt +++ b/src/main/kotlin/app/termora/PtyHostTerminalTab.kt @@ -1,5 +1,6 @@ package app.termora +import app.termora.actions.DataProviders import app.termora.terminal.* import kotlinx.coroutines.* import kotlinx.coroutines.swing.Swing @@ -135,4 +136,12 @@ abstract class PtyHostTerminalTab( } abstract suspend fun openPtyConnector(): PtyConnector + + @Suppress("UNCHECKED_CAST") + override fun getData(dataKey: DataKey): T? { + if (dataKey == DataProviders.TerminalPanel) { + return terminalPanel as T? + } + return super.getData(dataKey) + } } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/SFTPPtyTerminalTab.kt b/src/main/kotlin/app/termora/SFTPPtyTerminalTab.kt index 6f41abb..6acab17 100644 --- a/src/main/kotlin/app/termora/SFTPPtyTerminalTab.kt +++ b/src/main/kotlin/app/termora/SFTPPtyTerminalTab.kt @@ -95,7 +95,14 @@ class SFTPPtyTerminalTab(windowScope: WindowScope, host: Host) : PtyHostTerminal // 设置认证信息 setAuthentication(commands, host) - commands.add("${host.username}@${host.host}") + + val envs = host.options.envs() + if (envs.containsKey("CurrentDir")) { + val currentDir = envs.getValue("CurrentDir") + commands.add("${host.username}@${host.host}:${currentDir}") + } else { + commands.add("${host.username}@${host.host}") + } val winSize = terminalPanel.winSize() val ptyConnector = ptyConnectorFactory.createPtyConnector( diff --git a/src/main/kotlin/app/termora/TerminalTabbed.kt b/src/main/kotlin/app/termora/TerminalTabbed.kt index 4120c35..2889167 100644 --- a/src/main/kotlin/app/termora/TerminalTabbed.kt +++ b/src/main/kotlin/app/termora/TerminalTabbed.kt @@ -236,21 +236,12 @@ class TerminalTabbed( }) if (tab is HostTerminalTab) { - if (tab.host.protocol == Protocol.SSH || tab.host.protocol == Protocol.SFTPPty) { - popupMenu.addSeparator() - val sftpCommand = popupMenu.add(I18n.getString("termora.tabbed.contextmenu.sftp-command")) - sftpCommand.addActionListener { - if (SFTPPtyTerminalTab.canSupports) { - actionManager.getAction(OpenHostAction.OPEN_HOST) - ?.actionPerformed(OpenHostActionEvent(this, tab.host.copy(protocol = Protocol.SFTPPty), it)) - } else { - OptionPane.showMessageDialog( - SwingUtilities.getWindowAncestor(this), - I18n.getString("termora.tabbed.contextmenu.sftp-not-install"), - messageType = JOptionPane.ERROR_MESSAGE - ) - } - + val openHostAction = actionManager.getAction(OpenHostAction.OPEN_HOST) + if (openHostAction != null) { + if (tab.host.protocol == Protocol.SSH || tab.host.protocol == Protocol.SFTPPty) { + popupMenu.addSeparator() + val sftpCommand = popupMenu.add(I18n.getString("termora.tabbed.contextmenu.sftp-command")) + sftpCommand.addActionListener { openSFTPPtyTab(tab, openHostAction, it) } } } } @@ -328,6 +319,36 @@ class TerminalTabbed( Disposer.register(this, tab) } + private fun openSFTPPtyTab(tab: HostTerminalTab, openHostAction: Action, evt: EventObject) { + if (!SFTPPtyTerminalTab.canSupports) { + OptionPane.showMessageDialog( + SwingUtilities.getWindowAncestor(this), + I18n.getString("termora.tabbed.contextmenu.sftp-not-install"), + messageType = JOptionPane.ERROR_MESSAGE + ) + return + } + + var host = tab.host + + if (host.protocol == Protocol.SSH) { + val envs = tab.host.options.envs().toMutableMap() + val currentDir = tab.getData(DataProviders.Terminal)?.getTerminalModel() + ?.getData(DataKey.CurrentDir, StringUtils.EMPTY) ?: StringUtils.EMPTY + + if (currentDir.isNotBlank()) { + envs["CurrentDir"] = currentDir + } + + host = host.copy( + protocol = Protocol.SFTPPty, + options = host.options.copy(env = envs.toPropertiesString()) + ) + } + + openHostAction.actionPerformed(OpenHostActionEvent(this, host, evt)) + } + /** * 对着 ToolBar 右键 */ diff --git a/src/main/kotlin/app/termora/terminal/DataKey.kt b/src/main/kotlin/app/termora/terminal/DataKey.kt index 8e99c93..5c29010 100644 --- a/src/main/kotlin/app/termora/terminal/DataKey.kt +++ b/src/main/kotlin/app/termora/terminal/DataKey.kt @@ -74,6 +74,13 @@ class DataKey(val clazz: KClass) { */ val Workdir = DataKey(String::class) + /** + * OSC 1337 CurrentDir + * + * https://iterm2.com/documentation-escape-codes.html + */ + val CurrentDir = DataKey(String::class) + /** * true: alternate keypad. * false: Normal Keypad (DECKPNM) diff --git a/src/main/kotlin/app/termora/terminal/OperatingSystemCommandProcessor.kt b/src/main/kotlin/app/termora/terminal/OperatingSystemCommandProcessor.kt index ecde9ff..a5103d3 100644 --- a/src/main/kotlin/app/termora/terminal/OperatingSystemCommandProcessor.kt +++ b/src/main/kotlin/app/termora/terminal/OperatingSystemCommandProcessor.kt @@ -4,6 +4,8 @@ import org.apache.commons.codec.binary.Base64 import org.slf4j.LoggerFactory import java.awt.Toolkit import java.awt.datatransfer.StringSelection +import java.io.StringReader +import java.util.* class OperatingSystemCommandProcessor(terminal: Terminal, reader: TerminalReader) : AbstractProcessor(terminal, reader) { @@ -85,6 +87,19 @@ class OperatingSystemCommandProcessor(terminal: Terminal, reader: TerminalReader } } + // https://iterm2.com/documentation-escape-codes.html + 1337 -> { + val properties = Properties() + properties.load(StringReader(suffix)) + if (properties.containsKey("CurrentDir")) { + val currentDir = properties.getProperty("CurrentDir") + terminal.getTerminalModel().setData(DataKey.CurrentDir, currentDir) + if (log.isDebugEnabled) { + log.debug("CurrentDir: $currentDir") + } + } + } + // 11: background color // 10: foreground color 11, 10 -> {