mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: OSC 1337 (#244)
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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 右键
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 -> {
|
||||||
|
|||||||
Reference in New Issue
Block a user