diff --git a/src/main/kotlin/app/termora/Actions.kt b/src/main/kotlin/app/termora/Actions.kt index f8cb7d1..f992ea2 100644 --- a/src/main/kotlin/app/termora/Actions.kt +++ b/src/main/kotlin/app/termora/Actions.kt @@ -2,12 +2,6 @@ package app.termora object Actions { - - /** - * 将命令发送到多个会话 - */ - const val MULTIPLE = "MultipleAction" - /** * 关键词高亮 */ diff --git a/src/main/kotlin/app/termora/MultiplePtyConnector.kt b/src/main/kotlin/app/termora/MultiplePtyConnector.kt deleted file mode 100644 index db4245e..0000000 --- a/src/main/kotlin/app/termora/MultiplePtyConnector.kt +++ /dev/null @@ -1,49 +0,0 @@ -package app.termora - - -import app.termora.terminal.PtyConnector -import app.termora.terminal.PtyConnectorDelegate -import org.jdesktop.swingx.action.ActionManager - -/** - * 当开启转发时,会获取到所有的 [PtyConnector] 然后跳过中间层,直接找到最近的一个 [MultiplePtyConnector],如果找不到那就以最后一个匹配不到的为准 [getMultiplePtyConnector]。 - */ -class MultiplePtyConnector( - private val myConnector: PtyConnector -) : PtyConnectorDelegate(myConnector) { - - private val isMultiple get() = ActionManager.getInstance().isSelected(Actions.MULTIPLE) - private val ptyConnectors - get() = PtyConnectorFactory.getInstance().getPtyConnectors() - - override fun write(buffer: ByteArray, offset: Int, len: Int) { - if (isMultiple) { - for (connector in ptyConnectors) { - getMultiplePtyConnector(connector).write(buffer, offset, len) - } - } else { - myConnector.write(buffer, offset, len) - } - } - - - private fun getMultiplePtyConnector(connector: PtyConnector): PtyConnector { - if (connector is MultiplePtyConnector) { - val c = connector.myConnector - if (c is MultiplePtyConnector) { - return getMultiplePtyConnector(c) - } - return c - } - - if (connector is PtyConnectorDelegate) { - val c = connector.ptyConnector - if (c != null) { - return getMultiplePtyConnector(c) - } - } - - return connector - } - -} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/MultipleTerminalListener.kt b/src/main/kotlin/app/termora/MultipleTerminalListener.kt index 987f713..a76a58c 100644 --- a/src/main/kotlin/app/termora/MultipleTerminalListener.kt +++ b/src/main/kotlin/app/termora/MultipleTerminalListener.kt @@ -1,7 +1,9 @@ package app.termora -import app.termora.actions.ActionManager +import app.termora.actions.AnActionEvent +import app.termora.actions.DataProviders +import app.termora.actions.MultipleAction import app.termora.terminal.Terminal import app.termora.terminal.TerminalColor import app.termora.terminal.TextStyle @@ -9,8 +11,10 @@ import app.termora.terminal.panel.FloatingToolbarPanel import app.termora.terminal.panel.TerminalDisplay import app.termora.terminal.panel.TerminalPaintListener import app.termora.terminal.panel.TerminalPanel +import org.apache.commons.lang3.StringUtils import java.awt.Color import java.awt.Graphics +import java.util.* class MultipleTerminalListener : TerminalPaintListener { override fun after( @@ -21,9 +25,9 @@ class MultipleTerminalListener : TerminalPaintListener { terminalDisplay: TerminalDisplay, terminal: Terminal ) { - if (!ActionManager.getInstance().isSelected(Actions.MULTIPLE)) { - return - } + val windowScope = AnActionEvent(terminalPanel, StringUtils.EMPTY, EventObject(terminalPanel)) + .getData(DataProviders.WindowScope) ?: return + if (!MultipleAction.getInstance(windowScope).isSelected) return val oldFont = g.font val colorPalette = terminal.getTerminalModel().getColorPalette() diff --git a/src/main/kotlin/app/termora/PtyConnectorFactory.kt b/src/main/kotlin/app/termora/PtyConnectorFactory.kt index 1c51f3b..485b2c6 100644 --- a/src/main/kotlin/app/termora/PtyConnectorFactory.kt +++ b/src/main/kotlin/app/termora/PtyConnectorFactory.kt @@ -19,7 +19,8 @@ class PtyConnectorFactory : Disposable { companion object { private val log = LoggerFactory.getLogger(PtyConnectorFactory::class.java) fun getInstance(): PtyConnectorFactory { - return ApplicationScope.forApplicationScope().getOrCreate(PtyConnectorFactory::class) { PtyConnectorFactory() } + return ApplicationScope.forApplicationScope() + .getOrCreate(PtyConnectorFactory::class) { PtyConnectorFactory() } } } @@ -86,20 +87,14 @@ class PtyConnectorFactory : Disposable { } fun decorate(ptyConnector: PtyConnector): PtyConnector { - // 集成转发,如果PtyConnector支持转发那么应该在当前注释行前面代理 - val multiplePtyConnector = MultiplePtyConnector(ptyConnector) - // 宏应该在转发前面执行,不然会导致重复录制 - val macroPtyConnector = MacroPtyConnector(multiplePtyConnector) + // 宏 + val macroPtyConnector = MacroPtyConnector(ptyConnector) // 集成自动删除 val autoRemovePtyConnector = AutoRemovePtyConnector(macroPtyConnector) ptyConnectors.add(autoRemovePtyConnector) return autoRemovePtyConnector } - fun getPtyConnectors(): List { - return ptyConnectors - } - private inner class AutoRemovePtyConnector(connector: PtyConnector) : PtyConnectorDelegate(connector) { override fun close() { ptyConnectors.remove(this) diff --git a/src/main/kotlin/app/termora/PtyHostTerminalTab.kt b/src/main/kotlin/app/termora/PtyHostTerminalTab.kt index 267cfef..5685dab 100644 --- a/src/main/kotlin/app/termora/PtyHostTerminalTab.kt +++ b/src/main/kotlin/app/termora/PtyHostTerminalTab.kt @@ -23,16 +23,9 @@ abstract class PtyHostTerminalTab( private var readerJob: Job? = null private val ptyConnectorDelegate = PtyConnectorDelegate() - - private val terminalPanelFactory = TerminalPanelFactory.getInstance() - protected val terminalPanel = terminalPanelFactory.createTerminalPanel(terminal, ptyConnectorDelegate) - .apply { Disposer.register(this@PtyHostTerminalTab, this) } + protected val terminalPanel = TerminalPanelFactory.getInstance().createTerminalPanel(terminal, ptyConnectorDelegate) protected val ptyConnectorFactory get() = PtyConnectorFactory.getInstance() - init { - terminal.getTerminalModel().setData(DataKey.PtyConnector, ptyConnectorDelegate) - } - override fun start() { coroutineScope.launch(Dispatchers.IO) { @@ -122,10 +115,9 @@ abstract class PtyHostTerminalTab( override fun dispose() { stop() - terminalPanel + Disposer.dispose(terminalPanel) super.dispose() - if (log.isInfoEnabled) { log.info("Host: {} disposed", host.name) } @@ -141,6 +133,8 @@ abstract class PtyHostTerminalTab( override fun getData(dataKey: DataKey): T? { if (dataKey == DataProviders.TerminalPanel) { return terminalPanel as T? + } else if (dataKey == DataProviders.TerminalWriter) { + return terminalPanel.getData(DataKey.TerminalWriter) as T? } return super.getData(dataKey) } diff --git a/src/main/kotlin/app/termora/TerminalPanelFactory.kt b/src/main/kotlin/app/termora/TerminalPanelFactory.kt index 9a81c56..3999032 100644 --- a/src/main/kotlin/app/termora/TerminalPanelFactory.kt +++ b/src/main/kotlin/app/termora/TerminalPanelFactory.kt @@ -1,14 +1,21 @@ package app.termora +import app.termora.actions.AnActionEvent +import app.termora.actions.DataProviders +import app.termora.actions.MultipleAction import app.termora.highlight.KeywordHighlightPaintListener -import app.termora.terminal.DataKey import app.termora.terminal.PtyConnector import app.termora.terminal.Terminal import app.termora.terminal.panel.TerminalHyperlinkPaintListener import app.termora.terminal.panel.TerminalPanel +import app.termora.terminal.panel.TerminalWriter import kotlinx.coroutines.* +import org.apache.commons.lang3.StringUtils import java.awt.event.ComponentEvent import java.awt.event.ComponentListener +import java.nio.charset.Charset +import java.util.* +import javax.swing.JComponent import javax.swing.SwingUtilities import kotlin.time.Duration.Companion.milliseconds @@ -17,8 +24,6 @@ class TerminalPanelFactory : Disposable { companion object { - private val Factory = DataKey(TerminalPanelFactory::class) - fun getInstance(): TerminalPanelFactory { return ApplicationScope.forApplicationScope() .getOrCreate(TerminalPanelFactory::class) { TerminalPanelFactory() } @@ -32,17 +37,15 @@ class TerminalPanelFactory : Disposable { fun createTerminalPanel(terminal: Terminal, ptyConnector: PtyConnector): TerminalPanel { - val terminalPanel = TerminalPanel(terminal, ptyConnector) + val writer = MyTerminalWriter(ptyConnector) + val terminalPanel = TerminalPanel(terminal, writer) terminalPanel.addTerminalPaintListener(MultipleTerminalListener()) terminalPanel.addTerminalPaintListener(KeywordHighlightPaintListener.getInstance()) terminalPanel.addTerminalPaintListener(TerminalHyperlinkPaintListener.getInstance()) - terminal.getTerminalModel().setData(Factory, this) Disposer.register(terminalPanel, object : Disposable { override fun dispose() { - if (terminal.getTerminalModel().hasData(Factory)) { - terminal.getTerminalModel().getData(Factory).removeTerminalPanel(terminalPanel) - } + removeTerminalPanel(terminalPanel) } }) @@ -70,13 +73,12 @@ class TerminalPanelFactory : Disposable { } } - fun removeTerminalPanel(terminalPanel: TerminalPanel) { + private fun removeTerminalPanel(terminalPanel: TerminalPanel) { terminalPanels.remove(terminalPanel) } - fun addTerminalPanel(terminalPanel: TerminalPanel) { + private fun addTerminalPanel(terminalPanel: TerminalPanel) { terminalPanels.add(terminalPanel) - terminalPanel.terminal.getTerminalModel().setData(Factory, this) } private class Painter : Disposable { @@ -102,4 +104,49 @@ class TerminalPanelFactory : Disposable { } } + private class MyTerminalWriter(private val ptyConnector: PtyConnector) : TerminalWriter { + private lateinit var evt: AnActionEvent + + override fun onMounted(c: JComponent) { + evt = AnActionEvent(c, StringUtils.EMPTY, EventObject(c)) + } + + override fun write(request: TerminalWriter.WriteRequest) { + val windowScope = evt.getData(DataProviders.WindowScope) + if (windowScope == null) { + ptyConnector.write(request.buffer) + return + } + + val multipleAction = MultipleAction.getInstance(windowScope) + if (!multipleAction.isSelected) { + ptyConnector.write(request.buffer) + return + } + + val terminalTabbedManager = evt.getData(DataProviders.TerminalTabbedManager) + if (terminalTabbedManager == null) { + ptyConnector.write(request.buffer) + return + } + + for (tab in terminalTabbedManager.getTerminalTabs()) { + val writer = tab.getData(DataProviders.TerminalWriter) ?: continue + if (writer is MyTerminalWriter) { + writer.ptyConnector.write(request.buffer) + } + } + + } + + override fun resize(rows: Int, cols: Int) { + ptyConnector.resize(rows, cols) + } + + override fun getCharset(): Charset { + return ptyConnector.getCharset() + } + + } + } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/TermoraFrame.kt b/src/main/kotlin/app/termora/TermoraFrame.kt index dc28cfd..b78cf42 100644 --- a/src/main/kotlin/app/termora/TermoraFrame.kt +++ b/src/main/kotlin/app/termora/TermoraFrame.kt @@ -35,7 +35,7 @@ class TermoraFrame : JFrame(), DataProvider { private val windowScope = ApplicationScope.forWindowScope(this) private val titleBar = LogicCustomTitleBar.createCustomTitleBar(this) private val tabbedPane = MyTabbedPane() - private val toolbar = TermoraToolBar(titleBar, tabbedPane) + private val toolbar = TermoraToolBar(windowScope, titleBar, tabbedPane) private val terminalTabbed = TerminalTabbed(windowScope, toolbar, tabbedPane) private val isWindowDecorationsSupported by lazy { JBR.isWindowDecorationsSupported() } private val dataProviderSupport = DataProviderSupport() diff --git a/src/main/kotlin/app/termora/TermoraToolBar.kt b/src/main/kotlin/app/termora/TermoraToolBar.kt index c88c3a4..8c5fbb4 100644 --- a/src/main/kotlin/app/termora/TermoraToolBar.kt +++ b/src/main/kotlin/app/termora/TermoraToolBar.kt @@ -1,10 +1,7 @@ package app.termora import app.termora.Application.ohMyJson -import app.termora.actions.ActionManager -import app.termora.actions.AnAction -import app.termora.actions.AnActionEvent -import app.termora.actions.SettingsAction +import app.termora.actions.* import app.termora.findeverywhere.FindEverywhereAction import app.termora.snippet.SnippetAction import com.formdev.flatlaf.extras.components.FlatTabbedPane @@ -27,6 +24,7 @@ data class ToolBarAction( ) class TermoraToolBar( + private val windowScope: WindowScope, private val titleBar: WindowDecorations.CustomTitleBar, private val tabbedPane: FlatTabbedPane ) { @@ -49,7 +47,7 @@ class TermoraToolBar( ToolBarAction(Actions.MACRO, true), ToolBarAction(Actions.KEYWORD_HIGHLIGHT, true), ToolBarAction(Actions.KEY_MANAGER, true), - ToolBarAction(Actions.MULTIPLE, true), + ToolBarAction(MultipleAction.MULTIPLE, true), ToolBarAction(FindEverywhereAction.FIND_EVERYWHERE, true), ToolBarAction(SettingsAction.SETTING, true), ) @@ -126,8 +124,13 @@ class TermoraToolBar( // 获取显示的Action,如果不是 false 那么就是显示出来 for (action in getActions()) { if (action.visible) { - actionManager.getAction(action.id)?.let { - toolbar.add(actionContainerFactory.createButton(it)) + val ac = actionManager.getAction(action.id) + if (ac == null) { + if (action.id == MultipleAction.MULTIPLE) { + toolbar.add(actionContainerFactory.createButton(MultipleAction.getInstance(windowScope))) + } + } else { + toolbar.add(actionContainerFactory.createButton(ac)) } } } diff --git a/src/main/kotlin/app/termora/actions/ActionManager.kt b/src/main/kotlin/app/termora/actions/ActionManager.kt index ad168dc..e80fb0d 100644 --- a/src/main/kotlin/app/termora/actions/ActionManager.kt +++ b/src/main/kotlin/app/termora/actions/ActionManager.kt @@ -29,7 +29,6 @@ class ActionManager : org.jdesktop.swingx.action.ActionManager() { addAction(NewWindowAction.NEW_WINDOW, NewWindowAction()) addAction(FindEverywhereAction.FIND_EVERYWHERE, FindEverywhereAction()) - addAction(Actions.MULTIPLE, MultipleAction()) addAction(Actions.APP_UPDATE, AppUpdateAction.getInstance()) addAction(Actions.KEYWORD_HIGHLIGHT, KeywordHighlightAction()) addAction(Actions.TERMINAL_LOGGER, TerminalLoggerAction()) diff --git a/src/main/kotlin/app/termora/actions/DataProviders.kt b/src/main/kotlin/app/termora/actions/DataProviders.kt index d51ff94..9a34a6c 100644 --- a/src/main/kotlin/app/termora/actions/DataProviders.kt +++ b/src/main/kotlin/app/termora/actions/DataProviders.kt @@ -5,7 +5,7 @@ import app.termora.terminal.DataKey object DataProviders { val TerminalPanel = DataKey(app.termora.terminal.panel.TerminalPanel::class) val Terminal = DataKey(app.termora.terminal.Terminal::class) - val PtyConnector get() = DataKey.PtyConnector + val TerminalWriter get() = DataKey.TerminalWriter val TabbedPane = DataKey(app.termora.MyTabbedPane::class) val TerminalTabbed = DataKey(app.termora.TerminalTabbed::class) diff --git a/src/main/kotlin/app/termora/actions/MultipleAction.kt b/src/main/kotlin/app/termora/actions/MultipleAction.kt index 35bfeef..76aee39 100644 --- a/src/main/kotlin/app/termora/actions/MultipleAction.kt +++ b/src/main/kotlin/app/termora/actions/MultipleAction.kt @@ -3,11 +3,25 @@ package app.termora.actions import app.termora.I18n import app.termora.Icons import app.termora.TerminalPanelFactory +import app.termora.WindowScope -class MultipleAction : AnAction( +class MultipleAction private constructor() : AnAction( I18n.getString("termora.tools.multiple"), Icons.vcs ) { + + companion object { + + /** + * 将命令发送到多个会话 + */ + const val MULTIPLE = "MultipleAction" + + fun getInstance(windowScope: WindowScope): MultipleAction { + return windowScope.getOrCreate(MultipleAction::class) { MultipleAction() } + } + } + init { setStateAction() } diff --git a/src/main/kotlin/app/termora/findeverywhere/FindEverywhere.kt b/src/main/kotlin/app/termora/findeverywhere/FindEverywhere.kt index 1d0a708..3352097 100644 --- a/src/main/kotlin/app/termora/findeverywhere/FindEverywhere.kt +++ b/src/main/kotlin/app/termora/findeverywhere/FindEverywhere.kt @@ -3,6 +3,7 @@ package app.termora.findeverywhere import app.termora.DialogWrapper import app.termora.DynamicColor import app.termora.I18n +import app.termora.WindowScope import app.termora.actions.AnAction import app.termora.actions.AnActionEvent import app.termora.macro.MacroFindEverywhereProvider @@ -18,7 +19,7 @@ import javax.swing.* import javax.swing.event.DocumentEvent import javax.swing.event.DocumentListener -class FindEverywhere(owner: Window) : DialogWrapper(owner) { +class FindEverywhere(owner: Window, windowScope: WindowScope) : DialogWrapper(owner) { private val searchTextField = FlatTextField() private val model = DefaultListModel() private val resultList = FindEverywhereXList(model) @@ -26,7 +27,7 @@ class FindEverywhere(owner: Window) : DialogWrapper(owner) { private val providers = mutableListOf( BasicFilterFindEverywhereProvider(QuickCommandFindEverywhereProvider()), BasicFilterFindEverywhereProvider(SettingsFindEverywhereProvider()), - BasicFilterFindEverywhereProvider(QuickActionsFindEverywhereProvider()), + BasicFilterFindEverywhereProvider(QuickActionsFindEverywhereProvider(windowScope)), BasicFilterFindEverywhereProvider(MacroFindEverywhereProvider()), ) diff --git a/src/main/kotlin/app/termora/findeverywhere/FindEverywhereAction.kt b/src/main/kotlin/app/termora/findeverywhere/FindEverywhereAction.kt index fef37f8..e4ccd66 100644 --- a/src/main/kotlin/app/termora/findeverywhere/FindEverywhereAction.kt +++ b/src/main/kotlin/app/termora/findeverywhere/FindEverywhereAction.kt @@ -46,7 +46,7 @@ class FindEverywhereAction : AnAction(StringUtils.EMPTY, Icons.find) { return } - val dialog = FindEverywhere(owner) + val dialog = FindEverywhere(owner, scope) for (provider in FindEverywhereProvider.getFindEverywhereProviders(scope)) { dialog.registerProvider(provider) } diff --git a/src/main/kotlin/app/termora/findeverywhere/QuickActionsFindEverywhereProvider.kt b/src/main/kotlin/app/termora/findeverywhere/QuickActionsFindEverywhereProvider.kt index 4a01969..9733fb4 100644 --- a/src/main/kotlin/app/termora/findeverywhere/QuickActionsFindEverywhereProvider.kt +++ b/src/main/kotlin/app/termora/findeverywhere/QuickActionsFindEverywhereProvider.kt @@ -2,21 +2,32 @@ package app.termora.findeverywhere import app.termora.Actions import app.termora.I18n +import app.termora.WindowScope +import app.termora.actions.MultipleAction import org.jdesktop.swingx.action.ActionManager -class QuickActionsFindEverywhereProvider : FindEverywhereProvider { +class QuickActionsFindEverywhereProvider(private val windowScope: WindowScope) : FindEverywhereProvider { private val actions = listOf( Actions.KEY_MANAGER, Actions.KEYWORD_HIGHLIGHT, - Actions.MULTIPLE, + MultipleAction.MULTIPLE, ) override fun find(pattern: String): List { val actionManager = ActionManager.getInstance() - return actions - .mapNotNull { actionManager.getAction(it) } - .map { ActionFindEverywhereResult(it) } + val results = ArrayList() + for (action in actions) { + val ac = actionManager.getAction(action) + if (ac == null) { + if (action == MultipleAction.MULTIPLE) { + results.add(ActionFindEverywhereResult(MultipleAction.getInstance(windowScope))) + } + } else { + results.add(ActionFindEverywhereResult(ac)) + } + } + return results } diff --git a/src/main/kotlin/app/termora/snippet/SnippetAction.kt b/src/main/kotlin/app/termora/snippet/SnippetAction.kt index a61196d..550e43e 100644 --- a/src/main/kotlin/app/termora/snippet/SnippetAction.kt +++ b/src/main/kotlin/app/termora/snippet/SnippetAction.kt @@ -6,8 +6,7 @@ import app.termora.Icons import app.termora.actions.AnAction import app.termora.actions.AnActionEvent import app.termora.terminal.ControlCharacters -import app.termora.terminal.DataKey -import app.termora.terminal.Terminal +import app.termora.terminal.panel.TerminalWriter class SnippetAction private constructor() : AnAction(I18n.getString("termora.snippet.title"), Icons.codeSpan) { companion object { @@ -23,9 +22,8 @@ class SnippetAction private constructor() : AnAction(I18n.getString("termora.sni } - fun runSnippet(snippet: Snippet, terminal: Terminal) { + fun runSnippet(snippet: Snippet, writer: TerminalWriter) { if (snippet.type != SnippetType.Snippet) return - val terminalModel = terminal.getTerminalModel() val map = mapOf( "\\r" to ControlCharacters.CR, "\\n" to ControlCharacters.LF, @@ -35,13 +33,10 @@ class SnippetAction private constructor() : AnAction(I18n.getString("termora.sni "\\b" to ControlCharacters.BS, ) - if (terminalModel.hasData(DataKey.PtyConnector)) { - var text = snippet.snippet - for (e in map.entries) { - text = text.replace(e.key, e.value.toString()) - } - val ptyConnector = terminalModel.getData(DataKey.PtyConnector) - ptyConnector.write(text.toByteArray(ptyConnector.getCharset())) + var text = snippet.snippet + for (e in map.entries) { + text = text.replace(e.key, e.value.toString()) } + writer.write(TerminalWriter.WriteRequest.fromBytes(text.toByteArray(writer.getCharset()))) } } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/snippet/SnippetTreeDialog.kt b/src/main/kotlin/app/termora/snippet/SnippetTreeDialog.kt index c96dd7b..5a054d3 100644 --- a/src/main/kotlin/app/termora/snippet/SnippetTreeDialog.kt +++ b/src/main/kotlin/app/termora/snippet/SnippetTreeDialog.kt @@ -4,9 +4,12 @@ import app.termora.* import org.apache.commons.lang3.StringUtils import java.awt.Dimension import java.awt.Window +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent import javax.swing.BorderFactory import javax.swing.JComponent import javax.swing.JScrollPane +import javax.swing.SwingUtilities class SnippetTreeDialog(owner: Window) : DialogWrapper(owner) { private val snippetTree = SnippetTree() @@ -23,6 +26,15 @@ class SnippetTreeDialog(owner: Window) : DialogWrapper(owner) { setLocationRelativeTo(null) init() + snippetTree.addMouseListener(object : MouseAdapter() { + override fun mouseClicked(e: MouseEvent) { + if (SwingUtilities.isLeftMouseButton(e) && e.clickCount % 2 == 0) { + val node = snippetTree.getLastSelectedPathNode() ?: return + if (node.isFolder) return + doOKAction() + } + } + }) Disposer.register(disposable, object : Disposable { override fun dispose() { diff --git a/src/main/kotlin/app/termora/terminal/ControlSequenceIntroducerProcessor.kt b/src/main/kotlin/app/termora/terminal/ControlSequenceIntroducerProcessor.kt index a08bb59..7ee6063 100644 --- a/src/main/kotlin/app/termora/terminal/ControlSequenceIntroducerProcessor.kt +++ b/src/main/kotlin/app/termora/terminal/ControlSequenceIntroducerProcessor.kt @@ -1,6 +1,7 @@ package app.termora.terminal import app.termora.assertEventDispatchThread +import app.termora.terminal.panel.TerminalWriter import org.slf4j.LoggerFactory import kotlin.math.max import kotlin.math.min @@ -476,20 +477,20 @@ class ControlSequenceIntroducerProcessor(terminal: Terminal, reader: TerminalRea return } - if (!terminalModel.hasData(DataKey.PtyConnector)) { + if (!terminalModel.hasData(DataKey.TerminalWriter)) { return } - val ptyConnector = terminalModel.getData(DataKey.PtyConnector) + val writer = terminalModel.getData(DataKey.TerminalWriter) val m = args.first() if (m == '6') { val position = terminal.getCursorModel().getPosition() - val bytes = "${ControlCharacters.ESC}[${position.y};${position.x}R".toByteArray(ptyConnector.getCharset()) - ptyConnector.write(bytes) + val bytes = "${ControlCharacters.ESC}[${position.y};${position.x}R".toByteArray(writer.getCharset()) + writer.write(TerminalWriter.WriteRequest.fromBytes(bytes)) } else if (m == '5') { - val bytes = "${ControlCharacters.ESC}[0n".toByteArray(ptyConnector.getCharset()) - ptyConnector.write(bytes) + val bytes = "${ControlCharacters.ESC}[0n".toByteArray(writer.getCharset()) + writer.write(TerminalWriter.WriteRequest.fromBytes(bytes)) } } diff --git a/src/main/kotlin/app/termora/terminal/DataKey.kt b/src/main/kotlin/app/termora/terminal/DataKey.kt index 5c29010..18c3382 100644 --- a/src/main/kotlin/app/termora/terminal/DataKey.kt +++ b/src/main/kotlin/app/termora/terminal/DataKey.kt @@ -189,9 +189,9 @@ class DataKey(val clazz: KClass) { val CursorStyle = DataKey(app.termora.terminal.CursorStyle::class) /** - * Pty Connector + * TerminalWriter */ - val PtyConnector = DataKey(app.termora.terminal.PtyConnector::class) + val TerminalWriter = DataKey(app.termora.terminal.panel.TerminalWriter::class) } } diff --git a/src/main/kotlin/app/termora/terminal/panel/FloatingToolbarPanel.kt b/src/main/kotlin/app/termora/terminal/panel/FloatingToolbarPanel.kt index 71d8739..f6bb053 100644 --- a/src/main/kotlin/app/termora/terminal/panel/FloatingToolbarPanel.kt +++ b/src/main/kotlin/app/termora/terminal/panel/FloatingToolbarPanel.kt @@ -157,13 +157,13 @@ class FloatingToolbarPanel : FlatToolBar(), Disposable { btn.addActionListener(object : AnAction() { override fun actionPerformed(evt: AnActionEvent) { val tab = evt.getData(DataProviders.TerminalTab) ?: return - val terminal = tab.getData(DataProviders.Terminal) ?: return + val writer = tab.getData(DataProviders.TerminalWriter) ?: return val dialog = SnippetTreeDialog(evt.window) dialog.setLocationRelativeTo(btn) dialog.setLocation(dialog.x, btn.locationOnScreen.y + height + 2) dialog.isVisible = true val node = dialog.getSelectedNode() ?: return - SnippetAction.getInstance().runSnippet(node.data, terminal) + SnippetAction.getInstance().runSnippet(node.data, writer) } }) return btn diff --git a/src/main/kotlin/app/termora/terminal/panel/TerminalPanel.kt b/src/main/kotlin/app/termora/terminal/panel/TerminalPanel.kt index de374d9..62461d0 100644 --- a/src/main/kotlin/app/termora/terminal/panel/TerminalPanel.kt +++ b/src/main/kotlin/app/termora/terminal/panel/TerminalPanel.kt @@ -34,7 +34,7 @@ import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds -class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnector) : +class TerminalPanel(val terminal: Terminal, private val writer: TerminalWriter) : JPanel(BorderLayout()), DataProvider, Disposable, VisualWindowManager { companion object { @@ -117,6 +117,9 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect private fun initView() { + + writer.onMounted(this) + isFocusable = true isRequestFocusEnabled = true focusTraversalKeysEnabled = false @@ -146,13 +149,13 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect // DataProviders dataProviderSupport.addData(DataProviders.TerminalPanel, this) dataProviderSupport.addData(DataProviders.Terminal, terminal) - dataProviderSupport.addData(DataProviders.PtyConnector, ptyConnector) + dataProviderSupport.addData(DataProviders.TerminalWriter, writer) dataProviderSupport.addData(FloatingToolbarPanel.FloatingToolbar, floatingToolbar) } private fun initEvents() { - this.addKeyListener(TerminalPanelKeyAdapter(this, terminal, ptyConnector)) + this.addKeyListener(TerminalPanelKeyAdapter(this, terminal, writer)) this.addFocusListener(object : FocusAdapter() { override fun focusLost(e: FocusEvent) { @@ -165,7 +168,7 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect repaintImmediate() } }) - this.addComponentListener(TerminalPanelComponentAdapter(this, terminalDisplay, terminal, ptyConnector)) + this.addComponentListener(TerminalPanelComponentAdapter(this, terminalDisplay, terminal, writer)) // 选中相关 val mouseAdapter = TerminalPanelMouseSelectionAdapter(this, terminal) @@ -177,7 +180,7 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect this.addMouseListener(hyperlinkAdapter) // 鼠标跟踪 - val trackingAdapter = TerminalPanelMouseTrackingAdapter(this, terminal, ptyConnector) + val trackingAdapter = TerminalPanelMouseTrackingAdapter(this, terminal, writer) this.addMouseListener(trackingAdapter) this.addMouseWheelListener(trackingAdapter) @@ -329,7 +332,11 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect // 输入法提交 if (committedCharacterCount > 0) { - ptyConnector.write(sb.toString().toByteArray(ptyConnector.getCharset())) + writer.write( + TerminalWriter.WriteRequest.fromBytes( + sb.toString().toByteArray(writer.getCharset()) + ) + ) } else { val breakIterator = BreakIterator.getCharacterInstance() val chars = mutableListOf() @@ -439,13 +446,17 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect content = content.replace('\n', '\r') if (terminal.getTerminalModel().getData(DataKey.BracketedPasteMode, false)) { - ptyConnector.write( - "${ControlCharacters.ESC}[200~${content}${ControlCharacters.ESC}[201~".toByteArray( - ptyConnector.getCharset() + writer.write( + TerminalWriter.WriteRequest.fromBytes( + "${ControlCharacters.ESC}[200~${content}${ControlCharacters.ESC}[201~".toByteArray(writer.getCharset()) ) ) } else { - ptyConnector.write(content.toByteArray(ptyConnector.getCharset())) + writer.write( + TerminalWriter.WriteRequest.fromBytes( + content.toByteArray(writer.getCharset()) + ) + ) } terminal.getScrollingModel().scrollToRow( diff --git a/src/main/kotlin/app/termora/terminal/panel/TerminalPanelComponentAdapter.kt b/src/main/kotlin/app/termora/terminal/panel/TerminalPanelComponentAdapter.kt index bde1ce5..2ece98d 100644 --- a/src/main/kotlin/app/termora/terminal/panel/TerminalPanelComponentAdapter.kt +++ b/src/main/kotlin/app/termora/terminal/panel/TerminalPanelComponentAdapter.kt @@ -1,6 +1,5 @@ package app.termora.terminal.panel -import app.termora.terminal.PtyConnector import app.termora.terminal.Terminal import org.slf4j.LoggerFactory import java.awt.event.ComponentAdapter @@ -11,7 +10,7 @@ class TerminalPanelComponentAdapter( private val terminalPanel: TerminalPanel, private val terminalDisplay: TerminalDisplay, private val terminal: Terminal, - private val ptyConnector: PtyConnector + private val writer: TerminalWriter ) : ComponentAdapter() { companion object { @@ -30,7 +29,7 @@ class TerminalPanelComponentAdapter( // 修改大小 terminal.getTerminalModel().resize(rows = rows, cols = cols) // 修改终端大小 - ptyConnector.resize(rows, cols) + writer.resize(rows, cols) if (log.isTraceEnabled) { log.trace("size: {} , cols: {} , rows: {}", terminalPanel.size, cols, rows) diff --git a/src/main/kotlin/app/termora/terminal/panel/TerminalPanelKeyAdapter.kt b/src/main/kotlin/app/termora/terminal/panel/TerminalPanelKeyAdapter.kt index e0195fa..b6a2e26 100644 --- a/src/main/kotlin/app/termora/terminal/panel/TerminalPanelKeyAdapter.kt +++ b/src/main/kotlin/app/termora/terminal/panel/TerminalPanelKeyAdapter.kt @@ -2,7 +2,6 @@ package app.termora.terminal.panel import app.termora.keymap.KeyShortcut import app.termora.keymap.KeymapManager -import app.termora.terminal.PtyConnector import app.termora.terminal.Terminal import com.formdev.flatlaf.util.SystemInfo import java.awt.event.InputEvent @@ -13,7 +12,7 @@ import javax.swing.KeyStroke class TerminalPanelKeyAdapter( private val terminalPanel: TerminalPanel, private val terminal: Terminal, - private val ptyConnector: PtyConnector + private val writer: TerminalWriter ) : KeyAdapter() { private val activeKeymap get() = KeymapManager.getInstance().getActiveKeymap() @@ -24,7 +23,13 @@ class TerminalPanelKeyAdapter( } terminal.getSelectionModel().clearSelection() - ptyConnector.write("${e.keyChar}".toByteArray(ptyConnector.getCharset())) + + writer.write( + TerminalWriter.WriteRequest.fromBytes( + "${e.keyChar}".toByteArray(writer.getCharset()) + ) + ) + terminal.getScrollingModel().scrollTo(Int.MAX_VALUE) } @@ -47,7 +52,11 @@ class TerminalPanelKeyAdapter( val encode = terminal.getKeyEncoder().encode(AWTTerminalKeyEvent(e)) if (encode.isNotEmpty()) { - ptyConnector.write(encode.toByteArray(ptyConnector.getCharset())) + writer.write( + TerminalWriter.WriteRequest.fromBytes( + "${e.keyChar}".toByteArray(writer.getCharset()) + ) + ) } // https://github.com/TermoraDev/termora/issues/52 @@ -64,7 +73,11 @@ class TerminalPanelKeyAdapter( terminal.getSelectionModel().clearSelection() // 如果不为空表示已经发送过了,所以这里为空的时候再发送 if (encode.isEmpty()) { - ptyConnector.write("${e.keyChar}".toByteArray(ptyConnector.getCharset())) + writer.write( + TerminalWriter.WriteRequest.fromBytes( + "${e.keyChar}".toByteArray(writer.getCharset()) + ) + ) } terminal.getScrollingModel().scrollTo(Int.MAX_VALUE) } diff --git a/src/main/kotlin/app/termora/terminal/panel/TerminalPanelMouseTrackingAdapter.kt b/src/main/kotlin/app/termora/terminal/panel/TerminalPanelMouseTrackingAdapter.kt index b3332da..2ac8d22 100644 --- a/src/main/kotlin/app/termora/terminal/panel/TerminalPanelMouseTrackingAdapter.kt +++ b/src/main/kotlin/app/termora/terminal/panel/TerminalPanelMouseTrackingAdapter.kt @@ -11,7 +11,7 @@ import kotlin.math.abs class TerminalPanelMouseTrackingAdapter( private val terminalPanel: TerminalPanel, private val terminal: Terminal, - private val ptyConnector: PtyConnector + private val writer: TerminalWriter ) : MouseAdapter() { companion object { @@ -70,14 +70,18 @@ class TerminalPanelMouseTrackingAdapter( val encode = terminal.getKeyEncoder() .encode(TerminalKeyEvent(if (e.wheelRotation < 0) KeyEvent.VK_UP else KeyEvent.VK_DOWN)) if (encode.isBlank()) return - val bytes = encode.toByteArray(ptyConnector.getCharset()) + val bytes = encode.toByteArray(writer.getCharset()) for (i in 0 until abs(unitsToScroll)) { - ptyConnector.write(bytes) + writer.write(TerminalWriter.WriteRequest.fromBytes(bytes)) } } } - private fun sendMouseEvent(position: Position, event: TerminalMouseEvent, eventType: TerminalMouseEventType) { + private fun sendMouseEvent( + position: Position, + event: TerminalMouseEvent, + eventType: TerminalMouseEventType + ) { if (event.button == TerminalMouseButton.None) { return } @@ -112,7 +116,9 @@ class TerminalPanelMouseTrackingAdapter( } - private fun mouseReport(cb: Int, x: Int, y: Int) { + private fun mouseReport( + cb: Int, x: Int, y: Int + ) { val sb = StringBuilder() var charset = Charsets.UTF_8 @@ -143,7 +149,7 @@ class TerminalPanelMouseTrackingAdapter( .append((32 + cb).toChar()).append((32 + x).toChar()).append(x).append((32 + y).toChar()) } - ptyConnector.write(sb.toString().toByteArray(charset)) + writer.write(TerminalWriter.WriteRequest.fromBytes(sb.toString().toByteArray(charset))) if (log.isTraceEnabled) { log.trace("Send ESC{}", sb.substring(1)) diff --git a/src/main/kotlin/app/termora/terminal/panel/TerminalWriter.kt b/src/main/kotlin/app/termora/terminal/panel/TerminalWriter.kt new file mode 100644 index 0000000..360672b --- /dev/null +++ b/src/main/kotlin/app/termora/terminal/panel/TerminalWriter.kt @@ -0,0 +1,46 @@ +package app.termora.terminal.panel + +import java.nio.ByteBuffer +import java.nio.charset.Charset +import javax.swing.JComponent + +interface TerminalWriter { + + /** + * 挂载 + */ + fun onMounted(c: JComponent) + + /** + * 将数据写入 + */ + fun write(request: WriteRequest) + + /** + * 重置大小 + */ + fun resize(rows: Int, cols: Int) + + /** + * 字符集 + */ + fun getCharset(): Charset = Charsets.UTF_8 + + class WriteRequest private constructor(val buffer: ByteArray) { + + companion object { + fun fromBytes(bytes: ByteArray): WriteRequest { + return WriteRequest(bytes) + } + + fun fromBytes(buffer: ByteArray, offset: Int, len: Int): WriteRequest { + return WriteRequest(buffer.copyOfRange(offset, offset + len)) + } + + fun fromInt(buffer: Int): WriteRequest { + return fromBytes(ByteBuffer.allocate(Integer.BYTES).putInt(buffer).flip().array()) + } + + } + } +} \ No newline at end of file diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index 5abf3ba..393b695 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -258,12 +258,10 @@ termora.snippet=Snippet termora.snippet.title=Snippets # Tools -termora.tools.multiple=Send commands to multiple sessions +termora.tools.multiple=Send command to the current window sessions # Transport termora.transport.local=Local -termora.transport.parent-folder=Parent Folder -termora.transport.show-hidden-files=Show hidden files termora.transport.file-already-exists=The file {0} already exists termora.transport.bookmarks=Bookmarks Manager @@ -272,7 +270,6 @@ termora.transport.bookmarks.down=Down termora.transport.table.filename=Filename termora.transport.table.type=Type -termora.transport.table.type.folder=${termora.welcome.contextmenu.new.folder} termora.transport.table.type.symbolic-link=Symbolic Link termora.transport.table.size=Size termora.transport.table.modified-time=Modified diff --git a/src/main/resources/i18n/messages_zh_CN.properties b/src/main/resources/i18n/messages_zh_CN.properties index a10f2fe..bdf5d2d 100644 --- a/src/main/resources/i18n/messages_zh_CN.properties +++ b/src/main/resources/i18n/messages_zh_CN.properties @@ -200,7 +200,7 @@ termora.keymgr.ssh-copy-id.failed=复制失败 termora.keymgr.ssh-copy-id.end=复制公钥结束 # Tools -termora.tools.multiple=将命令发送到所有会话 +termora.tools.multiple=将命令发送到当前窗口会话 @@ -254,8 +254,6 @@ termora.snippet.title=代码片段 # Transport termora.transport.local=本机 -termora.transport.parent-folder=父文件夹 -termora.transport.show-hidden-files=显示隐藏文件 termora.transport.file-already-exists=文件 {0} 已存在 termora.transport.bookmarks=书签管理 diff --git a/src/main/resources/i18n/messages_zh_TW.properties b/src/main/resources/i18n/messages_zh_TW.properties index 7e9a766..b84cc58 100644 --- a/src/main/resources/i18n/messages_zh_TW.properties +++ b/src/main/resources/i18n/messages_zh_TW.properties @@ -197,7 +197,7 @@ termora.keymgr.ssh-copy-id.failed=複製失敗 termora.keymgr.ssh-copy-id.end=複製公鑰結束 # Tools -termora.tools.multiple=將指令傳送到所有會話 +termora.tools.multiple=將命令傳送到目前視窗會話 # Tabbed @@ -250,8 +250,6 @@ termora.snippet.title=程式碼片段 # Transport termora.transport.local=本機 -termora.transport.parent-folder=父資料夾 -termora.transport.show-hidden-files=顯示隱藏文件 termora.transport.file-already-exists=檔案 {0} 已存在 termora.transport.bookmarks=書籤管理