From eba85e63483948e2532421226416b4e3aefb73b9 Mon Sep 17 00:00:00 2001 From: hstyi Date: Thu, 27 Feb 2025 20:31:41 +0800 Subject: [PATCH] fix: emacs shift key --- .../ControlSequenceIntroducerProcessor.kt | 2 +- .../terminal/EscapeSequenceProcessor.kt | 2 +- .../app/termora/terminal/KeyEncoderImpl.kt | 136 ++++++++++++++++-- .../app/termora/terminal/VisualTerminal.kt | 36 +++-- 4 files changed, 155 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/app/termora/terminal/ControlSequenceIntroducerProcessor.kt b/src/main/kotlin/app/termora/terminal/ControlSequenceIntroducerProcessor.kt index 3acdfdfe..a08bb593 100644 --- a/src/main/kotlin/app/termora/terminal/ControlSequenceIntroducerProcessor.kt +++ b/src/main/kotlin/app/termora/terminal/ControlSequenceIntroducerProcessor.kt @@ -384,7 +384,7 @@ class ControlSequenceIntroducerProcessor(terminal: Terminal, reader: TerminalRea val mode = args.toInt(0) if (mode == 0) { val x = terminal.getCursorModel().getPosition().x - terminal.getTabulator().clearTabStop(x) + terminal.getTabulator().clearTabStop(x - 1) if (log.isDebugEnabled) { log.debug("Tab Clear (TBC). clearTabStop($x)") } diff --git a/src/main/kotlin/app/termora/terminal/EscapeSequenceProcessor.kt b/src/main/kotlin/app/termora/terminal/EscapeSequenceProcessor.kt index 93bc2843..63c8015b 100644 --- a/src/main/kotlin/app/termora/terminal/EscapeSequenceProcessor.kt +++ b/src/main/kotlin/app/termora/terminal/EscapeSequenceProcessor.kt @@ -171,7 +171,7 @@ class EscapeSequenceProcessor(terminal: Terminal, reader: TerminalReader) : Abst } } else { val x = terminal.getCursorModel().getPosition().x - terminal.getTabulator().setTabStop(x) + terminal.getTabulator().setTabStop(x - 1) if (log.isDebugEnabled) { log.debug("Horizontal Tab Set (HTS). col: $x") } diff --git a/src/main/kotlin/app/termora/terminal/KeyEncoderImpl.kt b/src/main/kotlin/app/termora/terminal/KeyEncoderImpl.kt index 88576e0f..9f5949f8 100644 --- a/src/main/kotlin/app/termora/terminal/KeyEncoderImpl.kt +++ b/src/main/kotlin/app/termora/terminal/KeyEncoderImpl.kt @@ -55,14 +55,14 @@ open class KeyEncoderImpl(private val terminal: Terminal) : KeyEncoder, DataList putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F2), encode = "${ControlCharacters.ESC}OQ") putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F3), encode = "${ControlCharacters.ESC}OR") putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F4), encode = "${ControlCharacters.ESC}OS") - putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F5), encode = "${ControlCharacters.ESC}[15~"); - putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F6), encode = "${ControlCharacters.ESC}[17~"); - putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F7), encode = "${ControlCharacters.ESC}[18~"); - putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F8), encode = "${ControlCharacters.ESC}[19~"); - putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F9), encode = "${ControlCharacters.ESC}[20~"); - putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F10), encode = "${ControlCharacters.ESC}[21~"); - putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F11), encode = "${ControlCharacters.ESC}[23~"); - putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F12), encode = "${ControlCharacters.ESC}[24~"); + putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F5), encode = "${ControlCharacters.ESC}[15~") + putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F6), encode = "${ControlCharacters.ESC}[17~") + putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F7), encode = "${ControlCharacters.ESC}[18~") + putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F8), encode = "${ControlCharacters.ESC}[19~") + putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F9), encode = "${ControlCharacters.ESC}[20~") + putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F10), encode = "${ControlCharacters.ESC}[21~") + putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F11), encode = "${ControlCharacters.ESC}[23~") + putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F12), encode = "${ControlCharacters.ESC}[24~") terminal.getTerminalModel().addDataListener(object : DataListener { override fun onChanged(key: DataKey<*>, data: Any) { @@ -73,7 +73,40 @@ open class KeyEncoderImpl(private val terminal: Terminal) : KeyEncoder, DataList } override fun encode(event: TerminalKeyEvent): String { - return mapping[event] ?: nothing + if (mapping.containsKey(event)) { + return mapping.getValue(event) + } + + var bytes = (mapping[TerminalKeyEvent(event.keyCode, 0)] ?: return nothing).toByteArray() + + if (alwaysSendEsc(event.keyCode) && (event.modifiers and TerminalEvent.ALT_MASK) != 0) { + bytes = insertCodeAt(bytes, makeCode(ControlCharacters.ESC.code), 0) + return String(bytes) + } + + if (alwaysSendEsc(event.keyCode) && (event.modifiers and TerminalEvent.META_MASK) != 0) { + bytes = insertCodeAt(bytes, makeCode(ControlCharacters.ESC.code), 0) + return String(bytes) + } + + if (isCursorKey(event.keyCode) || isFunctionKey(event.keyCode)) { + bytes = getCodeWithModifiers(bytes, event.modifiers) + return String(bytes) + } + + return String(bytes) + } + + private fun makeCode(vararg bytesAsInt: Int): ByteArray { + val bytes = ByteArray(bytesAsInt.size) + for ((i, byteAsInt) in bytesAsInt.withIndex()) { + bytes[i] = byteAsInt.toByte() + } + return bytes + } + + private fun alwaysSendEsc(key: Int): Boolean { + return isCursorKey(key) || key == '\b'.code } override fun getTerminal(): Terminal { @@ -84,6 +117,91 @@ open class KeyEncoderImpl(private val terminal: Terminal) : KeyEncoder, DataList mapping[event] = encode } + + /** + * Refer to section PC-Style Function Keys in http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + */ + private fun getCodeWithModifiers(bytes: ByteArray, modifiers: Int): ByteArray { + val code = modifiersToCode(modifiers) + + if (code > 0 && bytes.size > 2) { + // SS3 needs to become CSI. + if (bytes[0].toInt() == ControlCharacters.ESC.code && bytes[1] == 'O'.code.toByte()) { + bytes[1] = '['.code.toByte() + } + // If the control sequence has no parameters, it needs a default parameter. + // Either way it also needs a semicolon separator. + val prefix = if (bytes.size == 3) "1;" else ";" + return insertCodeAt( + bytes, + (prefix + code).toByteArray(), + bytes.size - 1 + ) + } + + return bytes + } + + private fun insertCodeAt(bytes: ByteArray, code: ByteArray, at: Int): ByteArray { + val res = ByteArray(bytes.size + code.size) + System.arraycopy(bytes, 0, res, 0, bytes.size) + System.arraycopy(bytes, at, res, at + code.size, bytes.size - at) + System.arraycopy(code, 0, res, at, code.size) + return res + } + + /** + * + * Code Modifiers + * ------+-------------------------- + * 2 | Shift + * 3 | Alt + * 4 | Shift + Alt + * 5 | Control + * 6 | Shift + Control + * 7 | Alt + Control + * 8 | Shift + Alt + Control + * 9 | Meta + * 10 | Meta + Shift + * 11 | Meta + Alt + * 12 | Meta + Alt + Shift + * 13 | Meta + Ctrl + * 14 | Meta + Ctrl + Shift + * 15 | Meta + Ctrl + Alt + * 16 | Meta + Ctrl + Alt + Shift + * ------+-------------------------- + * @param modifiers + * @return + */ + private fun modifiersToCode(modifiers: Int): Int { + var code = 0 + if ((modifiers and TerminalEvent.SHIFT_MASK) != 0) { + code = code or 1 + } + if ((modifiers and TerminalEvent.ALT_MASK) != 0) { + code = code or 2 + } + if ((modifiers and TerminalEvent.CTRL_MASK) != 0) { + code = code or 4 + } + if ((modifiers and TerminalEvent.META_MASK) != 0) { + code = code or 8 + } + return if (code != 0) code + 1 else 0 + } + + private fun isCursorKey(key: Int): Boolean { + return key == KeyEvent.VK_DOWN || key == KeyEvent.VK_UP + || key == KeyEvent.VK_LEFT || key == KeyEvent.VK_RIGHT + || key == KeyEvent.VK_HOME || key == KeyEvent.VK_END + } + + private fun isFunctionKey(key: Int): Boolean { + return key >= KeyEvent.VK_F1 && key <= KeyEvent.VK_F12 + || key == KeyEvent.VK_INSERT || key == KeyEvent.VK_DELETE + || key == KeyEvent.VK_PAGE_UP || key == KeyEvent.VK_PAGE_DOWN + } + fun arrowKeysApplicationSequences() { // Up putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_UP), encode = "${ControlCharacters.ESC}OA") diff --git a/src/main/kotlin/app/termora/terminal/VisualTerminal.kt b/src/main/kotlin/app/termora/terminal/VisualTerminal.kt index 07fdaa48..0de5165f 100644 --- a/src/main/kotlin/app/termora/terminal/VisualTerminal.kt +++ b/src/main/kotlin/app/termora/terminal/VisualTerminal.kt @@ -1,7 +1,7 @@ package app.termora.terminal import org.slf4j.LoggerFactory -import kotlin.math.min +import kotlin.math.max open class VisualTerminal : Terminal { @@ -119,6 +119,9 @@ open class VisualTerminal : Terminal { private class MyProcessor(private val terminal: Terminal, reader: TerminalReader) { private var state: ProcessorState = TerminalState.READY + private val document get() = terminal.getDocument() + private val cursorModel get() = terminal.getCursorModel() + private val terminalModel get() = terminal.getTerminalModel() companion object { private val log = LoggerFactory.getLogger(MyProcessor::class.java) @@ -135,7 +138,7 @@ private class MyProcessor(private val terminal: Terminal, reader: TerminalReader fun process(ch: Char) { if (log.isTraceEnabled) { - val position = terminal.getCursorModel().getPosition() + val position = cursorModel.getPosition() log.trace("process [${printChar(ch)}] , state: $state , position: $position") } @@ -155,16 +158,29 @@ private class MyProcessor(private val terminal: Terminal, reader: TerminalReader } ControlCharacters.CR -> { - terminal.getCursorModel().move(CursorMove.RowHome) + cursorModel.move(CursorMove.RowHome) TerminalState.READY } ControlCharacters.TAB -> { - val position = terminal.getCursorModel().getPosition() + val position = cursorModel.getPosition() // Next tab + 1,如果当前 x = 11,那么下一个就是 16,因为在 TerminalLineBuffer#writeTerminalLineChar 的时候会 - 1 会导致错乱一位 - var nextTab = terminal.getTabulator().nextTab(position.x) + 1 - nextTab = min(terminal.getTerminalModel().getCols(), nextTab) - terminal.getCursorModel().move(row = position.y, col = nextTab) + val nextTab = terminal.getTabulator().nextTab(position.x - 1) + 1 + val length = if (terminalModel.isAlternateScreenBuffer()) { + document.getCurrentTerminalLineBuffer() + .getLineAt(position.y - 1).getText().length + } else { + document.getCurrentTerminalLineBuffer() + .getScreenLineAt(position.y - 1) + .getText().length + } + + val x = max(position.x - 1, length) + if (x < nextTab) { + cursorModel.move(row = position.y, col = (position.x - 1) + (nextTab - x)) + } else { + cursorModel.move(row = position.y, col = nextTab) + } TerminalState.READY } @@ -176,12 +192,12 @@ private class MyProcessor(private val terminal: Terminal, reader: TerminalReader } ControlCharacters.BS -> { - terminal.getCursorModel().move(CursorMove.Left) + cursorModel.move(CursorMove.Left) TerminalState.READY } ControlCharacters.SI -> { - terminal.getTerminalModel().getData(DataKey.GraphicCharacterSet).use(Graphic.G0) + terminalModel.getData(DataKey.GraphicCharacterSet).use(Graphic.G0) if (log.isDebugEnabled) { log.debug("Use Graphic.G0") } @@ -189,7 +205,7 @@ private class MyProcessor(private val terminal: Terminal, reader: TerminalReader } ControlCharacters.SO -> { - terminal.getTerminalModel().getData(DataKey.GraphicCharacterSet).use(Graphic.G1) + terminalModel.getData(DataKey.GraphicCharacterSet).use(Graphic.G1) if (log.isDebugEnabled) { log.debug("Use Graphic.G1") }