jal3/textBuffer.nim

147 lines
3.7 KiB
Nim

# a multiline utf8 text buffer
import strutils
import unicode
import sequtils
type
TextBuffer* = ref object
content: seq[seq[Rune]]
cursorX: int # rune index
cursorY: int
proc newTextBuffer*: TextBuffer =
new(result)
result.content = @[]
result.content.add(@[])
result.cursorX = 0
result.cursorY = 0
proc getLine*(buf: TextBuffer): seq[Rune] =
buf.content[buf.cursorY]
proc getContent*(buf: TextBuffer): string =
buf.content.join("\n")
proc cline(buf: TextBuffer): var seq[Rune] =
buf.content[buf.cursorY]
proc insertRune*(buf: TextBuffer, rune: Rune) =
buf.cline().insert(rune, buf.cursorX)
buf.cursorX += 1
proc isEmpty*(buf: TextBuffer): bool =
buf.content.len() == 1 and buf.content[0].len() == 0
proc delete*(buf: TextBuffer) =
# emulates a delete key
if buf.cursorX == 0 and buf.cline().len() > 0:
buf.cline() = buf.cline[1..buf.cline.high()]
elif buf.cursorX > buf.cline.high():
if buf.cursorY < buf.content.high():
# merging lines
let left = buf.cline()
buf.content.delete(buf.cursorY)
buf.cline() = left & buf.cline()
else:
buf.cline() = buf.cline[0..buf.cursorX-1] & buf.cline[buf.cursorX+1..buf.cline().high()]
proc backspace*(buf: TextBuffer) =
# emulates a backspace key
if buf.cursorX == 0:
if buf.cursorY > 0:
# merging lines
let right = buf.cline()
buf.content.delete(buf.cursorY)
dec buf.cursorY
buf.cursorX = buf.cline().len()
buf.cline() &= right
elif buf.cursorX > buf.cline().high():
buf.cline() = buf.cline[0..buf.cline().high()-1]
dec buf.cursorX
else:
buf.cline() = buf.cline[0..buf.cursorX-2] & buf.cline[buf.cursorX..buf.cline().high()]
dec buf.cursorX
proc moveCursor*(buf: TextBuffer, dx, dy: int) =
if dx != 0:
buf.cursorX += dx
if buf.cursorX < 0:
buf.cursorX = 0 # TODO switching over lines
elif buf.cursorX > buf.cline().len():
buf.cursorX = buf.cline().len() # TODO switching over lines
if dy != 0:
buf.cursorY += dy
if buf.cursorY < 0:
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)
proc isCursorAtEnd*(buf: TextBuffer): bool =
return buf.cursorY == buf.content.high() and buf.cursorX > buf.cline().high()
proc moveToEnd*(buf: TextBuffer, forward: bool) =
# home/end keys
if forward:
buf.cursorX = buf.cline().len()
else:
buf.cursorX = 0
proc enter*(buf: TextBuffer) =
# emulates an enter in text editors
let pre = buf.cline[0..buf.cursorX-1]
let post = buf.cline[buf.cursorX..^1]
buf.cline() = pre
inc buf.cursorY
buf.content.insert(post, buf.cursorY)
buf.cursorX = 0
proc newLine*(buf: TextBuffer) =
# inserts a new empty line below the current one
# and moves cursor to it
inc buf.cursorY
let empty: seq[Rune] = @[]
buf.content.insert(empty, buf.cursorY)
buf.cursorX = 0
proc getLineCount*(buf: TextBuffer): int =
buf.content.len()
proc lines*(buf: TextBuffer): seq[seq[Rune]] =
buf.content
proc isLastLine*(buf: TextBuffer): bool =
buf.cursorY == buf.content.high()
proc isFistLine*(buf: TextBuffer): bool =
buf.cursorY == 0
proc isLineEmpty*(buf: TextBuffer): bool =
buf.cline().len() == 0
proc stripFinalNewline*(buf: TextBuffer) =
# nothing if only 1 line
if buf.content.len() <= 1:
return
# nothing if last line not empty
if buf.content[buf.content.high()].len() > 0:
return
# adjust cursor
if buf.cursorY == buf.content.high():
dec buf.cursorY
buf.cursorX = buf.cline().len()
# pop final element
buf.content.del(buf.content.high())