From 4e1ee4d0891b64ad30ff501b1e02667dc59bcc9b Mon Sep 17 00:00:00 2001 From: prod2 Date: Thu, 29 Dec 2022 00:32:15 +0100 Subject: [PATCH] basic editor skeleton now uses the buffer --- editor.nim | 27 ++++++++++--- example.nim | 3 +- termBuffer/buffer.nim | 71 +++++++++++++++++++++++++++++----- termBuffer/escapeSequences.nim | 2 +- 4 files changed, 85 insertions(+), 18 deletions(-) diff --git a/editor.nim b/editor.nim index 00989af..8d6c88d 100644 --- a/editor.nim +++ b/editor.nim @@ -4,6 +4,8 @@ import terminal import strformat import posix +import termBuffer/buffer + import keycodes type @@ -21,6 +23,7 @@ type 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 @@ -41,6 +44,7 @@ proc newEditor*(prompt: string = "> ", multiline: bool = true): EditorState = result.prompt = prompt result.maxRowsGoal = if multiline: 0 else: 1 result.buffer.add("") + result.screenBuffer = newBuffer(0, 0, terminalWidth(), terminalHeight(), stdout) editors.add(result) proc destroyEditor*(oldEditor: EditorState) = @@ -51,15 +55,17 @@ proc destroyEditor*(oldEditor: EditorState) = return proc render(ed: EditorState) = - eraseLine() - setCursorXPos(0) - write stdout, ed.prompt - write stdout, ed.buffer[ed.y] + 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 print*(ed: EditorState, text: string) = + ed.screenBuffer.print(text) + ed.screenBuffer.redraw() proc read*(ed: EditorState): (EditorResult, string) = @@ -76,6 +82,7 @@ proc read*(ed: EditorState): (EditorResult, string) = 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 @@ -84,6 +91,15 @@ proc read*(ed: EditorState): (EditorResult, string) = 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 @@ -110,7 +126,6 @@ proc read*(ed: EditorState): (EditorResult, string) = result = (editorResult, ed.buffer.join("\n")) # cleanup - write(stdout, "\n") ed.history.add(ed.buffer) ed.buffer = @[""] ed.y = 0 diff --git a/example.nim b/example.nim index a49b1b0..821a60a 100644 --- a/example.nim +++ b/example.nim @@ -5,5 +5,6 @@ let e = newEditor("> ", false) while true: let (res, text) = e.read() if res in {erCtrlC, erCtrlD} or text == "quit": + e.print("") break - echo text \ No newline at end of file + e.print(text) \ No newline at end of file diff --git a/termBuffer/buffer.nim b/termBuffer/buffer.nim index be68fe7..e1535c9 100644 --- a/termBuffer/buffer.nim +++ b/termBuffer/buffer.nim @@ -1,5 +1,6 @@ import unicode import escapeSequences +import sequtils type Buffer* = ref object @@ -40,6 +41,7 @@ type attributes: seq[uint8] text: Rune + func getCursorPos*(buf: Buffer): (int, int) = (buf.bufferX, buf.bufferY) @@ -57,7 +59,6 @@ proc setCursorXPos*(buf: Buffer, x: int) = raise newException(ValueError, "Provided x is out of bounds.") buf.bufferX = x - proc setAttributes*(buf: Buffer, attributes: seq[uint8]) = buf.cursorAttributes = attributes @@ -99,24 +100,56 @@ proc write*(buf: Buffer, text: string) = for i in fromPos..toPos: buf.buffered[i] = Cell(text: runes[i-fromPos], attributes: buf.cursorAttributes) + buf.bufferX += runes.len() -proc redraw*(buf: Buffer) = +proc print*(buf: Buffer, text: string) = + # write, but makes sure it's on its own line + if buf.bufferX > 0: + # TODO scroll implementation + buf.bufferX = 0 + if buf.bufferY < buf.height - 1: + buf.bufferY += 1 + + buf.write(text) + + # TODO scroll implementation + if text.len() > 0 and buf.bufferY < buf.height - 1: + buf.bufferY += 1 + + +proc redraw*(buf: Buffer, force: bool = false) = var toPrint = "" - # save cursor state - let oldDisplayX = buf.displayX - let oldDisplayY = buf.displayY # go over display and buffer to find any differences for i in 0..buf.buffered.high(): let cellBuffer = buf.buffered[i] let cellDisplay = buf.displayed[i] - # TODO continue here :) - - + # TODO: this is a spot for obvious optimization + if cellBuffer != cellDisplay or force: + let y = i div buf.width + let x = i mod buf.width + # go to given place + toPrint &= escCursorPos(x+buf.positionX, y+buf.positionY) + + # get the right attributes + # first reset + toPrint &= escAttributes(@[fsReset.uint8]) + # set cell attributes + toPrint &= escAttributes(cellBuffer.attributes) + # write the character if it's a non null + if cellBuffer.text != Rune(0): + toPrint &= $cellBuffer.text + else: + # erase a character since it's a null + toPrint &= escEraseCharacters(1) + + # update displayed buffer + buf.displayed[i] = cellBuffer # finish - toPrint &= escCursorPos(oldDisplayX, oldDisplayY) + toPrint &= escAttributes(@[fsReset.uint8]) + toPrint &= escCursorPos(buf.bufferX + buf.positionX, buf.bufferY + buf.positionY) buf.stdout.write(toPrint) @@ -131,4 +164,22 @@ func getPosition*(buf: Buffer): (int, int) = proc setPosition*(buf: Buffer, x, y: int) = buf.positionX = x - buf.positionY = y \ No newline at end of file + buf.positionY = y + +proc newBuffer*(x, y, w, h: int, stdout: File): Buffer = + new(result) + result.positionX = x + result.positionY = y + result.width = w + result.height = h + result.stdout = stdout + let length = w * h + result.buffered = repeat(Cell(text: Rune(0)), length) + result.displayed = repeat(Cell(text: Rune(0)), length) + result.bufferX = 0 + result.bufferY = 0 + result.displayX = 0 + result.displayY = 0 + result.cursorAttributes = @[] + result.redraw(force = true) + diff --git a/termBuffer/escapeSequences.nim b/termBuffer/escapeSequences.nim index c292752..c02a70c 100644 --- a/termBuffer/escapeSequences.nim +++ b/termBuffer/escapeSequences.nim @@ -4,7 +4,7 @@ import strformat import strutils func escCursorPos*(x, y: int): string = - &"\e[{x+1};{y+1}H" + &"\e[{y+1};{x+1}H" func escAttributes*(attributes: seq[uint8]): string = let joined = attributes.join(";")