From 2fffb11a69c235953e3fb11c3dec850bcc256d7f Mon Sep 17 00:00:00 2001 From: prod2 Date: Thu, 29 Dec 2022 18:50:57 +0100 Subject: [PATCH] work on multiline, horizontal scroll --- editor.nim | 4 ++- example.nim | 5 ++++ renderer.nim | 54 ++++++++++++++++++++++++++++++++++------ terminalUtils/buffer.nim | 34 ++++++++++++++++++++----- textBuffer.nim | 9 ++++++- 5 files changed, 91 insertions(+), 15 deletions(-) diff --git a/editor.nim b/editor.nim index c32a625..85ac8d3 100644 --- a/editor.nim +++ b/editor.nim @@ -49,8 +49,10 @@ proc read*(ed: EditorState): (EditorResult, string) = ed.history.add(ed.textBuffer) ed.historyIndex = ed.history.high() + var scroll: Scroll = new(Scroll) + while true: - render(ed.textBuffer, ed.termBuffer, ed.prompt) + render(ed.textBuffer, ed.termBuffer, ed.prompt, scroll) let (getKeyResult, key) = getKey() case getKeyResult: of gkChar: diff --git a/example.nim b/example.nim index 797fdff..5fe04b8 100644 --- a/example.nim +++ b/example.nim @@ -1,4 +1,5 @@ import editor +import terminal let e = newEditor(">>> ") @@ -6,5 +7,9 @@ while true: let (res, text) = e.read() if res in {erCtrlC, erCtrlD} or text == "quit": break + elif text == "clear": + eraseScreen() + setCursorPos(0, 0) + continue if text.len() > 0: echo text \ No newline at end of file diff --git a/renderer.nim b/renderer.nim index db495c4..09372bc 100644 --- a/renderer.nim +++ b/renderer.nim @@ -2,26 +2,66 @@ # when render() is called, rewrites the terminal buffer completely as a function of # of the text buffer +import strutils + import textBuffer import terminalUtils/buffer import terminalUtils/terminalGetInfo -proc render*(textBuffer: TextBuffer, termBuffer: Buffer, prompt: string) = +type Scroll* = ref object + x, y: int + +proc render*(textBuffer: TextBuffer, termBuffer: Buffer, prompt: string, scroll: Scroll) = # we are free to "redraw" everything everytime # since termBuffer double buffers # resizing the buffer if needed let lineCount = textBuffer.getLineCount() let termWidth = termGetWidth() - let termHeight = termGetHeight() # TODO + let termHeight = termGetHeight() let (bufWidth, bufHeight) = termBuffer.getSize() - - termBuffer.clearLine() - termBuffer.write(prompt) - termBuffer.write(textBuffer.getLine()) + if bufWidth != termWidth or bufHeight != min(lineCount, termHeight): + termBuffer.resize(termWidth, min(lineCount, termHeight)) + + termBuffer.clear() + let (x, y) = textBuffer.getCursorPos() - termBuffer.setCursorPos(x + prompt.len(), y) + + let lines = textBuffer.lines() + let promptLen = prompt.len() + let maxTextLen = termWidth - promptLen + if maxTextLen < 1: + termBuffer.redraw() + return # nothing will fit anyways + + for i in 0..lines.high(): + let line = lines[i] + if i == 0: + termBuffer.write(prompt) + else: + termBuffer.write(" ".repeat(promptLen)) + + let lineLen = line.len() + if lineLen < maxTextLen - 1: + scroll.x = 0 + termBuffer.write(line) + else: + const scrollOffset = 2 + # horizontal scroll + # right scroll + if x - scroll.x >= maxTextLen - scrollOffset: + scroll.x = x - maxTextLen + scrollOffset + # left scroll + if x - scroll.x < 0: + scroll.x = x + # left scroll + # keep what's in range + let fromPos = scroll.x + let toPos = min(scroll.x+maxTextLen-2, line.high()) + termBuffer.write(line[fromPos..toPos]) + termBuffer.newLine() + termBuffer.setCursorPos(x + promptLen - scroll.x, y - scroll.y) termBuffer.redraw() diff --git a/terminalUtils/buffer.nim b/terminalUtils/buffer.nim index 8a1c6e7..27a5679 100644 --- a/terminalUtils/buffer.nim +++ b/terminalUtils/buffer.nim @@ -104,6 +104,9 @@ proc write*(buf: Buffer, text: string) = let runes = text.toRunes() buf.write(runes) +proc newLine*(buf: Buffer) = + buf.bufferY += 1 + buf.bufferX = 0 proc redraw*(buf: Buffer, force: bool = false) = var toPrint = "" @@ -144,17 +147,36 @@ proc redraw*(buf: Buffer, force: bool = false) = func getSize*(buf: Buffer): (int, int) = (buf.width, buf.height) -proc resize*(buf: Buffer, newX, newY: int) = +proc resize*(buf: Buffer, newWidth, newHeight: int) = # shrinking X - if newX < buf.width: + if newWidth < buf.width: # dropping cells outside the screen var i = 0 - + buf.buffered.keepItIf(i mod buf.width < newWidth) + i = 0 + buf.displayed.keepItIf(i mod buf.width < newWidth) + # increasing X + elif newWidth > buf.width: + let extra = newWidth - buf.width + let extraCells = Cell(text: Rune(0)).repeat(extra) + for i in 0..buf.height-1: + let pos = i * newWidth + buf.width + buf.buffered.insert(extraCells, pos) + buf.displayed.insert(extraCells, pos) - + # shrinking Y + if newHeight < buf.height: + buf.buffered.setLen(newHeight * newWidth) + buf.displayed.setLen(newHeight * newWidth) + # increasing Y + elif newHeight > buf.height: + let extra = newHeight - buf.height + let extraCells = Cell(text: Rune(0)).repeat(extra * newWidth) + buf.buffered &= extraCells + buf.displayed &= extraCells - buf.width = newX - buf.height = newY + buf.width = newWidth + buf.height = newHeight func getPosition*(buf: Buffer): (int, int) = (buf.positionX, buf.positionY) diff --git a/textBuffer.nim b/textBuffer.nim index 8194fcf..93f328d 100644 --- a/textBuffer.nim +++ b/textBuffer.nim @@ -67,6 +67,10 @@ proc moveCursor*(buf: TextBuffer, dx, dy: int) = buf.cursorY = 0 elif buf.cursorY > buf.content.high(): buf.cursorY = buf.content.high() + + if buf.cursorX > buf.cline().len(): + buf.cursorX = buf.cline().len() + proc getCursorPos*(buf: TextBuffer): (int, int) = (buf.cursorX, buf.cursorY) @@ -98,4 +102,7 @@ proc newLine*(buf: TextBuffer) = buf.cursorX = 0 proc getLineCount*(buf: TextBuffer): int = - buf.content.len() \ No newline at end of file + buf.content.len() + +proc lines*(buf: TextBuffer): seq[seq[Rune]] = + buf.content \ No newline at end of file