2022-12-07 21:01:59 +01:00
|
|
|
import posix
|
2022-12-29 17:54:51 +01:00
|
|
|
import unicode
|
2022-12-07 21:01:59 +01:00
|
|
|
|
2022-12-29 15:54:08 +01:00
|
|
|
import terminalUtils/buffer
|
|
|
|
import terminalUtils/terminalGetInfo
|
|
|
|
import terminalUtils/keycodes
|
2022-12-07 21:01:59 +01:00
|
|
|
|
2022-12-29 17:54:51 +01:00
|
|
|
import textBuffer
|
|
|
|
import renderer
|
|
|
|
|
2022-12-07 21:01:59 +01:00
|
|
|
type
|
|
|
|
EditorState* = ref object
|
|
|
|
# config
|
|
|
|
prompt: string # prompt to display
|
|
|
|
|
2022-12-29 17:54:51 +01:00
|
|
|
textBuffer: TextBuffer # current text
|
|
|
|
termBuffer: Buffer
|
|
|
|
history: seq[TextBuffer] # past texts
|
2022-12-07 21:01:59 +01:00
|
|
|
historyIndex: int # where are we in history
|
2022-12-30 16:13:18 +01:00
|
|
|
scroll: Scroll
|
|
|
|
active: bool
|
|
|
|
|
|
|
|
# screen properties passed to renderer
|
|
|
|
width, height: int
|
|
|
|
cursorY: int
|
2022-12-07 21:01:59 +01:00
|
|
|
|
2022-12-28 18:12:29 +01:00
|
|
|
EditorResult* = enum
|
2022-12-30 16:13:18 +01:00
|
|
|
erError, erEnter, erCtrlC, erCtrlD
|
2022-12-28 18:12:29 +01:00
|
|
|
|
2022-12-07 21:01:59 +01:00
|
|
|
# for editors
|
|
|
|
var editors: seq[EditorState]
|
|
|
|
|
2022-12-30 16:13:18 +01:00
|
|
|
proc redraw*(ed: EditorState): int =
|
|
|
|
render(ed.textBuffer, ed.termBuffer, ed.prompt, ed.scroll, ed.width, ed.height, ed.cursorY)
|
|
|
|
|
2022-12-07 21:01:59 +01:00
|
|
|
# resize support
|
|
|
|
onSignal(28):
|
2022-12-30 16:13:18 +01:00
|
|
|
let nh = termGetHeight()
|
|
|
|
let nw = termGetWidth()
|
|
|
|
for ed in editors:
|
|
|
|
ed.width = nw
|
|
|
|
ed.height = nh
|
|
|
|
if ed.active:
|
|
|
|
discard ed.redraw()
|
2022-12-07 21:01:59 +01:00
|
|
|
|
2022-12-29 17:54:51 +01:00
|
|
|
proc newEditor*(prompt: string = "> "): EditorState =
|
2022-12-07 21:01:59 +01:00
|
|
|
new(result)
|
2022-12-30 16:13:18 +01:00
|
|
|
let (_, cursorY) = termGetCursorPos(stdout)
|
|
|
|
result.cursorY = cursorY
|
|
|
|
result.width = termGetWidth()
|
|
|
|
result.height = termGetHeight()
|
2022-12-07 21:01:59 +01:00
|
|
|
result.prompt = prompt
|
2022-12-30 16:13:18 +01:00
|
|
|
result.active = false
|
2022-12-07 21:01:59 +01:00
|
|
|
editors.add(result)
|
|
|
|
|
|
|
|
proc destroyEditor*(oldEditor: EditorState) =
|
|
|
|
for i in 0..editors.high():
|
|
|
|
let ed = editors[i]
|
|
|
|
if ed == oldEditor:
|
|
|
|
editors.del(i)
|
|
|
|
return
|
|
|
|
|
2022-12-28 18:12:29 +01:00
|
|
|
proc read*(ed: EditorState): (EditorResult, string) =
|
|
|
|
|
|
|
|
var editorResult = erError
|
2022-12-30 16:13:18 +01:00
|
|
|
ed.termBuffer = newBuffer(0, ed.cursorY, termGetWidth(), 1, stdout)
|
2022-12-29 17:54:51 +01:00
|
|
|
ed.textBuffer = newTextBuffer()
|
|
|
|
ed.history.add(ed.textBuffer)
|
|
|
|
ed.historyIndex = ed.history.high()
|
2022-12-30 16:13:18 +01:00
|
|
|
ed.scroll = new(Scroll)
|
|
|
|
ed.active = true
|
|
|
|
var lastRenderLines = 0
|
2022-12-29 18:50:57 +01:00
|
|
|
|
2022-12-30 00:17:51 +01:00
|
|
|
template moveInHistory(delta: int) =
|
2022-12-30 00:26:43 +01:00
|
|
|
let newHistoryIndex = min(max(ed.historyIndex + delta, 0), ed.history.high())
|
2022-12-30 00:17:51 +01:00
|
|
|
if newHistoryIndex != ed.historyIndex:
|
|
|
|
ed.textBuffer = ed.history[newHistoryIndex]
|
2022-12-30 16:13:18 +01:00
|
|
|
ed.scroll.reset()
|
2022-12-30 00:17:51 +01:00
|
|
|
ed.historyIndex = newHistoryIndex
|
|
|
|
|
2022-12-07 21:01:59 +01:00
|
|
|
while true:
|
2022-12-30 16:13:18 +01:00
|
|
|
lastRenderLines = ed.redraw()
|
2022-12-29 17:54:51 +01:00
|
|
|
let (getKeyResult, key) = getKey()
|
|
|
|
case getKeyResult:
|
|
|
|
of gkChar:
|
|
|
|
ed.textBuffer.insertRune(key.uint32().Rune())
|
|
|
|
of gkControl:
|
|
|
|
let control = key.JaleKeycode()
|
|
|
|
case control:
|
|
|
|
of jkEnter:
|
2022-12-30 00:17:51 +01:00
|
|
|
if ed.textBuffer.isLineEmpty() and ed.textBuffer.isLastLine():
|
2022-12-30 00:26:43 +01:00
|
|
|
ed.textBuffer.stripFinalNewline()
|
2022-12-29 17:54:51 +01:00
|
|
|
editorResult = erEnter
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
ed.textBuffer.enter()
|
|
|
|
of {jkBackspace, jkAltBackspace}:
|
|
|
|
ed.textBuffer.backspace()
|
|
|
|
of jkDelete:
|
|
|
|
ed.textBuffer.delete()
|
|
|
|
of jkCtrlC: # ctrl+c
|
|
|
|
editorResult = erCtrlC
|
|
|
|
break
|
|
|
|
of jkCtrlD: # ctrl+d
|
|
|
|
if ed.textBuffer.isEmpty():
|
|
|
|
editorResult = erCtrlD
|
|
|
|
break
|
|
|
|
of jkLeft:
|
|
|
|
ed.textBuffer.moveCursor(-1, 0)
|
|
|
|
of jkRight:
|
|
|
|
ed.textBuffer.moveCursor(1, 0)
|
|
|
|
of jkDown:
|
2022-12-30 00:17:51 +01:00
|
|
|
if ed.textBuffer.isLastLine():
|
|
|
|
moveInHistory(1)
|
|
|
|
else:
|
|
|
|
ed.textBuffer.moveCursor(0, 1)
|
2022-12-29 17:54:51 +01:00
|
|
|
of jkUp:
|
2022-12-30 00:17:51 +01:00
|
|
|
if ed.textBuffer.isFistLine():
|
|
|
|
moveInHistory(-1)
|
|
|
|
else:
|
|
|
|
ed.textBuffer.moveCursor(0, -1)
|
2022-12-29 17:54:51 +01:00
|
|
|
of jkEnd:
|
|
|
|
ed.textBuffer.moveToEnd(true)
|
|
|
|
of jkHome:
|
|
|
|
ed.textBuffer.moveToEnd(false)
|
2022-12-30 00:17:51 +01:00
|
|
|
of jkPageDown:
|
|
|
|
let termHeight = termGetHeight()
|
|
|
|
ed.textBuffer.moveCursor(0, termHeight div 2)
|
|
|
|
of jkPageUp:
|
|
|
|
let termHeight = termGetHeight()
|
|
|
|
ed.textBuffer.moveCursor(0, -(termHeight div 2))
|
2022-12-29 17:54:51 +01:00
|
|
|
of jkCtrlDown:
|
|
|
|
ed.textBuffer.newLine()
|
2022-12-07 21:01:59 +01:00
|
|
|
else:
|
2022-12-29 17:54:51 +01:00
|
|
|
discard # not implemented
|
2022-12-30 00:26:43 +01:00
|
|
|
|
2022-12-30 16:13:18 +01:00
|
|
|
# stop redraws on resize
|
|
|
|
ed.active = false
|
|
|
|
|
2022-12-30 00:17:51 +01:00
|
|
|
# return val, strip final newline (due to how double enter is how you enter)
|
|
|
|
let cont = ed.textBuffer.getContent()
|
2022-12-30 16:13:18 +01:00
|
|
|
if editorResult == erError:
|
|
|
|
raise newException(Defect, "Editor loop quit without setting editor result.")
|
2022-12-30 00:26:43 +01:00
|
|
|
result = (editorResult, cont)
|
2022-12-07 21:01:59 +01:00
|
|
|
|
|
|
|
# cleanup
|
2022-12-29 15:54:08 +01:00
|
|
|
stdout.write("\n")
|
2022-12-30 16:13:18 +01:00
|
|
|
ed.cursorY = min(ed.height-1, ed.cursorY + lastRenderLines)
|
2022-12-30 00:26:43 +01:00
|
|
|
|
|
|
|
# don't add empty lines to history
|
|
|
|
if ed.history[ed.history.high()].getContent() == "":
|
|
|
|
ed.history.del(ed.history.high())
|
2022-12-30 16:13:18 +01:00
|
|
|
|
|
|
|
proc updateCursorPos*(ed: EditorState) =
|
|
|
|
let (_, cursorY) = termGetCursorPos(stdout)
|
|
|
|
ed.cursorY = cursorY
|