new keybindings

better keybinding support
added shift and ctrl versions of arrow keys
added page up/down
added a few extra ctrl versions of utility keys
getkey is a utility for learning key numbers
This commit is contained in:
Productive2 2021-02-15 21:51:23 +01:00
parent 49fa8ab937
commit 9b12850ffb
8 changed files with 149 additions and 18 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
main
getkey

View File

@ -18,6 +18,14 @@ proc populateDefaults*(editor: LineEditor) =
editor.content.up()
editor.bindKey("down"):
editor.content.down()
editor.bindKey("home"):
editor.content.home()
editor.bindKey("end"):
editor.content.`end`()
editor.bindKey("pageup"):
editor.content.vhome()
editor.bindKey("pagedown"):
editor.content.vend()
editor.bindKey("backspace"):
editor.content.backspace()
editor.bindKey("delete"):
@ -26,7 +34,7 @@ proc populateDefaults*(editor: LineEditor) =
if editor.content.Y() == editor.content.high() and editor.content.getLine(editor.content.high()) == "":
editor.finish()
else:
editor.content.insertline()
editor.content.enter()
editor.bindKey("ctrl+c"):
editor.finish()
editor.events.call(jeQuit)

View File

@ -69,6 +69,8 @@ proc render(editor: LineEditor, line: int = -1, hscroll: bool = true) =
0
)
proc clearLine =
write stdout, "\r" & " ".repeat(terminalWidth())
proc fullRender(editor: LineEditor) =
# from the top cursor pos, it draws the entire multiline prompt, then
@ -80,7 +82,15 @@ proc fullRender(editor: LineEditor) =
else:
write stdout, "\n"
inc editor.rendered
cursorUp(editor.content.len() - editor.content.Y)
var extraup = 0
while editor.content.len() < editor.rendered:
clearLine()
cursorDown(1)
dec editor.rendered
inc extraup
cursorUp(editor.content.len() - editor.content.Y + extraup)
proc moveCursorToEnd(editor: LineEditor) =
# only called when read finished

15
getkey.nim Normal file
View File

@ -0,0 +1,15 @@
# handy tool that prints out char codes when pressed
import terminal
import strutils
echo "Press 'ctrl+c' to quit"
echo "Press 'ctrl+a' to print a horizontal bar"
while true:
let key = int(getch())
if key == 3:
break
if key == 1:
echo "=".repeat(terminalWidth())
else:
echo key

View File

@ -7,7 +7,12 @@ import terminal
type
JaleKeycode* = enum
jkStart = 255 # jale keycodes exported start one above jkStart
jkLeft = 256, jkRight, jkUp, jkDown, jkHome, jkEnd, jkDelete, jkBackspace,
jkLeft = 256, jkRight, jkUp, jkDown,
jkHome, jkEnd, jkPageUp, jkPageDown,
jkShiftLeft, jkShiftRight, jkShiftUp, jkShiftDown,
jkCtrlLeft, jkCtrlRight, jkCtrlUp, jkCtrlDown,
jkCtrlHome, jkCtrlEnd, jkCtrlPageUp, jkCtrlPageDown,
jkDelete, jkBackspace,
jkInsert, jkEnter
jkFinish, # jale keycodes exported end one below jkFinish
@ -56,20 +61,60 @@ block:
defEscSeq(@[224, 83], jkDelete)
# TODO: finish defining escape sequences
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)
# shift+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, 50, 65], jkShiftUp)
defEscSeq(@[27, 91, 49, 59, 50, 66], jkShiftDown)
defEscSeq(@[27, 91, 49, 59, 50, 67], jkShiftRight)
defEscSeq(@[27, 91, 49, 59, 50, 68], jkShiftLeft)
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
# home, end
defEscSeq(@[27, 91, 72], jkHome)
defEscSeq(@[27, 91, 70], jkEnd)
# ctrl+ home, end
defEscSeq(@[27, 91, 49, 59, 53, 72], jkCtrlHome)
defEscSeq(@[27, 91, 49, 59, 53, 70], jkCtrlEnd)
# fancy keys like delete, insert, pgup, pgdown
defEscSeq(@[27, 91, 51], jkContinue)
defEscSeq(@[27, 91, 50], jkContinue)
defEscSeq(@[27, 91, 54], jkContinue)
defEscSeq(@[27, 91, 53], jkContinue)
defEscSeq(@[27, 91, 51, 126], jkDelete)
defEscSeq(@[27, 91, 50, 126], jkInsert)
defEscSeq(@[27, 91, 53, 126], jkPageUp)
defEscSeq(@[27, 91, 54, 126], jkPageDown)
# ctrl + fancy keys like pgup, pgdown
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)
# other keys
defEscSeq(@[13], jkEnter)
defEscSeq(@[127], jkBackspace)
# TODO ctrl+h as backspace? test more terminals
proc getKey*: int =
var key: int = 0

View File

@ -6,13 +6,10 @@ type
Line* = ref object
content: string
# getters/setters
# getter
proc content*(l: Line): string =
l.content
proc `content=`*(l: Line, str: string) =
l.content = str
proc content*(line: Line): string =
line.content
# constructor
@ -38,3 +35,17 @@ proc delete*(line: Line, start: int, finish: int) =
if finish < line.content.high():
result &= line.content[finish+1..line.content.high()]
line.content = result
proc range*(line: Line, start: int, finish: int): string =
if start > finish or start < 0 or finish > line.content.high():
raise newException(CatchableError, &"Invalid arguments for Line.range: start {start}, finish {finish} for line of length {line.content.len()}")
result = line.content[start..finish]
proc len*(line: Line): int =
line.content.len()
proc high*(line: Line): int =
line.content.high()
proc clearLine*(line: Line) =
line.content = ""

View File

@ -27,7 +27,7 @@ proc newMultiline*: Multiline =
# methods
proc lineLen*(ml: Multiline): int =
ml.lines[ml.y].content.len()
ml.lines[ml.y].len()
proc lineHigh*(ml: Multiline): int =
ml.lineLen() - 1
@ -38,6 +38,20 @@ proc len*(ml: Multiline): int =
proc high*(ml: Multiline): int =
ml.lines.high()
# internal setter
proc sety(ml: Multiline, target: int) =
ml.y = target
if ml.x > ml.lineLen():
ml.x = ml.lineLen()
# warning check before calling them if y lands in illegal territory
# these are unsafe!
proc decy(ml: Multiline) =
ml.sety(ml.y-1)
proc incy(ml: Multiline) =
ml.sety(ml.y+1)
proc left*(ml: Multiline) =
if ml.x > 0:
dec ml.x
@ -48,15 +62,24 @@ proc right*(ml: Multiline) =
proc up*(ml: Multiline) =
if ml.y > 0:
dec ml.y
if ml.x > ml.lineLen():
ml.x = ml.lineLen()
ml.decy
proc down*(ml: Multiline) =
if ml.y < ml.lines.high():
inc ml.y
if ml.x > ml.lineLen():
ml.x = ml.lineLen()
ml.incy
proc home*(ml: Multiline) =
ml.x = 0
proc `end`*(ml: Multiline) =
ml.x = ml.lineLen()
proc vhome*(ml: Multiline) =
ml.sety(0)
proc vend*(ml: Multiline) =
ml.sety(ml.high())
proc insert*(ml: Multiline, str: string) =
ml.lines[ml.y].insert(str, ml.x)
@ -70,9 +93,16 @@ proc backspace*(ml: Multiline) =
if ml.x > 0:
ml.lines[ml.y].delete(ml.x - 1, ml.x - 1)
dec ml.x
elif ml.x == 0 and ml.y > 0:
let cut = ml.lines[ml.y].content
ml.lines.delete(ml.y)
dec ml.y
ml.x = ml.lineLen()
ml.lines[ml.y].insert(cut, ml.x)
proc insertline*(ml: Multiline) =
# TODO split line support
# the default behaviour of command mode o
if ml.y == ml.lines.high():
ml.lines.add(newLine())
else:
@ -80,9 +110,18 @@ proc insertline*(ml: Multiline) =
inc ml.y
ml.x = 0
proc enter*(ml: Multiline) =
# the default behaviour of enter in normie editors
if ml.x > ml.lineHigh():
ml.insertline() # when end of line, it's just an insertline
else:
let cut = ml.lines[ml.y].range(ml.x, ml.lineHigh())
ml.lines[ml.y].delete(ml.x, ml.lineHigh())
ml.insertline()
ml.lines[ml.y].insert(cut, 0)
proc clearline*(ml: Multiline) =
ml.lines[ml.y].content = ""
ml.lines[ml.y].clearLine()
proc removeline*(ml: Multiline) =
ml.lines.delete(ml.y)

View File

@ -13,6 +13,8 @@ template bindKey*(editor: LineEditor, key: char, body: untyped) =
body
template bindKey*(editor: LineEditor, key: string, body: untyped) =
if not keysByName.hasKey(key):
raise newException(Defect, "Invalid key " & key & ", it's not in the keycode table")
editor.bindKey(keysByName[key]):
body