From a4ae11e301d061da69c39744f3fd0cb52f1a6330 Mon Sep 17 00:00:00 2001 From: hstyi Date: Tue, 8 Jul 2025 18:04:29 +0800 Subject: [PATCH] feat: quick connect --- src/main/kotlin/app/termora/Host.kt | 5 + src/main/kotlin/app/termora/HostManager.kt | 8 +- .../termora/RequestAuthenticationDialog.kt | 4 +- src/main/kotlin/app/termora/TerminalTabbed.kt | 77 +------- .../app/termora/actions/ActionManager.kt | 1 + .../app/termora/actions/OpenHostAction.kt | 2 +- .../app/termora/actions/QuickConnectAction.kt | 174 ++++++++++++++++++ .../QuickCommandFindEverywhereProvider.kt | 17 +- .../termora/plugin/internal/ssh/SshClients.kt | 3 +- .../termora/transfer/TransferActionEvent.kt | 3 +- .../app/termora/transfer/TransferAnAction.kt | 36 ++-- .../kotlin/app/termora/tree/NewHostTree.kt | 8 +- src/main/resources/i18n/messages.properties | 1 + .../resources/i18n/messages_zh_CN.properties | 1 + .../resources/i18n/messages_zh_TW.properties | 1 + 15 files changed, 226 insertions(+), 115 deletions(-) create mode 100644 src/main/kotlin/app/termora/actions/QuickConnectAction.kt diff --git a/src/main/kotlin/app/termora/Host.kt b/src/main/kotlin/app/termora/Host.kt index e2eea4e..a14bb64 100644 --- a/src/main/kotlin/app/termora/Host.kt +++ b/src/main/kotlin/app/termora/Host.kt @@ -344,6 +344,11 @@ data class Host( val isFolder get() = StringUtils.equalsIgnoreCase(protocol, "Folder") + /** + * 临时的 SSH 不可以保存 + */ + val isTemporary get() = options.extras["Temporary"] != null + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/src/main/kotlin/app/termora/HostManager.kt b/src/main/kotlin/app/termora/HostManager.kt index 99df424..f6a37b0 100644 --- a/src/main/kotlin/app/termora/HostManager.kt +++ b/src/main/kotlin/app/termora/HostManager.kt @@ -21,9 +21,13 @@ class HostManager private constructor() : Disposable { */ fun addHost(host: Host, source: DatabaseChangedExtension.Source = DatabaseChangedExtension.Source.User) { assertEventDispatchThread() - if (host.ownerType.isBlank()) { + + if (host.isTemporary) + throw IllegalArgumentException("Temporary host") + + if (host.ownerType.isBlank()) throw IllegalArgumentException("Owner type cannot be null") - } + databaseManager.saveAndIncrementVersion( Data( id = host.id, diff --git a/src/main/kotlin/app/termora/RequestAuthenticationDialog.kt b/src/main/kotlin/app/termora/RequestAuthenticationDialog.kt index 50dadcd..57e4dec 100644 --- a/src/main/kotlin/app/termora/RequestAuthenticationDialog.kt +++ b/src/main/kotlin/app/termora/RequestAuthenticationDialog.kt @@ -37,6 +37,8 @@ class RequestAuthenticationDialog(owner: Window, host: Host) : DialogWrapper(own preferredSize = size minimumSize = size + rememberCheckBox.isVisible = host.isTemporary.not() + publicKeyComboBox.renderer = object : DefaultListCellRenderer() { override fun getListCellRendererComponent( list: JList<*>?, @@ -84,7 +86,7 @@ class RequestAuthenticationDialog(owner: Window, host: Host) : DialogWrapper(own switchPasswordComponent() - return FormBuilder.create().padding("$formMargin, $formMargin, $formMargin, $formMargin") + return FormBuilder.create().padding("1dlu, $formMargin, $formMargin, $formMargin") .layout(layout) .add("${I18n.getString("termora.new-host.general.authentication")}:").xy(1, 1) .add(authenticationTypeComboBox).xy(3, 1) diff --git a/src/main/kotlin/app/termora/TerminalTabbed.kt b/src/main/kotlin/app/termora/TerminalTabbed.kt index f04503c..2f6584e 100644 --- a/src/main/kotlin/app/termora/TerminalTabbed.kt +++ b/src/main/kotlin/app/termora/TerminalTabbed.kt @@ -17,7 +17,6 @@ import com.formdev.flatlaf.extras.components.FlatPopupMenu import com.formdev.flatlaf.extras.components.FlatTabbedPane import org.apache.commons.lang3.StringUtils import java.awt.* -import java.awt.event.AWTEventListener import java.awt.event.ActionEvent import java.awt.event.MouseAdapter import java.awt.event.MouseEvent @@ -32,11 +31,6 @@ class TerminalTabbed( private val layout: TermoraLayout, ) : JPanel(BorderLayout()), Disposable, TerminalTabbedManager, DataProvider { private val tabs = mutableListOf() - private val customizeToolBarAWTEventListener = object : AWTEventListener, Disposable { - override fun eventDispatched(event: AWTEvent?) { - - } - } private val actionManager = ActionManager.getInstance() private val dataProviderSupport = DataProviderSupport() private val appearance get() = DatabaseManager.getInstance().appearance @@ -72,7 +66,6 @@ class TerminalTabbed( private fun initEvents() { - Disposer.register(this, customizeToolBarAWTEventListener) // 关闭 tab tabbedPane.setTabCloseCallback { _, i -> removeTabAt(i, true) } @@ -146,9 +139,6 @@ class TerminalTabbed( } }).let { Disposer.register(this, it) } - // 监听全局事件 - toolkit.addAWTEventListener(customizeToolBarAWTEventListener, AWTEvent.MOUSE_EVENT_MASK) - } private fun removeTabAt(index: Int, disposable: Boolean = true) { @@ -301,9 +291,7 @@ class TerminalTabbed( // 关闭 val close = popupMenu.add(I18n.getString("termora.tabbed.contextmenu.close")) - close.addActionListener { - tabbedPane.tabCloseCallback?.accept(tabbedPane, tabIndex) - } + close.addActionListener { tabbedPane.tabCloseCallback?.accept(tabbedPane, tabIndex) } // 关闭其他标签页 popupMenu.add(I18n.getString("termora.tabbed.contextmenu.close-other-tabs")).addActionListener { @@ -326,7 +314,7 @@ class TerminalTabbed( close.isEnabled = tab.canClose() rename.isEnabled = close.isEnabled clone.isEnabled = close.isEnabled - edit.isEnabled = tab is HostTerminalTab && tab.host.id != "local" + edit.isEnabled = tab is HostTerminalTab && tab.host.id != "local" && tab.host.isTemporary.not() openInNewWindow.isEnabled = close.isEnabled // 如果不允许克隆 @@ -337,12 +325,7 @@ class TerminalTabbed( if (close.isEnabled) { popupMenu.addSeparator() val reconnect = popupMenu.add(I18n.getString("termora.tabbed.contextmenu.reconnect")) - reconnect.addActionListener { - if (tabIndex > 0) { - tabs[tabIndex].reconnect() - } - } - + reconnect.addActionListener { tabs[tabIndex].reconnect() } reconnect.isEnabled = tabs[tabIndex].canReconnect() } @@ -384,60 +367,6 @@ class TerminalTabbed( } } - - /** - * 对着 ToolBar 右键 - */ - /*private inner class CustomizeToolBarAWTEventListener : AWTEventListener, Disposable { - override fun eventDispatched(event: AWTEvent) { - if (event !is MouseEvent || event.id != MouseEvent.MOUSE_CLICKED || !SwingUtilities.isRightMouseButton(event)) return - // 如果 ToolBar 没有显示 - if (!toolbar.isShowing) return - // 如果不是作用于在 ToolBar 上面 - if (!Rectangle(toolbar.locationOnScreen, toolbar.size).contains(event.locationOnScreen)) return - - // 显示右键菜单 - showContextMenu(event) - } - - private fun showContextMenu(event: MouseEvent) { - val popupMenu = FlatPopupMenu() - popupMenu.add(I18n.getString("termora.toolbar.customize-toolbar")).addActionListener { - val owner = SwingUtilities.getWindowAncestor(this@TerminalTabbed) - val dialog = CustomizeToolBarDialog(owner, windowScope, termoraToolBar) - dialog.setLocationRelativeTo(owner) - if (dialog.open()) { - TermoraToolBar.rebuild() - } - } - popupMenu.show(event.component, event.x, event.y) - } - - override fun dispose() { - toolkit.removeAWTEventListener(this) - } - }*/ - - /*private inner class CustomizeToolBarDialog(owner: Window) : DialogWrapper(owner) { - init { - size = Dimension(UIManager.getInt("Dialog.width"), UIManager.getInt("Dialog.height")) - isModal = true - title = I18n.getString("termora.setting") - setLocationRelativeTo(null) - - init() - } - - override fun createCenterPanel(): JComponent { - val model = DefaultListModel() - val checkBoxList = CheckBoxList(model) - checkBoxList.fixedCellHeight = UIManager.getInt("Tree.rowHeight") - model.addElement("Test") - return checkBoxList - } - - }*/ - private inner class SwitchFindEverywhereResult( private val title: String, private val icon: Icon?, diff --git a/src/main/kotlin/app/termora/actions/ActionManager.kt b/src/main/kotlin/app/termora/actions/ActionManager.kt index e14b0a6..09d6ced 100644 --- a/src/main/kotlin/app/termora/actions/ActionManager.kt +++ b/src/main/kotlin/app/termora/actions/ActionManager.kt @@ -28,6 +28,7 @@ class ActionManager : org.jdesktop.swingx.action.ActionManager() { private fun registerActions() { addAction(NewWindowAction.NEW_WINDOW, NewWindowAction()) addAction(FindEverywhereAction.FIND_EVERYWHERE, FindEverywhereAction()) + addAction(QuickConnectAction.QUICK_CONNECT, QuickConnectAction.instance) addAction(Actions.APP_UPDATE, AppUpdateAction.getInstance()) addAction(Actions.KEYWORD_HIGHLIGHT, KeywordHighlightAction()) diff --git a/src/main/kotlin/app/termora/actions/OpenHostAction.kt b/src/main/kotlin/app/termora/actions/OpenHostAction.kt index 6149b98..8c18ee0 100644 --- a/src/main/kotlin/app/termora/actions/OpenHostAction.kt +++ b/src/main/kotlin/app/termora/actions/OpenHostAction.kt @@ -38,7 +38,7 @@ class OpenHostAction : AnAction() { if (providers.first { StringUtils.equalsIgnoreCase(it.getProtocol(), host.protocol) } .isTransfer()) { ActionManager.getInstance().getAction(Actions.SFTP) - .actionPerformed(TransferActionEvent(evt.source, evt.host.id, evt.event)) + .actionPerformed(TransferActionEvent(evt.source, host, evt.event)) return } diff --git a/src/main/kotlin/app/termora/actions/QuickConnectAction.kt b/src/main/kotlin/app/termora/actions/QuickConnectAction.kt new file mode 100644 index 0000000..2ae90cf --- /dev/null +++ b/src/main/kotlin/app/termora/actions/QuickConnectAction.kt @@ -0,0 +1,174 @@ +package app.termora.actions + +import app.termora.* +import app.termora.Application.ohMyJson +import app.termora.OptionsPane.Companion.FORM_MARGIN +import app.termora.database.DatabaseManager +import app.termora.protocol.ProtocolProvider +import com.jgoodies.forms.builder.FormBuilder +import com.jgoodies.forms.layout.FormLayout +import kotlinx.serialization.Serializable +import org.apache.commons.lang3.exception.ExceptionUtils +import java.awt.Dimension +import java.awt.Window +import java.net.URI +import java.util.* +import javax.swing.* + +class QuickConnectAction private constructor() : AnAction(I18n.getString("termora.actions.quick-connect"), Icons.find) { + companion object { + const val QUICK_CONNECT = "QuickConnectAction" + val instance = QuickConnectAction() + } + + + init { + putValue(SHORT_DESCRIPTION, I18n.getString("termora.actions.quick-connect")) + } + + override fun actionPerformed(evt: AnActionEvent) { + val scope = evt.getData(DataProviders.WindowScope) ?: return + val dialog = QuickConnectDialog(scope.window) + dialog.isVisible = true + } + + private class QuickConnectDialog(owner: Window) : DialogWrapper(owner) { + private val properties get() = DatabaseManager.getInstance().properties + private val hostComboBox = OutlineComboBox() + private val usernameTextField = OutlineTextField(256) + private val passwordTextField = OutlinePasswordField(256) + + init { + isModal = true + title = I18n.getString("termora.actions.quick-connect") + isResizable = false + init() + pack() + size = Dimension(UIManager.getInt("Dialog.width") - 250, preferredSize.height) + setLocationRelativeTo(owner) + } + + override fun createCenterPanel(): JComponent { + hostComboBox.isEditable = true + hostComboBox.placeholderText = "ssh://127.0.0.1:22" + + val histories = getHistories() + for (history in histories) { + if (histories.first() == history) { + usernameTextField.text = history.host.username + passwordTextField.text = history.host.authentication.password + } + hostComboBox.addItem(history.url) + } + + usernameTextField.placeholderText = I18n.getString("termora.new-host.general.username") + passwordTextField.placeholderText = I18n.getString("termora.new-host.general.password") + + val layout = FormLayout( + "left:pref, $FORM_MARGIN, default:grow", + "pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref" + ) + + return FormBuilder.create().layout(layout) + .border(BorderFactory.createEmptyBorder(0, 8, 8, 8)) + .add("${I18n.getString("termora.new-host.general.protocol")}:").xy(1, 1) + .add(hostComboBox).xy(3, 1) + + .add("${I18n.getString("termora.new-host.general.username")}:").xy(1, 3) + .add(usernameTextField).xy(3, 3) + + .add("${I18n.getString("termora.new-host.general.password")}:").xy(1, 5) + .add(passwordTextField).xy(3, 5) + .build() + + } + + override fun doOKAction() { + val host = hostComboBox.selectedItem as? String + if (host.isNullOrBlank()) { + hostComboBox.requestFocusInWindow() + return + } + + val historyHost: HistoryHost + try { + historyHost = getHistoryHost(host.trim()) + } catch (e: Exception) { + hostComboBox.requestFocusInWindow() + OptionPane.showMessageDialog( + this, + e.message ?: ExceptionUtils.getRootCauseMessage(e), + messageType = JOptionPane.ERROR_MESSAGE + ) + return + } + + val action = ActionManager.getInstance().getAction(OpenHostAction.OPEN_HOST) + if (action is OpenHostAction) { + SwingUtilities.invokeLater { + action.actionPerformed(OpenHostActionEvent(this, historyHost.host, EventObject(this))) + } + } + super.doOKAction() + + + } + + private fun getHistoryHost(host: String): HistoryHost { + + + val uri = URI.create(host) + val protocolProvider = ProtocolProvider.valueOf(uri.scheme) + if (protocolProvider == null) { + throw UnsupportedOperationException(I18n.getString("termora.protocol.not-supported", uri.scheme)) + } + + val historyHost = HistoryHost( + host, Host( + name = uri.host, + protocol = uri.scheme, + host = uri.host, + port = uri.port, + username = usernameTextField.text.trim(), + authentication = Authentication.No.copy( + type = AuthenticationType.Password, + password = String(passwordTextField.password) + ), + options = Options.Default.copy( + extras = mutableMapOf("Temporary" to "true") + ) + ) + ) + val histories = getHistories().toMutableList() + histories.removeIf { it.url == host } + histories.addFirst(historyHost) + + if (histories.size > 20) { + histories.removeLast() + } + + properties.putString("QuickConnect.historyHosts", ohMyJson.encodeToString(histories)) + + return historyHost + } + + private fun getHistories(): List { + val text = properties.getString("QuickConnect.historyHosts", "[]") + return ohMyJson.runCatching { ohMyJson.decodeFromString>(text) } + .getOrNull() ?: emptyList() + } + + override fun addNotify() { + super.addNotify() + controlsVisible = false + } + + } + + @Serializable + private data class HistoryHost( + val url: String, + val host: Host, + ) + +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/findeverywhere/QuickCommandFindEverywhereProvider.kt b/src/main/kotlin/app/termora/findeverywhere/QuickCommandFindEverywhereProvider.kt index 8bacc7a..1a14d84 100644 --- a/src/main/kotlin/app/termora/findeverywhere/QuickCommandFindEverywhereProvider.kt +++ b/src/main/kotlin/app/termora/findeverywhere/QuickCommandFindEverywhereProvider.kt @@ -6,6 +6,7 @@ import app.termora.Icons import app.termora.Scope import app.termora.actions.NewHostAction import app.termora.actions.OpenLocalTerminalAction +import app.termora.actions.QuickConnectAction import app.termora.snippet.SnippetAction import com.formdev.flatlaf.FlatLaf import org.jdesktop.swingx.action.ActionManager @@ -19,19 +20,13 @@ class QuickCommandFindEverywhereProvider : FindEverywhereProvider { actionManager.let { list.add(CreateHostFindEverywhereResult()) } // Local terminal - actionManager.getAction(OpenLocalTerminalAction.LOCAL_TERMINAL)?.let { - list.add(ActionFindEverywhereResult(it)) - } - + actionManager.getAction(OpenLocalTerminalAction.LOCAL_TERMINAL)?.let { list.add(ActionFindEverywhereResult(it)) } // Snippet - actionManager.getAction(SnippetAction.SNIPPET)?.let { - list.add(ActionFindEverywhereResult(it)) - } - + actionManager.getAction(SnippetAction.SNIPPET)?.let { list.add(ActionFindEverywhereResult(it)) } // SFTP - actionManager.getAction(Actions.SFTP)?.let { - list.add(ActionFindEverywhereResult(it)) - } + actionManager.getAction(Actions.SFTP)?.let { list.add(ActionFindEverywhereResult(it)) } + // quick connect + actionManager.getAction(QuickConnectAction.QUICK_CONNECT)?.let { list.add(ActionFindEverywhereResult(it)) } return list } diff --git a/src/main/kotlin/app/termora/plugin/internal/ssh/SshClients.kt b/src/main/kotlin/app/termora/plugin/internal/ssh/SshClients.kt index fe5090f..1f7c5e6 100644 --- a/src/main/kotlin/app/termora/plugin/internal/ssh/SshClients.kt +++ b/src/main/kotlin/app/termora/plugin/internal/ssh/SshClients.kt @@ -250,7 +250,8 @@ object SshClients { val session = client.connect(entry).verify(timeout).session if (host.authentication.type == AuthenticationType.Password) { - session.addPasswordIdentity(host.authentication.password) + if (StringUtils.isNotBlank(host.authentication.password)) + session.addPasswordIdentity(host.authentication.password) } else if (host.authentication.type == AuthenticationType.PublicKey) { session.keyIdentityProvider = OhKeyPairKeyPairProvider(host.authentication.password) } diff --git a/src/main/kotlin/app/termora/transfer/TransferActionEvent.kt b/src/main/kotlin/app/termora/transfer/TransferActionEvent.kt index 9bb703e..253890f 100644 --- a/src/main/kotlin/app/termora/transfer/TransferActionEvent.kt +++ b/src/main/kotlin/app/termora/transfer/TransferActionEvent.kt @@ -1,11 +1,12 @@ package app.termora.transfer +import app.termora.Host import app.termora.actions.AnActionEvent import org.apache.commons.lang3.StringUtils import java.util.* class TransferActionEvent( source: Any, - val hostId: String, + val host: Host? = null, event: EventObject ) : AnActionEvent(source, StringUtils.EMPTY, event) \ No newline at end of file diff --git a/src/main/kotlin/app/termora/transfer/TransferAnAction.kt b/src/main/kotlin/app/termora/transfer/TransferAnAction.kt index 8e13d28..e12e2ae 100644 --- a/src/main/kotlin/app/termora/transfer/TransferAnAction.kt +++ b/src/main/kotlin/app/termora/transfer/TransferAnAction.kt @@ -1,6 +1,5 @@ package app.termora.transfer -import app.termora.HostManager import app.termora.HostTerminalTab import app.termora.I18n import app.termora.Icons @@ -8,10 +7,8 @@ import app.termora.actions.AnAction import app.termora.actions.AnActionEvent import app.termora.actions.DataProviders import app.termora.protocol.TransferProtocolProvider -import org.apache.commons.lang3.StringUtils class TransferAnAction : AnAction(I18n.getString("termora.transport.sftp"), Icons.folder) { - private val hostManager get() = HostManager.getInstance() override fun actionPerformed(evt: AnActionEvent) { val terminalTabbedManager = evt.getData(DataProviders.TerminalTabbedManager) ?: return @@ -29,35 +26,32 @@ class TransferAnAction : AnAction(I18n.getString("termora.transport.sftp"), Icon terminalTabbedManager.addTerminalTab(sftpTab, false) } - var hostId = if (evt is TransferActionEvent) evt.hostId else StringUtils.EMPTY + var host = if (evt is TransferActionEvent) evt.host else null - // 如果不是特定事件,那么尝试获取选中的Tab,如果是一个 Host 并且是 SSH 协议那么直接打开 - if (hostId.isBlank()) { + if (host == null) { val tab = terminalTabbedManager.getSelectedTerminalTab() + // 如果当前选中的是 Host 主机 if (tab is HostTerminalTab) { if (TransferProtocolProvider.valueOf(tab.host.protocol) != null) { - hostId = tab.host.id + host = tab.host } } } - terminalTabbedManager.setSelectedTerminalTab(sftpTab) - - if (hostId.isBlank()) return - val tabbed = sftpTab.rightTabbed + // 如果已经打开了 那么直接选中 - for (i in 0 until tabbed.tabCount) { - val panel = tabbed.getTransportPanel(i) ?: continue - if (panel.host.id == hostId) { - tabbed.selectedIndex = i - return + if (host != null) { + for (i in 0 until tabbed.tabCount) { + val panel = tabbed.getTransportPanel(i) ?: continue + if (panel.host.id == host.id) { + tabbed.selectedIndex = i + return + } } } - val host = hostManager.getHost(hostId) ?: return var selectionPane: TransportSelectionPanel? = null - for (i in 0 until tabbed.tabCount) { val c = tabbed.getComponentAt(i) if (c is TransportSelectionPanel) { @@ -72,8 +66,10 @@ class TransferAnAction : AnAction(I18n.getString("termora.transport.sftp"), Icon selectionPane = tabbed.addSelectionTab() } - selectionPane.connect(host) - + if (host != null) { + selectionPane.connect(host) + } + terminalTabbedManager.setSelectedTerminalTab(sftpTab) } } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/tree/NewHostTree.kt b/src/main/kotlin/app/termora/tree/NewHostTree.kt index 324c0ce..c685e77 100644 --- a/src/main/kotlin/app/termora/tree/NewHostTree.kt +++ b/src/main/kotlin/app/termora/tree/NewHostTree.kt @@ -481,12 +481,12 @@ class NewHostTree : SimpleTree(), Disposable { } private fun openWithSFTP(evt: EventObject) { - val nodes = getSelectionSimpleTreeNodes(true) + val hosts = getSelectionSimpleTreeNodes(true) .map { it.host }.filter { TransferProtocolProvider.valueOf(it.protocol) != null } - if (nodes.isEmpty()) return + if (hosts.isEmpty()) return - for (node in nodes) { - sftpAction.actionPerformed(TransferActionEvent(this, node.id, evt)) + for (host in hosts) { + sftpAction.actionPerformed(TransferActionEvent(this, host, evt)) } } diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index 31edc69..89ec2b7 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -414,6 +414,7 @@ termora.actions.zoom-in-terminal=Zoom In Terminal termora.actions.zoom-out-terminal=Zoom Out Terminal termora.actions.zoom-reset-terminal=Reset Terminal Zoom termora.actions.open-local-terminal=Open Local Terminal +termora.actions.quick-connect=Quick Connect termora.actions.open-find-everywhere=Open FindEverywhere termora.actions.open-new-window=Open new Window termora.actions.clear-screen=Clear Terminal Screen diff --git a/src/main/resources/i18n/messages_zh_CN.properties b/src/main/resources/i18n/messages_zh_CN.properties index 3d07a67..c3d1c11 100644 --- a/src/main/resources/i18n/messages_zh_CN.properties +++ b/src/main/resources/i18n/messages_zh_CN.properties @@ -417,6 +417,7 @@ termora.actions.zoom-in-terminal=放大终端 termora.actions.zoom-out-terminal=缩小终端 termora.actions.zoom-reset-terminal=重置终端缩放 termora.actions.open-local-terminal=打开本地终端 +termora.actions.quick-connect=快速连接 termora.actions.open-find-everywhere=打开全局查找 termora.actions.open-new-window=打开新窗口 termora.actions.clear-screen=清除终端屏幕 diff --git a/src/main/resources/i18n/messages_zh_TW.properties b/src/main/resources/i18n/messages_zh_TW.properties index 7006783..0afc1ef 100644 --- a/src/main/resources/i18n/messages_zh_TW.properties +++ b/src/main/resources/i18n/messages_zh_TW.properties @@ -405,6 +405,7 @@ termora.actions.zoom-in-terminal=放大終端 termora.actions.zoom-out-terminal=縮小終端 termora.actions.zoom-reset-terminal=重置終端縮放 termora.actions.open-local-terminal=開啟本地終端 +termora.actions.quick-connect=快速連接 termora.actions.open-find-everywhere=開啟全域搜尋 termora.actions.open-new-window=開啟新視窗 termora.actions.clear-screen=清除終端機螢幕