131 lines
3.4 KiB
Nim
131 lines
3.4 KiB
Nim
import unicode
|
|
import strutils
|
|
import strformat
|
|
import posix
|
|
|
|
import terminalUtils/buffer
|
|
import terminalUtils/terminalGetInfo
|
|
import terminalUtils/keycodes
|
|
|
|
type
|
|
EditorState* = ref object
|
|
# config
|
|
prompt: string # prompt to display
|
|
maxRowsGoal: int # if terminal resizes, how to compute max rows
|
|
# positive: it will set max rows to maxRowsGoal, or terminal height, whichever is lower
|
|
# negative: it will set it to terminal height - maxRowsGoal or terminal height, whichever is lower
|
|
# 0: terminal height
|
|
|
|
# state reached during execution
|
|
x: int # current cursor x position on screen
|
|
bx: int # current x position in buffer (not same as x due to UTF-8)
|
|
y: int # which row are we editing
|
|
size: int # size of the row being edited in characters
|
|
buffer: seq[string] # current text
|
|
screenBuffer: Buffer
|
|
history: seq[seq[string]] # past texts
|
|
historyIndex: int # where are we in history
|
|
cols: int # number of columns in the terminal
|
|
maxRows: int # max number of rows allowed
|
|
|
|
EditorResult* = enum
|
|
erEnter, erCtrlC, erCtrlD, erError
|
|
|
|
# for editors
|
|
var editors: seq[EditorState]
|
|
|
|
# resize support
|
|
onSignal(28):
|
|
discard
|
|
|
|
proc newEditor*(prompt: string = "> ", multiline: bool = true): EditorState =
|
|
new(result)
|
|
result.prompt = prompt
|
|
result.maxRowsGoal = if multiline: 0 else: 1
|
|
result.buffer.add("")
|
|
editors.add(result)
|
|
|
|
proc destroyEditor*(oldEditor: EditorState) =
|
|
for i in 0..editors.high():
|
|
let ed = editors[i]
|
|
if ed == oldEditor:
|
|
editors.del(i)
|
|
return
|
|
|
|
proc render(ed: EditorState) =
|
|
ed.screenBuffer.clearLine()
|
|
ed.screenBuffer.write(ed.prompt)
|
|
ed.screenBuffer.write(ed.buffer[ed.y])
|
|
ed.screenBuffer.redraw()
|
|
|
|
template cline(ed: EditorState): var string =
|
|
ed.buffer[ed.y]
|
|
|
|
proc read*(ed: EditorState): (EditorResult, string) =
|
|
|
|
var editorResult = erError
|
|
let (_, cursorY) = termGetCursorPos(stdout)
|
|
ed.screenBuffer = newBuffer(0, cursorY, termGetWidth(), termGetHeight() - cursorY, stdout)
|
|
|
|
ed.render()
|
|
while true:
|
|
let key = getKey()
|
|
case key:
|
|
of jkEnter.int:
|
|
editorResult = erEnter
|
|
break
|
|
of jkBackspace.int:
|
|
if ed.x == 0:
|
|
discard # merge two lines TODO
|
|
elif ed.x > ed.cline().high():
|
|
# TODO UTF8
|
|
ed.cline() = ed.cline[0..ed.cline().high()-1]
|
|
dec ed.x
|
|
dec ed.bx
|
|
else:
|
|
# TODO UTF8
|
|
ed.cline() = ed.cline[0..ed.x-1] & ed.cline[ed.x+1..ed.cline().high()]
|
|
dec ed.x
|
|
dec ed.bx
|
|
of jkDelete.int:
|
|
if ed.x > ed.cline.high():
|
|
discard # merge two lines TODO
|
|
elif ed.x == 0:
|
|
# TODO UTF8
|
|
ed.cline() = ed.cline[1..ed.cline.high()]
|
|
else:
|
|
# TODO UTF8
|
|
discard # TODO implement
|
|
of 3: # ctrl+c
|
|
editorResult = erCtrlC
|
|
break
|
|
of 4: # ctrl+d
|
|
if ed.buffer.len() <= 1 and ed.cline().len() == 0:
|
|
editorResult = erCtrlD
|
|
break
|
|
else:
|
|
if key > 31 and key < 127:
|
|
# ascii char
|
|
let ch = key.char()
|
|
if ed.x == 0:
|
|
ed.cline() = $ch & ed.cline
|
|
elif ed.x > ed.cline.high():
|
|
ed.cline() = ed.cline & $ch
|
|
else:
|
|
ed.cline() = ed.cline[0..ed.x-1] & $ch & ed.cline[ed.x..ed.cline().high()]
|
|
inc ed.x
|
|
inc ed.bx
|
|
|
|
ed.render()
|
|
|
|
# return val
|
|
result = (editorResult, ed.buffer.join("\n"))
|
|
|
|
# cleanup
|
|
stdout.write("\n")
|
|
ed.history.add(ed.buffer)
|
|
ed.buffer = @[""]
|
|
ed.y = 0
|
|
ed.x = 0
|
|
ed.screenBuffer = nil
|
|
|