From 0cd818e9a03f17223573cd7f5556c08571fea225 Mon Sep 17 00:00:00 2001 From: hstyi Date: Wed, 15 Jan 2025 23:00:39 +0800 Subject: [PATCH] feat: support fast reconnect --- src/main/kotlin/app/termora/SSHTerminalTab.kt | 15 ++++++++-- .../kotlin/app/termora/TerminalTabDialog.kt | 2 ++ src/main/kotlin/app/termora/TerminalTabbed.kt | 16 ++++------- src/main/kotlin/app/termora/WelcomePanel.kt | 12 ++++++++ .../app/termora/actions/ActionManager.kt | 1 + .../app/termora/actions/DataProviders.kt | 3 ++ .../app/termora/actions/TabReconnectAction.kt | 21 ++++++++++++++ .../kotlin/app/termora/keymap/KeyShortcut.kt | 28 +++++++++++++++++++ .../kotlin/app/termora/keymap/KeymapImpl.kt | 6 ++++ .../kotlin/app/termora/keymap/KeymapPanel.kt | 3 +- .../app/termora/keymap/KeymapTableModel.kt | 23 ++------------- 11 files changed, 96 insertions(+), 34 deletions(-) create mode 100644 src/main/kotlin/app/termora/actions/TabReconnectAction.kt diff --git a/src/main/kotlin/app/termora/SSHTerminalTab.kt b/src/main/kotlin/app/termora/SSHTerminalTab.kt index 68816eb..9620484 100644 --- a/src/main/kotlin/app/termora/SSHTerminalTab.kt +++ b/src/main/kotlin/app/termora/SSHTerminalTab.kt @@ -1,7 +1,10 @@ package app.termora +import app.termora.actions.TabReconnectAction import app.termora.addons.zmodem.ZModemPtyConnectorAdaptor import app.termora.keyboardinteractive.TerminalUserInteraction +import app.termora.keymap.KeyShortcut +import app.termora.keymap.KeymapManager import app.termora.terminal.ControlCharacters import app.termora.terminal.DataKey import app.termora.terminal.PtyConnector @@ -109,10 +112,18 @@ class SSHTerminalTab(windowScope: WindowScope, host: Host) : PtyHostTerminalTab( channel.addChannelListener(object : ChannelListener { + private val reconnectShortcut + get() = KeymapManager.getInstance().getActiveKeymap() + .getShortcut(TabReconnectAction.RECONNECT_TAB).firstOrNull() + override fun channelClosed(channel: Channel, reason: Throwable?) { coroutineScope.launch(Dispatchers.Swing) { - terminal.write("\r\n${ControlCharacters.ESC}[31m") - terminal.write("Channel has been disconnected.\r\n") + terminal.write("\r\n\r\n${ControlCharacters.ESC}[31m") + 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") terminalModel.setData(DataKey.ShowCursor, false) } diff --git a/src/main/kotlin/app/termora/TerminalTabDialog.kt b/src/main/kotlin/app/termora/TerminalTabDialog.kt index 31ddd3f..3aaf182 100644 --- a/src/main/kotlin/app/termora/TerminalTabDialog.kt +++ b/src/main/kotlin/app/termora/TerminalTabDialog.kt @@ -48,6 +48,8 @@ class TerminalTabDialog( dataProviderSupport.addData(DataProviders.WindowScope, it) } } + + dataProviderSupport.addData(DataProviders.TerminalTab, terminalTab) } override fun createSouthPanel(): JComponent? { diff --git a/src/main/kotlin/app/termora/TerminalTabbed.kt b/src/main/kotlin/app/termora/TerminalTabbed.kt index c4c9dfa..f2a8585 100644 --- a/src/main/kotlin/app/termora/TerminalTabbed.kt +++ b/src/main/kotlin/app/termora/TerminalTabbed.kt @@ -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) { val c = tabbedPane.getComponentAt(tabIndex) as JComponent val tab = tabs[tabIndex] @@ -438,6 +428,12 @@ class TerminalTabbed( } override fun getData(dataKey: DataKey): 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) } diff --git a/src/main/kotlin/app/termora/WelcomePanel.kt b/src/main/kotlin/app/termora/WelcomePanel.kt index 67ac711..2b8200c 100644 --- a/src/main/kotlin/app/termora/WelcomePanel.kt +++ b/src/main/kotlin/app/termora/WelcomePanel.kt @@ -228,6 +228,18 @@ class WelcomePanel(private val windowScope: WindowScope) : JPanel(BorderLayout() return this } + override fun canReconnect(): Boolean { + return false + } + + override fun canClose(): Boolean { + return false + } + + override fun canClone(): Boolean { + return false + } + override fun dispose() { hostTree.setModel(null) properties.putString("WelcomeFullContent", fullContent.toString()) diff --git a/src/main/kotlin/app/termora/actions/ActionManager.kt b/src/main/kotlin/app/termora/actions/ActionManager.kt index fdc73f0..5a2a9b0 100644 --- a/src/main/kotlin/app/termora/actions/ActionManager.kt +++ b/src/main/kotlin/app/termora/actions/ActionManager.kt @@ -37,6 +37,7 @@ class ActionManager : org.jdesktop.swingx.action.ActionManager() { addAction(Actions.KEY_MANAGER, KeyManagerAction()) addAction(SwitchTabAction.SWITCH_TAB, SwitchTabAction()) + addAction(TabReconnectAction.RECONNECT_TAB, TabReconnectAction()) addAction(SettingsAction.SETTING, SettingsAction()) addAction(NewHostAction.NEW_HOST, NewHostAction()) diff --git a/src/main/kotlin/app/termora/actions/DataProviders.kt b/src/main/kotlin/app/termora/actions/DataProviders.kt index 311dd2d..e384f8a 100644 --- a/src/main/kotlin/app/termora/actions/DataProviders.kt +++ b/src/main/kotlin/app/termora/actions/DataProviders.kt @@ -6,8 +6,11 @@ object DataProviders { val TerminalPanel = DataKey(app.termora.terminal.panel.TerminalPanel::class) val Terminal = DataKey(app.termora.terminal.Terminal::class) val PtyConnector = DataKey(app.termora.terminal.PtyConnector::class) + val TerminalTabbed = DataKey(app.termora.TerminalTabbed::class) + val TerminalTab = DataKey(app.termora.TerminalTab::class) val TerminalTabbedManager = DataKey(app.termora.TerminalTabbedManager::class) + val TermoraFrame = DataKey(app.termora.TermoraFrame::class) val WindowScope = DataKey(app.termora.WindowScope::class) diff --git a/src/main/kotlin/app/termora/actions/TabReconnectAction.kt b/src/main/kotlin/app/termora/actions/TabReconnectAction.kt new file mode 100644 index 0000000..1c9d978 --- /dev/null +++ b/src/main/kotlin/app/termora/actions/TabReconnectAction.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/keymap/KeyShortcut.kt b/src/main/kotlin/app/termora/keymap/KeyShortcut.kt index 3da8fb7..cf017d5 100644 --- a/src/main/kotlin/app/termora/keymap/KeyShortcut.kt +++ b/src/main/kotlin/app/termora/keymap/KeyShortcut.kt @@ -1,8 +1,32 @@ package app.termora.keymap +import org.apache.commons.lang3.StringUtils +import java.awt.event.KeyEvent import javax.swing.KeyStroke 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 { return true } @@ -19,4 +43,8 @@ class KeyShortcut(val keyStroke: KeyStroke) : Shortcut() { override fun hashCode(): Int { return keyStroke.hashCode() } + + override fun toString(): String { + return toHumanText(keyStroke) + } } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/keymap/KeymapImpl.kt b/src/main/kotlin/app/termora/keymap/KeymapImpl.kt index 09d6e45..5e90a8f 100644 --- a/src/main/kotlin/app/termora/keymap/KeymapImpl.kt +++ b/src/main/kotlin/app/termora/keymap/KeymapImpl.kt @@ -70,6 +70,12 @@ class KeymapImpl(private val menuShortcutKeyMaskEx: Int) : Keymap("Keymap", null 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 for (i in KeyEvent.VK_1..KeyEvent.VK_9) { diff --git a/src/main/kotlin/app/termora/keymap/KeymapPanel.kt b/src/main/kotlin/app/termora/keymap/KeymapPanel.kt index 6c4c273..c8879ac 100644 --- a/src/main/kotlin/app/termora/keymap/KeymapPanel.kt +++ b/src/main/kotlin/app/termora/keymap/KeymapPanel.kt @@ -3,6 +3,7 @@ package app.termora.keymap import app.termora.* import app.termora.actions.ActionManager import app.termora.actions.SwitchTabAction +import app.termora.keymap.KeyShortcut.Companion.toHumanText import com.formdev.flatlaf.FlatClientProperties import com.formdev.flatlaf.extras.components.FlatToolBar import java.awt.BorderLayout @@ -225,7 +226,7 @@ class KeymapPanel : JPanel(BorderLayout()) { val text = duplicateAction.getValue(Action.SHORT_DESCRIPTION) ?: continue OptionPane.showMessageDialog( 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, ) } diff --git a/src/main/kotlin/app/termora/keymap/KeymapTableModel.kt b/src/main/kotlin/app/termora/keymap/KeymapTableModel.kt index f464d0b..b259d9f 100644 --- a/src/main/kotlin/app/termora/keymap/KeymapTableModel.kt +++ b/src/main/kotlin/app/termora/keymap/KeymapTableModel.kt @@ -3,12 +3,11 @@ package app.termora.keymap import app.termora.I18n import app.termora.actions.* import app.termora.findeverywhere.FindEverywhereAction +import app.termora.keymap.KeyShortcut.Companion.toHumanText import org.apache.commons.lang3.StringUtils import org.jdesktop.swingx.action.ActionManager import org.jdesktop.swingx.action.BoundAction.ACTION_COMMAND_KEY -import java.awt.event.KeyEvent import javax.swing.Action -import javax.swing.KeyStroke import javax.swing.table.DefaultTableModel class KeymapTableModel : DefaultTableModel() { @@ -30,6 +29,7 @@ class KeymapTableModel : DefaultTableModel() { OpenLocalTerminalAction.LOCAL_TERMINAL, FindEverywhereAction.FIND_EVERYWHERE, NewWindowAction.NEW_WINDOW, + TabReconnectAction.RECONNECT_TAB, SwitchTabAction.SWITCH_TAB, )) { val action = actionManager.getAction(id) ?: continue @@ -75,24 +75,5 @@ class KeymapTableModel : DefaultTableModel() { 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(" + ") - } - } \ No newline at end of file