basic editor skeleton now uses the buffer

This commit is contained in:
Art 2022-12-29 00:32:15 +01:00
parent 9376ed14f9
commit 4e1ee4d089
Signed by: prod2
GPG Key ID: F3BB5A97A70A8DDE
4 changed files with 85 additions and 18 deletions

View File

@ -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

View File

@ -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
e.print(text)

View File

@ -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
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)

View File

@ -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(";")