jal3/editor.nim

157 lines
4.3 KiB
Nim

import posix
import unicode
import terminalUtils/buffer
import terminalUtils/terminalGetInfo
import terminalUtils/keycodes
import textBuffer
import renderer
type
EditorState* = ref object
# config
prompt: string # prompt to display
textBuffer: TextBuffer # current text
termBuffer: Buffer
history: seq[TextBuffer] # past texts
historyIndex: int # where are we in history
scroll: Scroll
active: bool
# screen properties passed to renderer
width, height: int
cursorY: int
EditorResult* = enum
erError, erEnter, erCtrlC, erCtrlD
# for editors
var editors: seq[EditorState]
proc redraw*(ed: EditorState): int =
render(ed.textBuffer, ed.termBuffer, ed.prompt, ed.scroll, ed.width, ed.height, ed.cursorY)
# resize support
onSignal(28):
let nh = termGetHeight()
let nw = termGetWidth()
for ed in editors:
ed.width = nw
ed.height = nh
if ed.active:
discard ed.redraw()
proc newEditor*(prompt: string = "> "): EditorState =
new(result)
let (_, cursorY) = termGetCursorPos(stdout)
result.cursorY = cursorY
result.width = termGetWidth()
result.height = termGetHeight()
result.prompt = prompt
result.active = false
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 read*(ed: EditorState): (EditorResult, string) =
var editorResult = erError
ed.termBuffer = newBuffer(0, ed.cursorY, termGetWidth(), 1, stdout)
ed.textBuffer = newTextBuffer()
ed.history.add(ed.textBuffer)
ed.historyIndex = ed.history.high()
ed.scroll = new(Scroll)
ed.active = true
var lastRenderLines = 0
template moveInHistory(delta: int) =
let newHistoryIndex = min(max(ed.historyIndex + delta, 0), ed.history.high())
if newHistoryIndex != ed.historyIndex:
ed.textBuffer = ed.history[newHistoryIndex]
ed.scroll.reset()
ed.historyIndex = newHistoryIndex
while true:
lastRenderLines = ed.redraw()
let (getKeyResult, key) = getKey()
case getKeyResult:
of gkChar:
ed.textBuffer.insertRune(key.uint32().Rune())
of gkControl:
let control = key.JaleKeycode()
case control:
of jkEnter:
if ed.textBuffer.isLineEmpty() and ed.textBuffer.isLastLine():
ed.textBuffer.stripFinalNewline()
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:
if ed.textBuffer.isLastLine():
moveInHistory(1)
else:
ed.textBuffer.moveCursor(0, 1)
of jkUp:
if ed.textBuffer.isFistLine():
moveInHistory(-1)
else:
ed.textBuffer.moveCursor(0, -1)
of jkEnd:
ed.textBuffer.moveToEnd(true)
of jkHome:
ed.textBuffer.moveToEnd(false)
of jkPageDown:
let termHeight = termGetHeight()
ed.textBuffer.moveCursor(0, termHeight div 2)
of jkPageUp:
let termHeight = termGetHeight()
ed.textBuffer.moveCursor(0, -(termHeight div 2))
of jkCtrlDown:
ed.textBuffer.newLine()
else:
discard # not implemented
# stop redraws on resize
ed.active = false
# return val, strip final newline (due to how double enter is how you enter)
let cont = ed.textBuffer.getContent()
if editorResult == erError:
raise newException(Defect, "Editor loop quit without setting editor result.")
result = (editorResult, cont)
# cleanup
stdout.write("\n")
ed.cursorY = min(ed.height-1, ed.cursorY + lastRenderLines)
# don't add empty lines to history
if ed.history[ed.history.high()].getContent() == "":
ed.history.del(ed.history.high())
proc updateCursorPos*(ed: EditorState) =
let (_, cursorY) = termGetCursorPos(stdout)
ed.cursorY = cursorY