fix: emacs shift key

This commit is contained in:
hstyi
2025-02-27 20:31:41 +08:00
committed by hstyi
parent 483a7772f4
commit eba85e6348
4 changed files with 155 additions and 21 deletions

View File

@@ -384,7 +384,7 @@ class ControlSequenceIntroducerProcessor(terminal: Terminal, reader: TerminalRea
val mode = args.toInt(0) val mode = args.toInt(0)
if (mode == 0) { if (mode == 0) {
val x = terminal.getCursorModel().getPosition().x val x = terminal.getCursorModel().getPosition().x
terminal.getTabulator().clearTabStop(x) terminal.getTabulator().clearTabStop(x - 1)
if (log.isDebugEnabled) { if (log.isDebugEnabled) {
log.debug("Tab Clear (TBC). clearTabStop($x)") log.debug("Tab Clear (TBC). clearTabStop($x)")
} }

View File

@@ -171,7 +171,7 @@ class EscapeSequenceProcessor(terminal: Terminal, reader: TerminalReader) : Abst
} }
} else { } else {
val x = terminal.getCursorModel().getPosition().x val x = terminal.getCursorModel().getPosition().x
terminal.getTabulator().setTabStop(x) terminal.getTabulator().setTabStop(x - 1)
if (log.isDebugEnabled) { if (log.isDebugEnabled) {
log.debug("Horizontal Tab Set (HTS). col: $x") log.debug("Horizontal Tab Set (HTS). col: $x")
} }

View File

@@ -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_F2), encode = "${ControlCharacters.ESC}OQ")
putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F3), encode = "${ControlCharacters.ESC}OR") 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_F4), encode = "${ControlCharacters.ESC}OS")
putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F5), encode = "${ControlCharacters.ESC}[15~"); 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_F6), encode = "${ControlCharacters.ESC}[17~")
putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F7), encode = "${ControlCharacters.ESC}[18~"); 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_F8), encode = "${ControlCharacters.ESC}[19~")
putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F9), encode = "${ControlCharacters.ESC}[20~"); 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_F10), encode = "${ControlCharacters.ESC}[21~")
putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_F11), encode = "${ControlCharacters.ESC}[23~"); 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_F12), encode = "${ControlCharacters.ESC}[24~")
terminal.getTerminalModel().addDataListener(object : DataListener { terminal.getTerminalModel().addDataListener(object : DataListener {
override fun onChanged(key: DataKey<*>, data: Any) { 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 { 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 { override fun getTerminal(): Terminal {
@@ -84,6 +117,91 @@ open class KeyEncoderImpl(private val terminal: Terminal) : KeyEncoder, DataList
mapping[event] = encode 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() { fun arrowKeysApplicationSequences() {
// Up // Up
putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_UP), encode = "${ControlCharacters.ESC}OA") putCode(TerminalKeyEvent(keyCode = KeyEvent.VK_UP), encode = "${ControlCharacters.ESC}OA")

View File

@@ -1,7 +1,7 @@
package app.termora.terminal package app.termora.terminal
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import kotlin.math.min import kotlin.math.max
open class VisualTerminal : Terminal { open class VisualTerminal : Terminal {
@@ -119,6 +119,9 @@ open class VisualTerminal : Terminal {
private class MyProcessor(private val terminal: Terminal, reader: TerminalReader) { private class MyProcessor(private val terminal: Terminal, reader: TerminalReader) {
private var state: ProcessorState = TerminalState.READY 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 { companion object {
private val log = LoggerFactory.getLogger(MyProcessor::class.java) 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) { fun process(ch: Char) {
if (log.isTraceEnabled) { if (log.isTraceEnabled) {
val position = terminal.getCursorModel().getPosition() val position = cursorModel.getPosition()
log.trace("process [${printChar(ch)}] , state: $state , position: $position") log.trace("process [${printChar(ch)}] , state: $state , position: $position")
} }
@@ -155,16 +158,29 @@ private class MyProcessor(private val terminal: Terminal, reader: TerminalReader
} }
ControlCharacters.CR -> { ControlCharacters.CR -> {
terminal.getCursorModel().move(CursorMove.RowHome) cursorModel.move(CursorMove.RowHome)
TerminalState.READY TerminalState.READY
} }
ControlCharacters.TAB -> { ControlCharacters.TAB -> {
val position = terminal.getCursorModel().getPosition() val position = cursorModel.getPosition()
// Next tab + 1如果当前 x = 11那么下一个就是 16因为在 TerminalLineBuffer#writeTerminalLineChar 的时候会 - 1 会导致错乱一位 // Next tab + 1如果当前 x = 11那么下一个就是 16因为在 TerminalLineBuffer#writeTerminalLineChar 的时候会 - 1 会导致错乱一位
var nextTab = terminal.getTabulator().nextTab(position.x) + 1 val nextTab = terminal.getTabulator().nextTab(position.x - 1) + 1
nextTab = min(terminal.getTerminalModel().getCols(), nextTab) val length = if (terminalModel.isAlternateScreenBuffer()) {
terminal.getCursorModel().move(row = position.y, col = nextTab) 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 TerminalState.READY
} }
@@ -176,12 +192,12 @@ private class MyProcessor(private val terminal: Terminal, reader: TerminalReader
} }
ControlCharacters.BS -> { ControlCharacters.BS -> {
terminal.getCursorModel().move(CursorMove.Left) cursorModel.move(CursorMove.Left)
TerminalState.READY TerminalState.READY
} }
ControlCharacters.SI -> { ControlCharacters.SI -> {
terminal.getTerminalModel().getData(DataKey.GraphicCharacterSet).use(Graphic.G0) terminalModel.getData(DataKey.GraphicCharacterSet).use(Graphic.G0)
if (log.isDebugEnabled) { if (log.isDebugEnabled) {
log.debug("Use Graphic.G0") log.debug("Use Graphic.G0")
} }
@@ -189,7 +205,7 @@ private class MyProcessor(private val terminal: Terminal, reader: TerminalReader
} }
ControlCharacters.SO -> { ControlCharacters.SO -> {
terminal.getTerminalModel().getData(DataKey.GraphicCharacterSet).use(Graphic.G1) terminalModel.getData(DataKey.GraphicCharacterSet).use(Graphic.G1)
if (log.isDebugEnabled) { if (log.isDebugEnabled) {
log.debug("Use Graphic.G1") log.debug("Use Graphic.G1")
} }