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 {
/**
* 将命令发送到多个会话
*/
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
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()

View File

@@ -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<PtyConnector> {
return ptyConnectors
}
private inner class AutoRemovePtyConnector(connector: PtyConnector) : PtyConnectorDelegate(connector) {
override fun close() {
ptyConnectors.remove(this)

View File

@@ -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 <T : Any> getData(dataKey: DataKey<T>): 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)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<FindEverywhereResult>()
private val resultList = FindEverywhereXList(model)
@@ -26,7 +27,7 @@ class FindEverywhere(owner: Window) : DialogWrapper(owner) {
private val providers = mutableListOf<FindEverywhereProvider>(
BasicFilterFindEverywhereProvider(QuickCommandFindEverywhereProvider()),
BasicFilterFindEverywhereProvider(SettingsFindEverywhereProvider()),
BasicFilterFindEverywhereProvider(QuickActionsFindEverywhereProvider()),
BasicFilterFindEverywhereProvider(QuickActionsFindEverywhereProvider(windowScope)),
BasicFilterFindEverywhereProvider(MacroFindEverywhereProvider()),
)

View File

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

View File

@@ -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<FindEverywhereResult> {
val actionManager = ActionManager.getInstance()
return actions
.mapNotNull { actionManager.getAction(it) }
.map { ActionFindEverywhereResult(it) }
val results = ArrayList<FindEverywhereResult>()
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
}

View File

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

View File

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

View File

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

View File

@@ -189,9 +189,9 @@ class DataKey<T : Any>(val clazz: KClass<T>) {
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() {
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

View File

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

View File

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

View File

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

View File

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

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
# 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

View File

@@ -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=书签管理

View File

@@ -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=書籤管理