mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: send command to the current window sessions
This commit is contained in:
@@ -2,12 +2,6 @@ package app.termora
|
||||
|
||||
object Actions {
|
||||
|
||||
|
||||
/**
|
||||
* 将命令发送到多个会话
|
||||
*/
|
||||
const val MULTIPLE = "MultipleAction"
|
||||
|
||||
/**
|
||||
* 关键词高亮
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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())))
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
46
src/main/kotlin/app/termora/terminal/panel/TerminalWriter.kt
Normal file
46
src/main/kotlin/app/termora/terminal/panel/TerminalWriter.kt
Normal 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())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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=书签管理
|
||||
|
||||
@@ -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=書籤管理
|
||||
|
||||
Reference in New Issue
Block a user