mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: support fast reconnect
This commit is contained in:
@@ -1,7 +1,10 @@
|
|||||||
package app.termora
|
package app.termora
|
||||||
|
|
||||||
|
import app.termora.actions.TabReconnectAction
|
||||||
import app.termora.addons.zmodem.ZModemPtyConnectorAdaptor
|
import app.termora.addons.zmodem.ZModemPtyConnectorAdaptor
|
||||||
import app.termora.keyboardinteractive.TerminalUserInteraction
|
import app.termora.keyboardinteractive.TerminalUserInteraction
|
||||||
|
import app.termora.keymap.KeyShortcut
|
||||||
|
import app.termora.keymap.KeymapManager
|
||||||
import app.termora.terminal.ControlCharacters
|
import app.termora.terminal.ControlCharacters
|
||||||
import app.termora.terminal.DataKey
|
import app.termora.terminal.DataKey
|
||||||
import app.termora.terminal.PtyConnector
|
import app.termora.terminal.PtyConnector
|
||||||
@@ -109,10 +112,18 @@ class SSHTerminalTab(windowScope: WindowScope, host: Host) : PtyHostTerminalTab(
|
|||||||
|
|
||||||
|
|
||||||
channel.addChannelListener(object : ChannelListener {
|
channel.addChannelListener(object : ChannelListener {
|
||||||
|
private val reconnectShortcut
|
||||||
|
get() = KeymapManager.getInstance().getActiveKeymap()
|
||||||
|
.getShortcut(TabReconnectAction.RECONNECT_TAB).firstOrNull()
|
||||||
|
|
||||||
override fun channelClosed(channel: Channel, reason: Throwable?) {
|
override fun channelClosed(channel: Channel, reason: Throwable?) {
|
||||||
coroutineScope.launch(Dispatchers.Swing) {
|
coroutineScope.launch(Dispatchers.Swing) {
|
||||||
terminal.write("\r\n${ControlCharacters.ESC}[31m")
|
terminal.write("\r\n\r\n${ControlCharacters.ESC}[31m")
|
||||||
terminal.write("Channel has been disconnected.\r\n")
|
terminal.write("Channel has been disconnected.")
|
||||||
|
if (reconnectShortcut is KeyShortcut) {
|
||||||
|
terminal.write(" Type $reconnectShortcut to reconnect.")
|
||||||
|
}
|
||||||
|
terminal.write("\r\n")
|
||||||
terminal.write("${ControlCharacters.ESC}[0m")
|
terminal.write("${ControlCharacters.ESC}[0m")
|
||||||
terminalModel.setData(DataKey.ShowCursor, false)
|
terminalModel.setData(DataKey.ShowCursor, false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ class TerminalTabDialog(
|
|||||||
dataProviderSupport.addData(DataProviders.WindowScope, it)
|
dataProviderSupport.addData(DataProviders.WindowScope, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dataProviderSupport.addData(DataProviders.TerminalTab, terminalTab)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createSouthPanel(): JComponent? {
|
override fun createSouthPanel(): JComponent? {
|
||||||
|
|||||||
@@ -181,16 +181,6 @@ class TerminalTabbed(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun openHost(host: Host) {
|
|
||||||
val tab = if (host.protocol == Protocol.SSH)
|
|
||||||
SSHTerminalTab(ApplicationScope.forWindowScope(this), host)
|
|
||||||
else LocalTerminalTab(ApplicationScope.forWindowScope(this), host)
|
|
||||||
addTab(tab)
|
|
||||||
tab.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun showContextMenu(tabIndex: Int, e: MouseEvent) {
|
private fun showContextMenu(tabIndex: Int, e: MouseEvent) {
|
||||||
val c = tabbedPane.getComponentAt(tabIndex) as JComponent
|
val c = tabbedPane.getComponentAt(tabIndex) as JComponent
|
||||||
val tab = tabs[tabIndex]
|
val tab = tabs[tabIndex]
|
||||||
@@ -438,6 +428,12 @@ class TerminalTabbed(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
|
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
|
||||||
|
if (dataKey == DataProviders.TerminalTab) {
|
||||||
|
dataProviderSupport.removeData(dataKey)
|
||||||
|
if (tabbedPane.selectedIndex >= 0 && tabs.size > tabbedPane.selectedIndex) {
|
||||||
|
dataProviderSupport.addData(dataKey, tabs[tabbedPane.selectedIndex])
|
||||||
|
}
|
||||||
|
}
|
||||||
return dataProviderSupport.getData(dataKey)
|
return dataProviderSupport.getData(dataKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -228,6 +228,18 @@ class WelcomePanel(private val windowScope: WindowScope) : JPanel(BorderLayout()
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun canReconnect(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canClose(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canClone(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
hostTree.setModel(null)
|
hostTree.setModel(null)
|
||||||
properties.putString("WelcomeFullContent", fullContent.toString())
|
properties.putString("WelcomeFullContent", fullContent.toString())
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class ActionManager : org.jdesktop.swingx.action.ActionManager() {
|
|||||||
addAction(Actions.KEY_MANAGER, KeyManagerAction())
|
addAction(Actions.KEY_MANAGER, KeyManagerAction())
|
||||||
|
|
||||||
addAction(SwitchTabAction.SWITCH_TAB, SwitchTabAction())
|
addAction(SwitchTabAction.SWITCH_TAB, SwitchTabAction())
|
||||||
|
addAction(TabReconnectAction.RECONNECT_TAB, TabReconnectAction())
|
||||||
addAction(SettingsAction.SETTING, SettingsAction())
|
addAction(SettingsAction.SETTING, SettingsAction())
|
||||||
|
|
||||||
addAction(NewHostAction.NEW_HOST, NewHostAction())
|
addAction(NewHostAction.NEW_HOST, NewHostAction())
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ object DataProviders {
|
|||||||
val TerminalPanel = DataKey(app.termora.terminal.panel.TerminalPanel::class)
|
val TerminalPanel = DataKey(app.termora.terminal.panel.TerminalPanel::class)
|
||||||
val Terminal = DataKey(app.termora.terminal.Terminal::class)
|
val Terminal = DataKey(app.termora.terminal.Terminal::class)
|
||||||
val PtyConnector = DataKey(app.termora.terminal.PtyConnector::class)
|
val PtyConnector = DataKey(app.termora.terminal.PtyConnector::class)
|
||||||
|
|
||||||
val TerminalTabbed = DataKey(app.termora.TerminalTabbed::class)
|
val TerminalTabbed = DataKey(app.termora.TerminalTabbed::class)
|
||||||
|
val TerminalTab = DataKey(app.termora.TerminalTab::class)
|
||||||
val TerminalTabbedManager = DataKey(app.termora.TerminalTabbedManager::class)
|
val TerminalTabbedManager = DataKey(app.termora.TerminalTabbedManager::class)
|
||||||
|
|
||||||
val TermoraFrame = DataKey(app.termora.TermoraFrame::class)
|
val TermoraFrame = DataKey(app.termora.TermoraFrame::class)
|
||||||
val WindowScope = DataKey(app.termora.WindowScope::class)
|
val WindowScope = DataKey(app.termora.WindowScope::class)
|
||||||
|
|
||||||
|
|||||||
21
src/main/kotlin/app/termora/actions/TabReconnectAction.kt
Normal file
21
src/main/kotlin/app/termora/actions/TabReconnectAction.kt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package app.termora.actions
|
||||||
|
|
||||||
|
import app.termora.I18n
|
||||||
|
|
||||||
|
class TabReconnectAction : AnAction() {
|
||||||
|
companion object {
|
||||||
|
const val RECONNECT_TAB = "TabReconnectAction"
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
putValue(ACTION_COMMAND_KEY, RECONNECT_TAB)
|
||||||
|
putValue(SHORT_DESCRIPTION, I18n.getString("termora.tabbed.contextmenu.reconnect"))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun actionPerformed(evt: AnActionEvent) {
|
||||||
|
val tab = evt.getData(DataProviders.TerminalTab) ?: return
|
||||||
|
if (tab.canReconnect()) {
|
||||||
|
tab.reconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,32 @@
|
|||||||
package app.termora.keymap
|
package app.termora.keymap
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
|
import java.awt.event.KeyEvent
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
class KeyShortcut(val keyStroke: KeyStroke) : Shortcut() {
|
class KeyShortcut(val keyStroke: KeyStroke) : Shortcut() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun toHumanText(keyStroke: KeyStroke): String {
|
||||||
|
|
||||||
|
var text = keyStroke.toString()
|
||||||
|
text = text.replace("shift", "⇧")
|
||||||
|
text = text.replace("ctrl", "⌃")
|
||||||
|
text = text.replace("meta", "⌘")
|
||||||
|
text = text.replace("alt", "⌥")
|
||||||
|
text = text.replace("pressed", StringUtils.EMPTY)
|
||||||
|
text = text.replace(StringUtils.SPACE, StringUtils.EMPTY)
|
||||||
|
|
||||||
|
if (keyStroke.keyCode == KeyEvent.VK_EQUALS) {
|
||||||
|
text = text.replace("EQUALS", "+")
|
||||||
|
} else if (keyStroke.keyCode == KeyEvent.VK_MINUS) {
|
||||||
|
text = text.replace("MINUS", "-")
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.toCharArray().joinToString(" + ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun isKeyboard(): Boolean {
|
override fun isKeyboard(): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -19,4 +43,8 @@ class KeyShortcut(val keyStroke: KeyStroke) : Shortcut() {
|
|||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return keyStroke.hashCode()
|
return keyStroke.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return toHumanText(keyStroke)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -70,6 +70,12 @@ class KeymapImpl(private val menuShortcutKeyMaskEx: Int) : Keymap("Keymap", null
|
|||||||
KeyShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_0, menuShortcutKeyMaskEx))
|
KeyShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_0, menuShortcutKeyMaskEx))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Command + Shift + R
|
||||||
|
addShortcut(
|
||||||
|
TabReconnectAction.RECONNECT_TAB,
|
||||||
|
KeyShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_R, menuShortcutKeyMaskEx or InputEvent.SHIFT_DOWN_MASK))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
// switch map
|
// switch map
|
||||||
for (i in KeyEvent.VK_1..KeyEvent.VK_9) {
|
for (i in KeyEvent.VK_1..KeyEvent.VK_9) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package app.termora.keymap
|
|||||||
import app.termora.*
|
import app.termora.*
|
||||||
import app.termora.actions.ActionManager
|
import app.termora.actions.ActionManager
|
||||||
import app.termora.actions.SwitchTabAction
|
import app.termora.actions.SwitchTabAction
|
||||||
|
import app.termora.keymap.KeyShortcut.Companion.toHumanText
|
||||||
import com.formdev.flatlaf.FlatClientProperties
|
import com.formdev.flatlaf.FlatClientProperties
|
||||||
import com.formdev.flatlaf.extras.components.FlatToolBar
|
import com.formdev.flatlaf.extras.components.FlatToolBar
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
@@ -225,7 +226,7 @@ class KeymapPanel : JPanel(BorderLayout()) {
|
|||||||
val text = duplicateAction.getValue(Action.SHORT_DESCRIPTION) ?: continue
|
val text = duplicateAction.getValue(Action.SHORT_DESCRIPTION) ?: continue
|
||||||
OptionPane.showMessageDialog(
|
OptionPane.showMessageDialog(
|
||||||
SwingUtilities.getWindowAncestor(this@KeymapPanel),
|
SwingUtilities.getWindowAncestor(this@KeymapPanel),
|
||||||
I18n.getString("termora.settings.keymap.already-exists", model.toHumanText(keyStroke), text),
|
I18n.getString("termora.settings.keymap.already-exists", toHumanText(keyStroke), text),
|
||||||
messageType = JOptionPane.ERROR_MESSAGE,
|
messageType = JOptionPane.ERROR_MESSAGE,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ package app.termora.keymap
|
|||||||
import app.termora.I18n
|
import app.termora.I18n
|
||||||
import app.termora.actions.*
|
import app.termora.actions.*
|
||||||
import app.termora.findeverywhere.FindEverywhereAction
|
import app.termora.findeverywhere.FindEverywhereAction
|
||||||
|
import app.termora.keymap.KeyShortcut.Companion.toHumanText
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
import org.jdesktop.swingx.action.ActionManager
|
import org.jdesktop.swingx.action.ActionManager
|
||||||
import org.jdesktop.swingx.action.BoundAction.ACTION_COMMAND_KEY
|
import org.jdesktop.swingx.action.BoundAction.ACTION_COMMAND_KEY
|
||||||
import java.awt.event.KeyEvent
|
|
||||||
import javax.swing.Action
|
import javax.swing.Action
|
||||||
import javax.swing.KeyStroke
|
|
||||||
import javax.swing.table.DefaultTableModel
|
import javax.swing.table.DefaultTableModel
|
||||||
|
|
||||||
class KeymapTableModel : DefaultTableModel() {
|
class KeymapTableModel : DefaultTableModel() {
|
||||||
@@ -30,6 +29,7 @@ class KeymapTableModel : DefaultTableModel() {
|
|||||||
OpenLocalTerminalAction.LOCAL_TERMINAL,
|
OpenLocalTerminalAction.LOCAL_TERMINAL,
|
||||||
FindEverywhereAction.FIND_EVERYWHERE,
|
FindEverywhereAction.FIND_EVERYWHERE,
|
||||||
NewWindowAction.NEW_WINDOW,
|
NewWindowAction.NEW_WINDOW,
|
||||||
|
TabReconnectAction.RECONNECT_TAB,
|
||||||
SwitchTabAction.SWITCH_TAB,
|
SwitchTabAction.SWITCH_TAB,
|
||||||
)) {
|
)) {
|
||||||
val action = actionManager.getAction(id) ?: continue
|
val action = actionManager.getAction(id) ?: continue
|
||||||
@@ -75,24 +75,5 @@ class KeymapTableModel : DefaultTableModel() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toHumanText(keyStroke: KeyStroke): String {
|
|
||||||
|
|
||||||
var text = keyStroke.toString()
|
|
||||||
text = text.replace("shift", "⇧")
|
|
||||||
text = text.replace("ctrl", "⌃")
|
|
||||||
text = text.replace("meta", "⌘")
|
|
||||||
text = text.replace("alt", "⌥")
|
|
||||||
text = text.replace("pressed", StringUtils.EMPTY)
|
|
||||||
text = text.replace(StringUtils.SPACE, StringUtils.EMPTY)
|
|
||||||
|
|
||||||
if (keyStroke.keyCode == KeyEvent.VK_EQUALS) {
|
|
||||||
text = text.replace("EQUALS", "+")
|
|
||||||
} else if (keyStroke.keyCode == KeyEvent.VK_MINUS) {
|
|
||||||
text = text.replace("MINUS", "-")
|
|
||||||
}
|
|
||||||
|
|
||||||
return text.toCharArray().joinToString(" + ")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user