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