feat: OSC 1337 (#244)

This commit is contained in:
hstyi
2025-02-15 17:38:06 +08:00
committed by GitHub
parent 0c768aa1ca
commit 5fdfe98f26
7 changed files with 96 additions and 17 deletions

View File

@@ -5,6 +5,17 @@ import org.apache.commons.lang3.StringUtils
import java.util.* 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 { fun UUID.toSimpleString(): String {
return toString().replace("-", StringUtils.EMPTY) return toString().replace("-", StringUtils.EMPTY)
} }

View File

@@ -1,5 +1,7 @@
package app.termora package app.termora
import app.termora.actions.DataProvider
import app.termora.actions.DataProviders
import app.termora.terminal.* import app.termora.terminal.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -12,7 +14,7 @@ abstract class HostTerminalTab(
val windowScope: WindowScope, val windowScope: WindowScope,
val host: Host, val host: Host,
protected val terminal: Terminal = TerminalFactory.getInstance(windowScope).createTerminal() protected val terminal: Terminal = TerminalFactory.getInstance(windowScope).createTerminal()
) : PropertyTerminalTab() { ) : PropertyTerminalTab(), DataProvider {
companion object { companion object {
val Host = DataKey(app.termora.Host::class) val Host = DataKey(app.termora.Host::class)
} }
@@ -69,4 +71,11 @@ abstract class HostTerminalTab(
unread = false unread = false
} }
@Suppress("UNCHECKED_CAST")
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
if (dataKey == DataProviders.Terminal) {
return terminal as T?
}
return null
}
} }

View File

@@ -1,5 +1,6 @@
package app.termora package app.termora
import app.termora.actions.DataProviders
import app.termora.terminal.* import app.termora.terminal.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.swing.Swing import kotlinx.coroutines.swing.Swing
@@ -135,4 +136,12 @@ abstract class PtyHostTerminalTab(
} }
abstract suspend fun openPtyConnector(): PtyConnector abstract suspend fun openPtyConnector(): PtyConnector
@Suppress("UNCHECKED_CAST")
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
if (dataKey == DataProviders.TerminalPanel) {
return terminalPanel as T?
}
return super.getData(dataKey)
}
} }

View File

@@ -95,7 +95,14 @@ class SFTPPtyTerminalTab(windowScope: WindowScope, host: Host) : PtyHostTerminal
// 设置认证信息 // 设置认证信息
setAuthentication(commands, host) 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 winSize = terminalPanel.winSize()
val ptyConnector = ptyConnectorFactory.createPtyConnector( val ptyConnector = ptyConnectorFactory.createPtyConnector(

View File

@@ -236,21 +236,12 @@ class TerminalTabbed(
}) })
if (tab is HostTerminalTab) { if (tab is HostTerminalTab) {
if (tab.host.protocol == Protocol.SSH || tab.host.protocol == Protocol.SFTPPty) { val openHostAction = actionManager.getAction(OpenHostAction.OPEN_HOST)
popupMenu.addSeparator() if (openHostAction != null) {
val sftpCommand = popupMenu.add(I18n.getString("termora.tabbed.contextmenu.sftp-command")) if (tab.host.protocol == Protocol.SSH || tab.host.protocol == Protocol.SFTPPty) {
sftpCommand.addActionListener { popupMenu.addSeparator()
if (SFTPPtyTerminalTab.canSupports) { val sftpCommand = popupMenu.add(I18n.getString("termora.tabbed.contextmenu.sftp-command"))
actionManager.getAction(OpenHostAction.OPEN_HOST) sftpCommand.addActionListener { openSFTPPtyTab(tab, openHostAction, it) }
?.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
)
}
} }
} }
} }
@@ -328,6 +319,36 @@ class TerminalTabbed(
Disposer.register(this, tab) 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 右键 * 对着 ToolBar 右键
*/ */

View File

@@ -74,6 +74,13 @@ class DataKey<T : Any>(val clazz: KClass<T>) {
*/ */
val Workdir = DataKey(String::class) val Workdir = DataKey(String::class)
/**
* OSC 1337 CurrentDir
*
* https://iterm2.com/documentation-escape-codes.html
*/
val CurrentDir = DataKey(String::class)
/** /**
* true: alternate keypad. * true: alternate keypad.
* false: Normal Keypad (DECKPNM) * false: Normal Keypad (DECKPNM)

View File

@@ -4,6 +4,8 @@ import org.apache.commons.codec.binary.Base64
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.awt.Toolkit import java.awt.Toolkit
import java.awt.datatransfer.StringSelection import java.awt.datatransfer.StringSelection
import java.io.StringReader
import java.util.*
class OperatingSystemCommandProcessor(terminal: Terminal, reader: TerminalReader) : class OperatingSystemCommandProcessor(terminal: Terminal, reader: TerminalReader) :
AbstractProcessor(terminal, reader) { 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 // 11: background color
// 10: foreground color // 10: foreground color
11, 10 -> { 11, 10 -> {