228 lines
6.7 KiB
Nim
228 lines
6.7 KiB
Nim
# keycodes.nim
|
|
|
|
import tables
|
|
import strutils
|
|
import terminal
|
|
import unicode
|
|
|
|
type
|
|
JaleKeycode* = enum
|
|
jkNull = 0,
|
|
jkCtrlA = 1, jkCtrlB, jkCtrlC, jkCtrlD, jkCtrlE,
|
|
jkCtrlF, jkCtrlG,
|
|
jkBackspace = 8, # ctrl+h = backspace
|
|
jkTab = 9, # ctrl+i = tab
|
|
jkCtrlJ = 10, jkCtrlK, jkCtrlL,
|
|
jkEnter = 13, # ctrl+m = enter
|
|
jkCtrlN = 14, jkCtrlO, jkCtrlP, jkCtrlQ,
|
|
jkCtrlR, jkCtrlS, jkCtrlT, jkCtrlU, jkCtrlV,
|
|
jkCtrlW, jkCtrlX, jkCtrlY, jkCtrlZ,
|
|
jkEscape = 27,
|
|
jkCtrlBackslash = 28,
|
|
jkCtrlRightBracket = 29,
|
|
# the rest are handled by escapeSeqs
|
|
jkAltBackspace,
|
|
jkLeft, jkRight, jkUp, jkDown,
|
|
jkCtrlLeft, jkCtrlRight, jkCtrlUp, jkCtrlDown,
|
|
# other 4 move keys
|
|
jkHome, jkEnd, jkPageUp, jkPageDown,
|
|
jkCtrlHome, jkCtrlEnd, jkCtrlPageUp, jkCtrlPageDown,
|
|
# special keys
|
|
jkDelete, jkInsert,
|
|
|
|
jkContinue # represents an unfinished escape sequence
|
|
|
|
var escapeSeqs*: Table[int, JaleKeycode]
|
|
|
|
proc defEscSeq(keys: seq[int], id: JaleKeycode) =
|
|
var result = 0
|
|
for key in keys:
|
|
result *= 256
|
|
result += key
|
|
if escapeSeqs.hasKey(result):
|
|
raise newException(Defect, "Duplicate escape sequence definition")
|
|
escapeSeqs[result] = id
|
|
|
|
block:
|
|
defEscSeq(@[127], jkAltBackspace)
|
|
|
|
when defined(windows):
|
|
defEscSeq(@[224], jkContinue)
|
|
|
|
# arrow keys
|
|
defEscSeq(@[224, 72], jkUp)
|
|
defEscSeq(@[224, 80], jkDown)
|
|
defEscSeq(@[224, 77], jkRight)
|
|
defEscSeq(@[224, 75], jkLeft)
|
|
# ctrl+arrow keys
|
|
defEscSeq(@[224, 141], jkCtrlUp)
|
|
defEscSeq(@[224, 145], jkCtrlDown)
|
|
defEscSeq(@[224, 116], jkCtrlRight)
|
|
defEscSeq(@[224, 115], jkCtrlLeft)
|
|
# moves
|
|
defEscSeq(@[224, 71], jkHome)
|
|
defEscSeq(@[224, 79], jkEnd)
|
|
defEscSeq(@[224, 73], jkPageUp)
|
|
defEscSeq(@[224, 81], jkPageDown)
|
|
# ctrl+moves
|
|
defEscSeq(@[224, 134], jkCtrlPageUp)
|
|
defEscSeq(@[224, 118], jkCtrlPageDown)
|
|
defEscSeq(@[224, 119], jkCtrlHome)
|
|
defEscSeq(@[224, 117], jkCtrlEnd)
|
|
|
|
# special keys
|
|
defEscSeq(@[224, 82], jkInsert)
|
|
defEscSeq(@[224, 83], jkDelete)
|
|
else:
|
|
# arrow keys
|
|
defEscSeq(@[27], jkContinue)
|
|
defEscSeq(@[27, 91], jkContinue)
|
|
defEscSeq(@[27, 91, 65], jkUp)
|
|
defEscSeq(@[27, 91, 66], jkDown)
|
|
defEscSeq(@[27, 91, 67], jkRight)
|
|
defEscSeq(@[27, 91, 68], jkLeft)
|
|
|
|
# ctrl+arrow keys
|
|
defEscSeq(@[27, 91, 49], jkContinue)
|
|
defEscSeq(@[27, 91, 49, 59], jkContinue)
|
|
defEscSeq(@[27, 91, 49, 59, 53], jkContinue) # ctrl
|
|
|
|
defEscSeq(@[27, 91, 49, 59, 50], jkContinue) # shift
|
|
|
|
defEscSeq(@[27, 91, 49, 59, 53, 65], jkCtrlUp) # ctrl
|
|
defEscSeq(@[27, 91, 49, 59, 53, 66], jkCtrlDown) # ctrl
|
|
defEscSeq(@[27, 91, 49, 59, 53, 67], jkCtrlRight) # ctrl
|
|
defEscSeq(@[27, 91, 49, 59, 53, 68], jkCtrlLeft) # ctrl
|
|
|
|
# urxvt
|
|
defEscSeq(@[27, 79], jkContinue)
|
|
defEscSeq(@[27, 79, 97], jkCtrlUp)
|
|
defEscSeq(@[27, 79, 98], jkCtrlDown)
|
|
defEscSeq(@[27, 79, 99], jkCtrlRight)
|
|
defEscSeq(@[27, 79, 100], jkCtrlLeft)
|
|
|
|
# other 4 move keys
|
|
defEscSeq(@[27, 91, 72], jkHome)
|
|
defEscSeq(@[27, 91, 70], jkEnd)
|
|
defEscSeq(@[27, 91, 54], jkContinue)
|
|
defEscSeq(@[27, 91, 53], jkContinue)
|
|
defEscSeq(@[27, 91, 53, 126], jkPageUp)
|
|
defEscSeq(@[27, 91, 54, 126], jkPageDown)
|
|
# alternative home/end for tty
|
|
defEscSeq(@[27, 91, 49, 126], jkHome)
|
|
defEscSeq(@[27, 91, 52], jkContinue)
|
|
defEscSeq(@[27, 91, 52, 126], jkEnd)
|
|
# urxvt
|
|
defEscSeq(@[27, 91, 55], jkContinue)
|
|
defEscSeq(@[27, 91, 56], jkContinue)
|
|
defEscSeq(@[27, 91, 55, 126], jkHome)
|
|
defEscSeq(@[27, 91, 56, 126], jkEnd)
|
|
|
|
# ctrl + fancy keys like pgup, pgdown, home, end
|
|
defEscSeq(@[27, 91, 53, 59], jkContinue)
|
|
defEscSeq(@[27, 91, 53, 59, 53], jkContinue)
|
|
defEscSeq(@[27, 91, 53, 59, 53, 126], jkCtrlPageUp)
|
|
defEscSeq(@[27, 91, 54, 59], jkContinue)
|
|
defEscSeq(@[27, 91, 54, 59, 53], jkContinue)
|
|
defEscSeq(@[27, 91, 54, 59, 53, 126], jkCtrlPageDown)
|
|
|
|
defEscSeq(@[27, 91, 49, 59, 53, 72], jkCtrlHome)
|
|
defEscSeq(@[27, 91, 49, 59, 53, 70], jkCtrlEnd)
|
|
|
|
# urxvt
|
|
defEscSeq(@[27, 91, 53, 94], jkCtrlPageUp)
|
|
defEscSeq(@[27, 91, 54, 94], jkCtrlPageDown)
|
|
defEscSeq(@[27, 91, 55, 94], jkCtrlHome)
|
|
defEscSeq(@[27, 91, 56, 94], jkCtrlEnd)
|
|
|
|
# other keys
|
|
defEscSeq(@[27, 91, 51], jkContinue)
|
|
defEscSeq(@[27, 91, 50], jkContinue)
|
|
defEscSeq(@[27, 91, 51, 126], jkDelete)
|
|
defEscSeq(@[27, 91, 50, 126], jkInsert)
|
|
|
|
type IsKeyUTF8Results = enum
|
|
ikValid, ikUnfinished, ikOverlong, ikInvalid2,
|
|
ikInvalid3, ikInvalid4, ikInvalidUnknown
|
|
|
|
func isContinuation(singleByte: uint8): bool =
|
|
singleByte shr 6 == 0b10
|
|
|
|
func isValidUTF8(key: int): IsKeyUTF8Results =
|
|
const L = sizeof(int)
|
|
var bytes: array[L, uint8]
|
|
var key = key
|
|
var i = L - 1
|
|
|
|
# to not make any assumptions about how int is
|
|
# stored in memory
|
|
while key > 0:
|
|
bytes[i] = (key mod 256).uint8
|
|
key = key div 256
|
|
dec i
|
|
|
|
# https://github.com/nim-lang/Nim/blob/version-1-6/lib/pure/unicode.nim#L166
|
|
i = 0
|
|
while i < L:
|
|
if bytes[i] <= 127:
|
|
inc i
|
|
elif bytes[i] shr 5 == 0b110:
|
|
# 2 long
|
|
if bytes[i] < 0xc2: return ikOverlong # overlong
|
|
elif i + 1 >= L: return ikUnfinished
|
|
elif bytes[i+1].isContinuation: i += 2
|
|
else: return ikInvalid2
|
|
elif bytes[i] shr 4 == 0b1110:
|
|
# 3 long
|
|
if i + 2 >= L: return ikUnfinished
|
|
elif bytes[i+1].isContinuation() and bytes[i+2].isContinuation():
|
|
i += 3
|
|
else: return ikInvalid3
|
|
elif bytes[i] shr 3 == 0b11110:
|
|
# 4 long
|
|
if i+3 >= L: return ikUnfinished
|
|
elif bytes[i+1].isContinuation() and
|
|
bytes[i+2].isContinuation() and
|
|
bytes[i+3].isContinuation():
|
|
i += 4
|
|
else: return ikInvalid4
|
|
else:
|
|
return ikInvalidUnknown
|
|
return ikValid
|
|
|
|
type
|
|
GetKeyResult* = enum
|
|
gkControl, gkChar
|
|
|
|
proc getKey*: (GetKeyResult, int) =
|
|
# TODO move away from int approach completely
|
|
var key = 0
|
|
var bytes: string
|
|
while true:
|
|
key *= 256
|
|
let newChar = getch()
|
|
key += newChar.int()
|
|
bytes &= newChar
|
|
if escapeSeqs.hasKey(key):
|
|
if escapeSeqs[key] != jkContinue:
|
|
key = escapeSeqs[key].int()
|
|
return (gkControl, key)
|
|
elif key < 30 and key > 0:
|
|
# JaleKeycode has values from 1 to 29
|
|
# which are NOT in escapeSeqs to save typing
|
|
return (gkControl, key)
|
|
else:
|
|
let validity = isValidUTF8(key)
|
|
case validity:
|
|
of ikValid:
|
|
# TODO: find a better alternative to runeAt
|
|
return (gkChar, bytes.runeAt(0).int)
|
|
of ikUnfinished:
|
|
discard # continue looping
|
|
else:
|
|
# For now, in development builds, crash
|
|
# later it might be wise to just ignore
|
|
# bad bytes, might check what other line
|
|
# editors do
|
|
raise newException(ValueError, "Invalid UTF8 input, " & $validity)
|