feat: send command to the current window sessions

This commit is contained in:
hstyi
2025-03-13 21:47:53 +08:00
committed by hstyi
parent aef44bd0da
commit 9a97b3a304
27 changed files with 252 additions and 163 deletions

View File

@@ -2,12 +2,6 @@ package app.termora
object Actions { object Actions {
/**
* 将命令发送到多个会话
*/
const val MULTIPLE = "MultipleAction"
/** /**
* 关键词高亮 * 关键词高亮
*/ */

View File

@@ -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
}
}

View File

@@ -1,7 +1,9 @@
package app.termora 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.Terminal
import app.termora.terminal.TerminalColor import app.termora.terminal.TerminalColor
import app.termora.terminal.TextStyle 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.TerminalDisplay
import app.termora.terminal.panel.TerminalPaintListener import app.termora.terminal.panel.TerminalPaintListener
import app.termora.terminal.panel.TerminalPanel import app.termora.terminal.panel.TerminalPanel
import org.apache.commons.lang3.StringUtils
import java.awt.Color import java.awt.Color
import java.awt.Graphics import java.awt.Graphics
import java.util.*
class MultipleTerminalListener : TerminalPaintListener { class MultipleTerminalListener : TerminalPaintListener {
override fun after( override fun after(
@@ -21,9 +25,9 @@ class MultipleTerminalListener : TerminalPaintListener {
terminalDisplay: TerminalDisplay, terminalDisplay: TerminalDisplay,
terminal: Terminal terminal: Terminal
) { ) {
if (!ActionManager.getInstance().isSelected(Actions.MULTIPLE)) { val windowScope = AnActionEvent(terminalPanel, StringUtils.EMPTY, EventObject(terminalPanel))
return .getData(DataProviders.WindowScope) ?: return
} if (!MultipleAction.getInstance(windowScope).isSelected) return
val oldFont = g.font val oldFont = g.font
val colorPalette = terminal.getTerminalModel().getColorPalette() val colorPalette = terminal.getTerminalModel().getColorPalette()

View File

@@ -19,7 +19,8 @@ class PtyConnectorFactory : Disposable {
companion object { companion object {
private val log = LoggerFactory.getLogger(PtyConnectorFactory::class.java) private val log = LoggerFactory.getLogger(PtyConnectorFactory::class.java)
fun getInstance(): PtyConnectorFactory { 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 { fun decorate(ptyConnector: PtyConnector): PtyConnector {
// 集成转发如果PtyConnector支持转发那么应该在当前注释行前面代理 //
val multiplePtyConnector = MultiplePtyConnector(ptyConnector) val macroPtyConnector = MacroPtyConnector(ptyConnector)
// 宏应该在转发前面执行,不然会导致重复录制
val macroPtyConnector = MacroPtyConnector(multiplePtyConnector)
// 集成自动删除 // 集成自动删除
val autoRemovePtyConnector = AutoRemovePtyConnector(macroPtyConnector) val autoRemovePtyConnector = AutoRemovePtyConnector(macroPtyConnector)
ptyConnectors.add(autoRemovePtyConnector) ptyConnectors.add(autoRemovePtyConnector)
return autoRemovePtyConnector return autoRemovePtyConnector
} }
fun getPtyConnectors(): List<PtyConnector> {
return ptyConnectors
}
private inner class AutoRemovePtyConnector(connector: PtyConnector) : PtyConnectorDelegate(connector) { private inner class AutoRemovePtyConnector(connector: PtyConnector) : PtyConnectorDelegate(connector) {
override fun close() { override fun close() {
ptyConnectors.remove(this) ptyConnectors.remove(this)

View File

@@ -23,16 +23,9 @@ abstract class PtyHostTerminalTab(
private var readerJob: Job? = null private var readerJob: Job? = null
private val ptyConnectorDelegate = PtyConnectorDelegate() private val ptyConnectorDelegate = PtyConnectorDelegate()
protected val terminalPanel = TerminalPanelFactory.getInstance().createTerminalPanel(terminal, ptyConnectorDelegate)
private val terminalPanelFactory = TerminalPanelFactory.getInstance()
protected val terminalPanel = terminalPanelFactory.createTerminalPanel(terminal, ptyConnectorDelegate)
.apply { Disposer.register(this@PtyHostTerminalTab, this) }
protected val ptyConnectorFactory get() = PtyConnectorFactory.getInstance() protected val ptyConnectorFactory get() = PtyConnectorFactory.getInstance()
init {
terminal.getTerminalModel().setData(DataKey.PtyConnector, ptyConnectorDelegate)
}
override fun start() { override fun start() {
coroutineScope.launch(Dispatchers.IO) { coroutineScope.launch(Dispatchers.IO) {
@@ -122,10 +115,9 @@ abstract class PtyHostTerminalTab(
override fun dispose() { override fun dispose() {
stop() stop()
terminalPanel Disposer.dispose(terminalPanel)
super.dispose() super.dispose()
if (log.isInfoEnabled) { if (log.isInfoEnabled) {
log.info("Host: {} disposed", host.name) log.info("Host: {} disposed", host.name)
} }
@@ -141,6 +133,8 @@ abstract class PtyHostTerminalTab(
override fun <T : Any> getData(dataKey: DataKey<T>): T? { override fun <T : Any> getData(dataKey: DataKey<T>): T? {
if (dataKey == DataProviders.TerminalPanel) { if (dataKey == DataProviders.TerminalPanel) {
return terminalPanel as T? return terminalPanel as T?
} else if (dataKey == DataProviders.TerminalWriter) {
return terminalPanel.getData(DataKey.TerminalWriter) as T?
} }
return super.getData(dataKey) return super.getData(dataKey)
} }

View File

@@ -1,14 +1,21 @@
package app.termora 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.highlight.KeywordHighlightPaintListener
import app.termora.terminal.DataKey
import app.termora.terminal.PtyConnector import app.termora.terminal.PtyConnector
import app.termora.terminal.Terminal import app.termora.terminal.Terminal
import app.termora.terminal.panel.TerminalHyperlinkPaintListener import app.termora.terminal.panel.TerminalHyperlinkPaintListener
import app.termora.terminal.panel.TerminalPanel import app.termora.terminal.panel.TerminalPanel
import app.termora.terminal.panel.TerminalWriter
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.apache.commons.lang3.StringUtils
import java.awt.event.ComponentEvent import java.awt.event.ComponentEvent
import java.awt.event.ComponentListener import java.awt.event.ComponentListener
import java.nio.charset.Charset
import java.util.*
import javax.swing.JComponent
import javax.swing.SwingUtilities import javax.swing.SwingUtilities
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
@@ -17,8 +24,6 @@ class TerminalPanelFactory : Disposable {
companion object { companion object {
private val Factory = DataKey(TerminalPanelFactory::class)
fun getInstance(): TerminalPanelFactory { fun getInstance(): TerminalPanelFactory {
return ApplicationScope.forApplicationScope() return ApplicationScope.forApplicationScope()
.getOrCreate(TerminalPanelFactory::class) { TerminalPanelFactory() } .getOrCreate(TerminalPanelFactory::class) { TerminalPanelFactory() }
@@ -32,17 +37,15 @@ class TerminalPanelFactory : Disposable {
fun createTerminalPanel(terminal: Terminal, ptyConnector: PtyConnector): TerminalPanel { 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(MultipleTerminalListener())
terminalPanel.addTerminalPaintListener(KeywordHighlightPaintListener.getInstance()) terminalPanel.addTerminalPaintListener(KeywordHighlightPaintListener.getInstance())
terminalPanel.addTerminalPaintListener(TerminalHyperlinkPaintListener.getInstance()) terminalPanel.addTerminalPaintListener(TerminalHyperlinkPaintListener.getInstance())
terminal.getTerminalModel().setData(Factory, this)
Disposer.register(terminalPanel, object : Disposable { Disposer.register(terminalPanel, object : Disposable {
override fun dispose() { override fun dispose() {
if (terminal.getTerminalModel().hasData(Factory)) { removeTerminalPanel(terminalPanel)
terminal.getTerminalModel().getData(Factory).removeTerminalPanel(terminalPanel)
}
} }
}) })
@@ -70,13 +73,12 @@ class TerminalPanelFactory : Disposable {
} }
} }
fun removeTerminalPanel(terminalPanel: TerminalPanel) { private fun removeTerminalPanel(terminalPanel: TerminalPanel) {
terminalPanels.remove(terminalPanel) terminalPanels.remove(terminalPanel)
} }
fun addTerminalPanel(terminalPanel: TerminalPanel) { private fun addTerminalPanel(terminalPanel: TerminalPanel) {
terminalPanels.add(terminalPanel) terminalPanels.add(terminalPanel)
terminalPanel.terminal.getTerminalModel().setData(Factory, this)
} }
private class Painter : Disposable { 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()
}
}
} }

View File

@@ -35,7 +35,7 @@ class TermoraFrame : JFrame(), DataProvider {
private val windowScope = ApplicationScope.forWindowScope(this) private val windowScope = ApplicationScope.forWindowScope(this)
private val titleBar = LogicCustomTitleBar.createCustomTitleBar(this) private val titleBar = LogicCustomTitleBar.createCustomTitleBar(this)
private val tabbedPane = MyTabbedPane() 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 terminalTabbed = TerminalTabbed(windowScope, toolbar, tabbedPane)
private val isWindowDecorationsSupported by lazy { JBR.isWindowDecorationsSupported() } private val isWindowDecorationsSupported by lazy { JBR.isWindowDecorationsSupported() }
private val dataProviderSupport = DataProviderSupport() private val dataProviderSupport = DataProviderSupport()

View File

@@ -1,10 +1,7 @@
package app.termora package app.termora
import app.termora.Application.ohMyJson import app.termora.Application.ohMyJson
import app.termora.actions.ActionManager import app.termora.actions.*
import app.termora.actions.AnAction
import app.termora.actions.AnActionEvent
import app.termora.actions.SettingsAction
import app.termora.findeverywhere.FindEverywhereAction import app.termora.findeverywhere.FindEverywhereAction
import app.termora.snippet.SnippetAction import app.termora.snippet.SnippetAction
import com.formdev.flatlaf.extras.components.FlatTabbedPane import com.formdev.flatlaf.extras.components.FlatTabbedPane
@@ -27,6 +24,7 @@ data class ToolBarAction(
) )
class TermoraToolBar( class TermoraToolBar(
private val windowScope: WindowScope,
private val titleBar: WindowDecorations.CustomTitleBar, private val titleBar: WindowDecorations.CustomTitleBar,
private val tabbedPane: FlatTabbedPane private val tabbedPane: FlatTabbedPane
) { ) {
@@ -49,7 +47,7 @@ class TermoraToolBar(
ToolBarAction(Actions.MACRO, true), ToolBarAction(Actions.MACRO, true),
ToolBarAction(Actions.KEYWORD_HIGHLIGHT, true), ToolBarAction(Actions.KEYWORD_HIGHLIGHT, true),
ToolBarAction(Actions.KEY_MANAGER, true), ToolBarAction(Actions.KEY_MANAGER, true),
ToolBarAction(Actions.MULTIPLE, true), ToolBarAction(MultipleAction.MULTIPLE, true),
ToolBarAction(FindEverywhereAction.FIND_EVERYWHERE, true), ToolBarAction(FindEverywhereAction.FIND_EVERYWHERE, true),
ToolBarAction(SettingsAction.SETTING, true), ToolBarAction(SettingsAction.SETTING, true),
) )
@@ -126,8 +124,13 @@ class TermoraToolBar(
// 获取显示的Action如果不是 false 那么就是显示出来 // 获取显示的Action如果不是 false 那么就是显示出来
for (action in getActions()) { for (action in getActions()) {
if (action.visible) { if (action.visible) {
actionManager.getAction(action.id)?.let { val ac = actionManager.getAction(action.id)
toolbar.add(actionContainerFactory.createButton(it)) if (ac == null) {
if (action.id == MultipleAction.MULTIPLE) {
toolbar.add(actionContainerFactory.createButton(MultipleAction.getInstance(windowScope)))
}
} else {
toolbar.add(actionContainerFactory.createButton(ac))
} }
} }
} }

View File

@@ -29,7 +29,6 @@ class ActionManager : org.jdesktop.swingx.action.ActionManager() {
addAction(NewWindowAction.NEW_WINDOW, NewWindowAction()) addAction(NewWindowAction.NEW_WINDOW, NewWindowAction())
addAction(FindEverywhereAction.FIND_EVERYWHERE, FindEverywhereAction()) addAction(FindEverywhereAction.FIND_EVERYWHERE, FindEverywhereAction())
addAction(Actions.MULTIPLE, MultipleAction())
addAction(Actions.APP_UPDATE, AppUpdateAction.getInstance()) addAction(Actions.APP_UPDATE, AppUpdateAction.getInstance())
addAction(Actions.KEYWORD_HIGHLIGHT, KeywordHighlightAction()) addAction(Actions.KEYWORD_HIGHLIGHT, KeywordHighlightAction())
addAction(Actions.TERMINAL_LOGGER, TerminalLoggerAction()) addAction(Actions.TERMINAL_LOGGER, TerminalLoggerAction())

View File

@@ -5,7 +5,7 @@ import app.termora.terminal.DataKey
object DataProviders { 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 get() = DataKey.PtyConnector val TerminalWriter get() = DataKey.TerminalWriter
val TabbedPane = DataKey(app.termora.MyTabbedPane::class) val TabbedPane = DataKey(app.termora.MyTabbedPane::class)
val TerminalTabbed = DataKey(app.termora.TerminalTabbed::class) val TerminalTabbed = DataKey(app.termora.TerminalTabbed::class)

View File

@@ -3,11 +3,25 @@ package app.termora.actions
import app.termora.I18n import app.termora.I18n
import app.termora.Icons import app.termora.Icons
import app.termora.TerminalPanelFactory import app.termora.TerminalPanelFactory
import app.termora.WindowScope
class MultipleAction : AnAction( class MultipleAction private constructor() : AnAction(
I18n.getString("termora.tools.multiple"), I18n.getString("termora.tools.multiple"),
Icons.vcs Icons.vcs
) { ) {
companion object {
/**
* 将命令发送到多个会话
*/
const val MULTIPLE = "MultipleAction"
fun getInstance(windowScope: WindowScope): MultipleAction {
return windowScope.getOrCreate(MultipleAction::class) { MultipleAction() }
}
}
init { init {
setStateAction() setStateAction()
} }

View File

@@ -3,6 +3,7 @@ package app.termora.findeverywhere
import app.termora.DialogWrapper import app.termora.DialogWrapper
import app.termora.DynamicColor import app.termora.DynamicColor
import app.termora.I18n import app.termora.I18n
import app.termora.WindowScope
import app.termora.actions.AnAction import app.termora.actions.AnAction
import app.termora.actions.AnActionEvent import app.termora.actions.AnActionEvent
import app.termora.macro.MacroFindEverywhereProvider import app.termora.macro.MacroFindEverywhereProvider
@@ -18,7 +19,7 @@ import javax.swing.*
import javax.swing.event.DocumentEvent import javax.swing.event.DocumentEvent
import javax.swing.event.DocumentListener 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 searchTextField = FlatTextField()
private val model = DefaultListModel<FindEverywhereResult>() private val model = DefaultListModel<FindEverywhereResult>()
private val resultList = FindEverywhereXList(model) private val resultList = FindEverywhereXList(model)
@@ -26,7 +27,7 @@ class FindEverywhere(owner: Window) : DialogWrapper(owner) {
private val providers = mutableListOf<FindEverywhereProvider>( private val providers = mutableListOf<FindEverywhereProvider>(
BasicFilterFindEverywhereProvider(QuickCommandFindEverywhereProvider()), BasicFilterFindEverywhereProvider(QuickCommandFindEverywhereProvider()),
BasicFilterFindEverywhereProvider(SettingsFindEverywhereProvider()), BasicFilterFindEverywhereProvider(SettingsFindEverywhereProvider()),
BasicFilterFindEverywhereProvider(QuickActionsFindEverywhereProvider()), BasicFilterFindEverywhereProvider(QuickActionsFindEverywhereProvider(windowScope)),
BasicFilterFindEverywhereProvider(MacroFindEverywhereProvider()), BasicFilterFindEverywhereProvider(MacroFindEverywhereProvider()),
) )

View File

@@ -46,7 +46,7 @@ class FindEverywhereAction : AnAction(StringUtils.EMPTY, Icons.find) {
return return
} }
val dialog = FindEverywhere(owner) val dialog = FindEverywhere(owner, scope)
for (provider in FindEverywhereProvider.getFindEverywhereProviders(scope)) { for (provider in FindEverywhereProvider.getFindEverywhereProviders(scope)) {
dialog.registerProvider(provider) dialog.registerProvider(provider)
} }

View File

@@ -2,21 +2,32 @@ package app.termora.findeverywhere
import app.termora.Actions import app.termora.Actions
import app.termora.I18n import app.termora.I18n
import app.termora.WindowScope
import app.termora.actions.MultipleAction
import org.jdesktop.swingx.action.ActionManager import org.jdesktop.swingx.action.ActionManager
class QuickActionsFindEverywhereProvider : FindEverywhereProvider { class QuickActionsFindEverywhereProvider(private val windowScope: WindowScope) : FindEverywhereProvider {
private val actions = listOf( private val actions = listOf(
Actions.KEY_MANAGER, Actions.KEY_MANAGER,
Actions.KEYWORD_HIGHLIGHT, Actions.KEYWORD_HIGHLIGHT,
Actions.MULTIPLE, MultipleAction.MULTIPLE,
) )
override fun find(pattern: String): List<FindEverywhereResult> { override fun find(pattern: String): List<FindEverywhereResult> {
val actionManager = ActionManager.getInstance() val actionManager = ActionManager.getInstance()
return actions val results = ArrayList<FindEverywhereResult>()
.mapNotNull { actionManager.getAction(it) } for (action in actions) {
.map { ActionFindEverywhereResult(it) } 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
} }

View File

@@ -6,8 +6,7 @@ import app.termora.Icons
import app.termora.actions.AnAction import app.termora.actions.AnAction
import app.termora.actions.AnActionEvent import app.termora.actions.AnActionEvent
import app.termora.terminal.ControlCharacters import app.termora.terminal.ControlCharacters
import app.termora.terminal.DataKey import app.termora.terminal.panel.TerminalWriter
import app.termora.terminal.Terminal
class SnippetAction private constructor() : AnAction(I18n.getString("termora.snippet.title"), Icons.codeSpan) { class SnippetAction private constructor() : AnAction(I18n.getString("termora.snippet.title"), Icons.codeSpan) {
companion object { 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 if (snippet.type != SnippetType.Snippet) return
val terminalModel = terminal.getTerminalModel()
val map = mapOf( val map = mapOf(
"\\r" to ControlCharacters.CR, "\\r" to ControlCharacters.CR,
"\\n" to ControlCharacters.LF, "\\n" to ControlCharacters.LF,
@@ -35,13 +33,10 @@ class SnippetAction private constructor() : AnAction(I18n.getString("termora.sni
"\\b" to ControlCharacters.BS, "\\b" to ControlCharacters.BS,
) )
if (terminalModel.hasData(DataKey.PtyConnector)) {
var text = snippet.snippet var text = snippet.snippet
for (e in map.entries) { for (e in map.entries) {
text = text.replace(e.key, e.value.toString()) text = text.replace(e.key, e.value.toString())
} }
val ptyConnector = terminalModel.getData(DataKey.PtyConnector) writer.write(TerminalWriter.WriteRequest.fromBytes(text.toByteArray(writer.getCharset())))
ptyConnector.write(text.toByteArray(ptyConnector.getCharset()))
}
} }
} }

View File

@@ -4,9 +4,12 @@ import app.termora.*
import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.StringUtils
import java.awt.Dimension import java.awt.Dimension
import java.awt.Window import java.awt.Window
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.BorderFactory import javax.swing.BorderFactory
import javax.swing.JComponent import javax.swing.JComponent
import javax.swing.JScrollPane import javax.swing.JScrollPane
import javax.swing.SwingUtilities
class SnippetTreeDialog(owner: Window) : DialogWrapper(owner) { class SnippetTreeDialog(owner: Window) : DialogWrapper(owner) {
private val snippetTree = SnippetTree() private val snippetTree = SnippetTree()
@@ -23,6 +26,15 @@ class SnippetTreeDialog(owner: Window) : DialogWrapper(owner) {
setLocationRelativeTo(null) setLocationRelativeTo(null)
init() 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 { Disposer.register(disposable, object : Disposable {
override fun dispose() { override fun dispose() {

View File

@@ -1,6 +1,7 @@
package app.termora.terminal package app.termora.terminal
import app.termora.assertEventDispatchThread import app.termora.assertEventDispatchThread
import app.termora.terminal.panel.TerminalWriter
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@@ -476,20 +477,20 @@ class ControlSequenceIntroducerProcessor(terminal: Terminal, reader: TerminalRea
return return
} }
if (!terminalModel.hasData(DataKey.PtyConnector)) { if (!terminalModel.hasData(DataKey.TerminalWriter)) {
return return
} }
val ptyConnector = terminalModel.getData(DataKey.PtyConnector) val writer = terminalModel.getData(DataKey.TerminalWriter)
val m = args.first() val m = args.first()
if (m == '6') { if (m == '6') {
val position = terminal.getCursorModel().getPosition() val position = terminal.getCursorModel().getPosition()
val bytes = "${ControlCharacters.ESC}[${position.y};${position.x}R".toByteArray(ptyConnector.getCharset()) val bytes = "${ControlCharacters.ESC}[${position.y};${position.x}R".toByteArray(writer.getCharset())
ptyConnector.write(bytes) writer.write(TerminalWriter.WriteRequest.fromBytes(bytes))
} else if (m == '5') { } else if (m == '5') {
val bytes = "${ControlCharacters.ESC}[0n".toByteArray(ptyConnector.getCharset()) val bytes = "${ControlCharacters.ESC}[0n".toByteArray(writer.getCharset())
ptyConnector.write(bytes) writer.write(TerminalWriter.WriteRequest.fromBytes(bytes))
} }
} }

View File

@@ -189,9 +189,9 @@ class DataKey<T : Any>(val clazz: KClass<T>) {
val CursorStyle = DataKey(app.termora.terminal.CursorStyle::class) 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)
} }
} }

View File

@@ -157,13 +157,13 @@ class FloatingToolbarPanel : FlatToolBar(), Disposable {
btn.addActionListener(object : AnAction() { btn.addActionListener(object : AnAction() {
override fun actionPerformed(evt: AnActionEvent) { override fun actionPerformed(evt: AnActionEvent) {
val tab = evt.getData(DataProviders.TerminalTab) ?: return 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) val dialog = SnippetTreeDialog(evt.window)
dialog.setLocationRelativeTo(btn) dialog.setLocationRelativeTo(btn)
dialog.setLocation(dialog.x, btn.locationOnScreen.y + height + 2) dialog.setLocation(dialog.x, btn.locationOnScreen.y + height + 2)
dialog.isVisible = true dialog.isVisible = true
val node = dialog.getSelectedNode() ?: return val node = dialog.getSelectedNode() ?: return
SnippetAction.getInstance().runSnippet(node.data, terminal) SnippetAction.getInstance().runSnippet(node.data, writer)
} }
}) })
return btn return btn

View File

@@ -34,7 +34,7 @@ import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds 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 { JPanel(BorderLayout()), DataProvider, Disposable, VisualWindowManager {
companion object { companion object {
@@ -117,6 +117,9 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect
private fun initView() { private fun initView() {
writer.onMounted(this)
isFocusable = true isFocusable = true
isRequestFocusEnabled = true isRequestFocusEnabled = true
focusTraversalKeysEnabled = false focusTraversalKeysEnabled = false
@@ -146,13 +149,13 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect
// DataProviders // DataProviders
dataProviderSupport.addData(DataProviders.TerminalPanel, this) dataProviderSupport.addData(DataProviders.TerminalPanel, this)
dataProviderSupport.addData(DataProviders.Terminal, terminal) dataProviderSupport.addData(DataProviders.Terminal, terminal)
dataProviderSupport.addData(DataProviders.PtyConnector, ptyConnector) dataProviderSupport.addData(DataProviders.TerminalWriter, writer)
dataProviderSupport.addData(FloatingToolbarPanel.FloatingToolbar, floatingToolbar) dataProviderSupport.addData(FloatingToolbarPanel.FloatingToolbar, floatingToolbar)
} }
private fun initEvents() { private fun initEvents() {
this.addKeyListener(TerminalPanelKeyAdapter(this, terminal, ptyConnector)) this.addKeyListener(TerminalPanelKeyAdapter(this, terminal, writer))
this.addFocusListener(object : FocusAdapter() { this.addFocusListener(object : FocusAdapter() {
override fun focusLost(e: FocusEvent) { override fun focusLost(e: FocusEvent) {
@@ -165,7 +168,7 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect
repaintImmediate() repaintImmediate()
} }
}) })
this.addComponentListener(TerminalPanelComponentAdapter(this, terminalDisplay, terminal, ptyConnector)) this.addComponentListener(TerminalPanelComponentAdapter(this, terminalDisplay, terminal, writer))
// 选中相关 // 选中相关
val mouseAdapter = TerminalPanelMouseSelectionAdapter(this, terminal) val mouseAdapter = TerminalPanelMouseSelectionAdapter(this, terminal)
@@ -177,7 +180,7 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect
this.addMouseListener(hyperlinkAdapter) this.addMouseListener(hyperlinkAdapter)
// 鼠标跟踪 // 鼠标跟踪
val trackingAdapter = TerminalPanelMouseTrackingAdapter(this, terminal, ptyConnector) val trackingAdapter = TerminalPanelMouseTrackingAdapter(this, terminal, writer)
this.addMouseListener(trackingAdapter) this.addMouseListener(trackingAdapter)
this.addMouseWheelListener(trackingAdapter) this.addMouseWheelListener(trackingAdapter)
@@ -329,7 +332,11 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect
// 输入法提交 // 输入法提交
if (committedCharacterCount > 0) { if (committedCharacterCount > 0) {
ptyConnector.write(sb.toString().toByteArray(ptyConnector.getCharset())) writer.write(
TerminalWriter.WriteRequest.fromBytes(
sb.toString().toByteArray(writer.getCharset())
)
)
} else { } else {
val breakIterator = BreakIterator.getCharacterInstance() val breakIterator = BreakIterator.getCharacterInstance()
val chars = mutableListOf<Char>() val chars = mutableListOf<Char>()
@@ -439,13 +446,17 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect
content = content.replace('\n', '\r') content = content.replace('\n', '\r')
if (terminal.getTerminalModel().getData(DataKey.BracketedPasteMode, false)) { if (terminal.getTerminalModel().getData(DataKey.BracketedPasteMode, false)) {
ptyConnector.write( writer.write(
"${ControlCharacters.ESC}[200~${content}${ControlCharacters.ESC}[201~".toByteArray( TerminalWriter.WriteRequest.fromBytes(
ptyConnector.getCharset() "${ControlCharacters.ESC}[200~${content}${ControlCharacters.ESC}[201~".toByteArray(writer.getCharset())
) )
) )
} else { } else {
ptyConnector.write(content.toByteArray(ptyConnector.getCharset())) writer.write(
TerminalWriter.WriteRequest.fromBytes(
content.toByteArray(writer.getCharset())
)
)
} }
terminal.getScrollingModel().scrollToRow( terminal.getScrollingModel().scrollToRow(

View File

@@ -1,6 +1,5 @@
package app.termora.terminal.panel package app.termora.terminal.panel
import app.termora.terminal.PtyConnector
import app.termora.terminal.Terminal import app.termora.terminal.Terminal
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.awt.event.ComponentAdapter import java.awt.event.ComponentAdapter
@@ -11,7 +10,7 @@ class TerminalPanelComponentAdapter(
private val terminalPanel: TerminalPanel, private val terminalPanel: TerminalPanel,
private val terminalDisplay: TerminalDisplay, private val terminalDisplay: TerminalDisplay,
private val terminal: Terminal, private val terminal: Terminal,
private val ptyConnector: PtyConnector private val writer: TerminalWriter
) : ComponentAdapter() { ) : ComponentAdapter() {
companion object { companion object {
@@ -30,7 +29,7 @@ class TerminalPanelComponentAdapter(
// 修改大小 // 修改大小
terminal.getTerminalModel().resize(rows = rows, cols = cols) terminal.getTerminalModel().resize(rows = rows, cols = cols)
// 修改终端大小 // 修改终端大小
ptyConnector.resize(rows, cols) writer.resize(rows, cols)
if (log.isTraceEnabled) { if (log.isTraceEnabled) {
log.trace("size: {} , cols: {} , rows: {}", terminalPanel.size, cols, rows) log.trace("size: {} , cols: {} , rows: {}", terminalPanel.size, cols, rows)

View File

@@ -2,7 +2,6 @@ package app.termora.terminal.panel
import app.termora.keymap.KeyShortcut import app.termora.keymap.KeyShortcut
import app.termora.keymap.KeymapManager import app.termora.keymap.KeymapManager
import app.termora.terminal.PtyConnector
import app.termora.terminal.Terminal import app.termora.terminal.Terminal
import com.formdev.flatlaf.util.SystemInfo import com.formdev.flatlaf.util.SystemInfo
import java.awt.event.InputEvent import java.awt.event.InputEvent
@@ -13,7 +12,7 @@ import javax.swing.KeyStroke
class TerminalPanelKeyAdapter( class TerminalPanelKeyAdapter(
private val terminalPanel: TerminalPanel, private val terminalPanel: TerminalPanel,
private val terminal: Terminal, private val terminal: Terminal,
private val ptyConnector: PtyConnector private val writer: TerminalWriter
) : KeyAdapter() { ) : KeyAdapter() {
private val activeKeymap get() = KeymapManager.getInstance().getActiveKeymap() private val activeKeymap get() = KeymapManager.getInstance().getActiveKeymap()
@@ -24,7 +23,13 @@ class TerminalPanelKeyAdapter(
} }
terminal.getSelectionModel().clearSelection() 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) terminal.getScrollingModel().scrollTo(Int.MAX_VALUE)
} }
@@ -47,7 +52,11 @@ class TerminalPanelKeyAdapter(
val encode = terminal.getKeyEncoder().encode(AWTTerminalKeyEvent(e)) val encode = terminal.getKeyEncoder().encode(AWTTerminalKeyEvent(e))
if (encode.isNotEmpty()) { 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 // https://github.com/TermoraDev/termora/issues/52
@@ -64,7 +73,11 @@ class TerminalPanelKeyAdapter(
terminal.getSelectionModel().clearSelection() terminal.getSelectionModel().clearSelection()
// 如果不为空表示已经发送过了,所以这里为空的时候再发送 // 如果不为空表示已经发送过了,所以这里为空的时候再发送
if (encode.isEmpty()) { 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) terminal.getScrollingModel().scrollTo(Int.MAX_VALUE)
} }

View File

@@ -11,7 +11,7 @@ import kotlin.math.abs
class TerminalPanelMouseTrackingAdapter( class TerminalPanelMouseTrackingAdapter(
private val terminalPanel: TerminalPanel, private val terminalPanel: TerminalPanel,
private val terminal: Terminal, private val terminal: Terminal,
private val ptyConnector: PtyConnector private val writer: TerminalWriter
) : MouseAdapter() { ) : MouseAdapter() {
companion object { companion object {
@@ -70,14 +70,18 @@ class TerminalPanelMouseTrackingAdapter(
val encode = terminal.getKeyEncoder() val encode = terminal.getKeyEncoder()
.encode(TerminalKeyEvent(if (e.wheelRotation < 0) KeyEvent.VK_UP else KeyEvent.VK_DOWN)) .encode(TerminalKeyEvent(if (e.wheelRotation < 0) KeyEvent.VK_UP else KeyEvent.VK_DOWN))
if (encode.isBlank()) return if (encode.isBlank()) return
val bytes = encode.toByteArray(ptyConnector.getCharset()) val bytes = encode.toByteArray(writer.getCharset())
for (i in 0 until abs(unitsToScroll)) { 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) { if (event.button == TerminalMouseButton.None) {
return 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() val sb = StringBuilder()
var charset = Charsets.UTF_8 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()) .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) { if (log.isTraceEnabled) {
log.trace("Send ESC{}", sb.substring(1)) log.trace("Send ESC{}", sb.substring(1))

View File

@@ -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())
}
}
}
}

View File

@@ -258,12 +258,10 @@ termora.snippet=Snippet
termora.snippet.title=Snippets termora.snippet.title=Snippets
# Tools # Tools
termora.tools.multiple=Send commands to multiple sessions termora.tools.multiple=Send command to the current window sessions
# Transport # Transport
termora.transport.local=Local 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.file-already-exists=The file {0} already exists
termora.transport.bookmarks=Bookmarks Manager termora.transport.bookmarks=Bookmarks Manager
@@ -272,7 +270,6 @@ termora.transport.bookmarks.down=Down
termora.transport.table.filename=Filename termora.transport.table.filename=Filename
termora.transport.table.type=Type 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.type.symbolic-link=Symbolic Link
termora.transport.table.size=Size termora.transport.table.size=Size
termora.transport.table.modified-time=Modified termora.transport.table.modified-time=Modified

View File

@@ -200,7 +200,7 @@ termora.keymgr.ssh-copy-id.failed=复制失败
termora.keymgr.ssh-copy-id.end=复制公钥结束 termora.keymgr.ssh-copy-id.end=复制公钥结束
# Tools # Tools
termora.tools.multiple=将命令发送到所有会话 termora.tools.multiple=将命令发送到当前窗口会话
@@ -254,8 +254,6 @@ termora.snippet.title=代码片段
# Transport # Transport
termora.transport.local=本机 termora.transport.local=本机
termora.transport.parent-folder=父文件夹
termora.transport.show-hidden-files=显示隐藏文件
termora.transport.file-already-exists=文件 {0} 已存在 termora.transport.file-already-exists=文件 {0} 已存在
termora.transport.bookmarks=书签管理 termora.transport.bookmarks=书签管理

View File

@@ -197,7 +197,7 @@ termora.keymgr.ssh-copy-id.failed=複製失敗
termora.keymgr.ssh-copy-id.end=複製公鑰結束 termora.keymgr.ssh-copy-id.end=複製公鑰結束
# Tools # Tools
termora.tools.multiple=令傳送到所有會話 termora.tools.multiple=令傳送到目前視窗會話
# Tabbed # Tabbed
@@ -250,8 +250,6 @@ termora.snippet.title=程式碼片段
# Transport # Transport
termora.transport.local=本機 termora.transport.local=本機
termora.transport.parent-folder=父資料夾
termora.transport.show-hidden-files=顯示隱藏文件
termora.transport.file-already-exists=檔案 {0} 已存在 termora.transport.file-already-exists=檔案 {0} 已存在
termora.transport.bookmarks=書籤管理 termora.transport.bookmarks=書籤管理