Skip to content

Commit

Permalink
fix: emacs shift key
Browse files Browse the repository at this point in the history
  • Loading branch information
hstyi committed Feb 27, 2025
1 parent 483a777 commit eba85e6
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
136 changes: 127 additions & 9 deletions src/main/kotlin/app/termora/terminal/KeyEncoderImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 {
Expand All @@ -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")
Expand Down
36 changes: 26 additions & 10 deletions src/main/kotlin/app/termora/terminal/VisualTerminal.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package app.termora.terminal

import org.slf4j.LoggerFactory
import kotlin.math.min
import kotlin.math.max

open class VisualTerminal : Terminal {

Expand Down Expand Up @@ -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)
Expand All @@ -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")
}

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

Expand All @@ -176,20 +192,20 @@ 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")
}
TerminalState.READY
}

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")
}
Expand Down

0 comments on commit eba85e6

Please sign in to comment.