unicode support, half done multiline
This commit is contained in:
parent
3799102798
commit
d2001bb458
138
editor.nim
138
editor.nim
|
@ -1,32 +1,22 @@
|
||||||
import unicode
|
|
||||||
import strutils
|
|
||||||
import strformat
|
|
||||||
import posix
|
import posix
|
||||||
|
import unicode
|
||||||
|
|
||||||
import terminalUtils/buffer
|
import terminalUtils/buffer
|
||||||
import terminalUtils/terminalGetInfo
|
import terminalUtils/terminalGetInfo
|
||||||
import terminalUtils/keycodes
|
import terminalUtils/keycodes
|
||||||
|
|
||||||
|
import textBuffer
|
||||||
|
import renderer
|
||||||
|
|
||||||
type
|
type
|
||||||
EditorState* = ref object
|
EditorState* = ref object
|
||||||
# config
|
# config
|
||||||
prompt: string # prompt to display
|
prompt: string # prompt to display
|
||||||
maxRowsGoal: int # if terminal resizes, how to compute max rows
|
|
||||||
# positive: it will set max rows to maxRowsGoal, or terminal height, whichever is lower
|
|
||||||
# negative: it will set it to terminal height - maxRowsGoal or terminal height, whichever is lower
|
|
||||||
# 0: terminal height
|
|
||||||
|
|
||||||
# state reached during execution
|
textBuffer: TextBuffer # current text
|
||||||
x: int # current cursor x position on screen
|
termBuffer: Buffer
|
||||||
bx: int # current x position in buffer (not same as x due to UTF-8)
|
history: seq[TextBuffer] # past texts
|
||||||
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
|
historyIndex: int # where are we in history
|
||||||
cols: int # number of columns in the terminal
|
|
||||||
maxRows: int # max number of rows allowed
|
|
||||||
|
|
||||||
EditorResult* = enum
|
EditorResult* = enum
|
||||||
erEnter, erCtrlC, erCtrlD, erError
|
erEnter, erCtrlC, erCtrlD, erError
|
||||||
|
@ -38,11 +28,9 @@ var editors: seq[EditorState]
|
||||||
onSignal(28):
|
onSignal(28):
|
||||||
discard
|
discard
|
||||||
|
|
||||||
proc newEditor*(prompt: string = "> ", multiline: bool = true): EditorState =
|
proc newEditor*(prompt: string = "> "): EditorState =
|
||||||
new(result)
|
new(result)
|
||||||
result.prompt = prompt
|
result.prompt = prompt
|
||||||
result.maxRowsGoal = if multiline: 0 else: 1
|
|
||||||
result.buffer.add("")
|
|
||||||
editors.add(result)
|
editors.add(result)
|
||||||
|
|
||||||
proc destroyEditor*(oldEditor: EditorState) =
|
proc destroyEditor*(oldEditor: EditorState) =
|
||||||
|
@ -52,80 +40,62 @@ proc destroyEditor*(oldEditor: EditorState) =
|
||||||
editors.del(i)
|
editors.del(i)
|
||||||
return
|
return
|
||||||
|
|
||||||
proc render(ed: EditorState) =
|
|
||||||
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 read*(ed: EditorState): (EditorResult, string) =
|
proc read*(ed: EditorState): (EditorResult, string) =
|
||||||
|
|
||||||
var editorResult = erError
|
var editorResult = erError
|
||||||
let (_, cursorY) = termGetCursorPos(stdout)
|
let (_, cursorY) = termGetCursorPos(stdout)
|
||||||
ed.screenBuffer = newBuffer(0, cursorY, termGetWidth(), termGetHeight() - cursorY, stdout)
|
ed.termBuffer = newBuffer(0, cursorY, termGetWidth(), 1, stdout)
|
||||||
|
ed.textBuffer = newTextBuffer()
|
||||||
|
ed.history.add(ed.textBuffer)
|
||||||
|
ed.historyIndex = ed.history.high()
|
||||||
|
|
||||||
ed.render()
|
|
||||||
while true:
|
while true:
|
||||||
let key = getKey()
|
render(ed.textBuffer, ed.termBuffer, ed.prompt)
|
||||||
case key:
|
let (getKeyResult, key) = getKey()
|
||||||
of jkEnter.int:
|
case getKeyResult:
|
||||||
editorResult = erEnter
|
of gkChar:
|
||||||
break
|
ed.textBuffer.insertRune(key.uint32().Rune())
|
||||||
of jkBackspace.int:
|
of gkControl:
|
||||||
if ed.x == 0:
|
let control = key.JaleKeycode()
|
||||||
discard # merge two lines TODO
|
case control:
|
||||||
elif ed.x > ed.cline().high():
|
of jkEnter:
|
||||||
# TODO UTF8
|
if ed.textBuffer.isCursorAtEnd():
|
||||||
ed.cline() = ed.cline[0..ed.cline().high()-1]
|
editorResult = erEnter
|
||||||
dec ed.x
|
break
|
||||||
dec ed.bx
|
else:
|
||||||
else:
|
ed.textBuffer.enter()
|
||||||
# TODO UTF8
|
of {jkBackspace, jkAltBackspace}:
|
||||||
ed.cline() = ed.cline[0..ed.x-1] & ed.cline[ed.x+1..ed.cline().high()]
|
ed.textBuffer.backspace()
|
||||||
dec ed.x
|
of jkDelete:
|
||||||
dec ed.bx
|
ed.textBuffer.delete()
|
||||||
of jkDelete.int:
|
of jkCtrlC: # ctrl+c
|
||||||
if ed.x > ed.cline.high():
|
editorResult = erCtrlC
|
||||||
discard # merge two lines TODO
|
break
|
||||||
elif ed.x == 0:
|
of jkCtrlD: # ctrl+d
|
||||||
# TODO UTF8
|
if ed.textBuffer.isEmpty():
|
||||||
ed.cline() = ed.cline[1..ed.cline.high()]
|
editorResult = erCtrlD
|
||||||
else:
|
break
|
||||||
# TODO UTF8
|
of jkLeft:
|
||||||
discard # TODO implement
|
ed.textBuffer.moveCursor(-1, 0)
|
||||||
of 3: # ctrl+c
|
of jkRight:
|
||||||
editorResult = erCtrlC
|
ed.textBuffer.moveCursor(1, 0)
|
||||||
break
|
of jkDown:
|
||||||
of 4: # ctrl+d
|
ed.textBuffer.moveCursor(0, 1)
|
||||||
if ed.buffer.len() <= 1 and ed.cline().len() == 0:
|
of jkUp:
|
||||||
editorResult = erCtrlD
|
ed.textBuffer.moveCursor(0, -1)
|
||||||
break
|
of jkEnd:
|
||||||
else:
|
ed.textBuffer.moveToEnd(true)
|
||||||
if key > 31 and key < 127:
|
of jkHome:
|
||||||
# ascii char
|
ed.textBuffer.moveToEnd(false)
|
||||||
let ch = key.char()
|
of jkCtrlDown:
|
||||||
if ed.x == 0:
|
ed.textBuffer.newLine()
|
||||||
ed.cline() = $ch & ed.cline
|
|
||||||
elif ed.x > ed.cline.high():
|
|
||||||
ed.cline() = ed.cline & $ch
|
|
||||||
else:
|
else:
|
||||||
ed.cline() = ed.cline[0..ed.x-1] & $ch & ed.cline[ed.x..ed.cline().high()]
|
discard # not implemented
|
||||||
inc ed.x
|
|
||||||
inc ed.bx
|
|
||||||
|
|
||||||
ed.render()
|
|
||||||
|
|
||||||
# return val
|
# return val
|
||||||
result = (editorResult, ed.buffer.join("\n"))
|
result = (editorResult, ed.textBuffer.getContent())
|
||||||
|
|
||||||
# cleanup
|
# cleanup
|
||||||
stdout.write("\n")
|
stdout.write("\n")
|
||||||
ed.history.add(ed.buffer)
|
ed.termBuffer = nil
|
||||||
ed.buffer = @[""]
|
|
||||||
ed.y = 0
|
|
||||||
ed.x = 0
|
|
||||||
ed.screenBuffer = nil
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import editor
|
import editor
|
||||||
|
|
||||||
let e = newEditor(">>> ", false)
|
let e = newEditor(">>> ")
|
||||||
|
|
||||||
while true:
|
while true:
|
||||||
let (res, text) = e.read()
|
let (res, text) = e.read()
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
# takes a text buffer and a terminal buffer (terminalUtils/buffer)
|
||||||
|
# when render() is called, rewrites the terminal buffer completely as a function of
|
||||||
|
# of the text buffer
|
||||||
|
|
||||||
|
import textBuffer
|
||||||
|
import terminalUtils/buffer
|
||||||
|
import terminalUtils/terminalGetInfo
|
||||||
|
|
||||||
|
proc render*(textBuffer: TextBuffer, termBuffer: Buffer, prompt: string) =
|
||||||
|
# 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 (bufWidth, bufHeight) = termBuffer.getSize()
|
||||||
|
|
||||||
|
|
||||||
|
termBuffer.clearLine()
|
||||||
|
termBuffer.write(prompt)
|
||||||
|
termBuffer.write(textBuffer.getLine())
|
||||||
|
let (x, y) = textBuffer.getCursorPos()
|
||||||
|
termBuffer.setCursorPos(x + prompt.len(), y)
|
||||||
|
termBuffer.redraw()
|
||||||
|
|
|
@ -86,10 +86,7 @@ proc clearLine*(buf: Buffer) =
|
||||||
buf.buffered[i] = Cell(text: Rune(0))
|
buf.buffered[i] = Cell(text: Rune(0))
|
||||||
buf.bufferX = 0
|
buf.bufferX = 0
|
||||||
|
|
||||||
proc write*(buf: Buffer, text: string) =
|
proc write*(buf: Buffer, runes: seq[Rune]) =
|
||||||
# convert text to characters
|
|
||||||
let runes = text.toRunes()
|
|
||||||
|
|
||||||
# if it would go out of the screen, crash
|
# if it would go out of the screen, crash
|
||||||
if buf.bufferX + runes.len() >= buf.width:
|
if buf.bufferX + runes.len() >= buf.width:
|
||||||
raise newException(ValueError, "Text too long, would overflow the buffer.")
|
raise newException(ValueError, "Text too long, would overflow the buffer.")
|
||||||
|
@ -102,6 +99,12 @@ proc write*(buf: Buffer, text: string) =
|
||||||
|
|
||||||
buf.bufferX += runes.len()
|
buf.bufferX += runes.len()
|
||||||
|
|
||||||
|
proc write*(buf: Buffer, text: string) =
|
||||||
|
# convert text to characters
|
||||||
|
let runes = text.toRunes()
|
||||||
|
buf.write(runes)
|
||||||
|
|
||||||
|
|
||||||
proc redraw*(buf: Buffer, force: bool = false) =
|
proc redraw*(buf: Buffer, force: bool = false) =
|
||||||
var toPrint = ""
|
var toPrint = ""
|
||||||
|
|
||||||
|
@ -142,8 +145,16 @@ func getSize*(buf: Buffer): (int, int) =
|
||||||
(buf.width, buf.height)
|
(buf.width, buf.height)
|
||||||
|
|
||||||
proc resize*(buf: Buffer, newX, newY: int) =
|
proc resize*(buf: Buffer, newX, newY: int) =
|
||||||
# TODO
|
# shrinking X
|
||||||
raise newException(Defect, "Not implemented")
|
if newX < buf.width:
|
||||||
|
# dropping cells outside the screen
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
buf.width = newX
|
||||||
|
buf.height = newY
|
||||||
|
|
||||||
func getPosition*(buf: Buffer): (int, int) =
|
func getPosition*(buf: Buffer): (int, int) =
|
||||||
(buf.positionX, buf.positionY)
|
(buf.positionX, buf.positionY)
|
||||||
|
|
|
@ -3,49 +3,34 @@
|
||||||
import tables
|
import tables
|
||||||
import strutils
|
import strutils
|
||||||
import terminal
|
import terminal
|
||||||
|
import unicode
|
||||||
|
|
||||||
type
|
type
|
||||||
JaleKeycode* = enum
|
JaleKeycode* = enum
|
||||||
jkStart = 255 # jale keycodes exported start one above jkStart
|
jkCtrlA = 1, jkCtrlB, jkCtrlC, jkCtrlD, jkCtrlE,
|
||||||
# arrow keys
|
jkCtrlF, jkCtrlG, # ctrl+h invalid
|
||||||
|
jkBackspace = 8,
|
||||||
|
jkTab = 9,
|
||||||
|
jkCtrlJ = 10, jkCtrlK, jkCtrlL,
|
||||||
|
jkEnter = 13,
|
||||||
|
jkCtrlN = 14, jkCtrlO, jkCtrlP, jkCtrlQ,
|
||||||
|
jkCtrlR, jkCtrlS, jkCtrlT, jkCtrlU, jkCtrlV,
|
||||||
|
jkCtrlW, jkCtrlX, jkCtrlY, jkCtrlZ,
|
||||||
|
jkEscape = 27,
|
||||||
|
jkCtrlBackslash = 28,
|
||||||
|
jkCtrlRightBracket = 29,
|
||||||
|
jkAltBackspace = 127,
|
||||||
|
|
||||||
jkLeft = 256, jkRight, jkUp, jkDown,
|
jkLeft = 256, jkRight, jkUp, jkDown,
|
||||||
jkCtrlLeft, jkCtrlRight, jkCtrlUp, jkCtrlDown,
|
jkCtrlLeft, jkCtrlRight, jkCtrlUp, jkCtrlDown,
|
||||||
# other 4 move keys
|
# other 4 move keys
|
||||||
jkHome, jkEnd, jkPageUp, jkPageDown,
|
jkHome, jkEnd, jkPageUp, jkPageDown,
|
||||||
jkCtrlHome, jkCtrlEnd, jkCtrlPageUp, jkCtrlPageDown,
|
jkCtrlHome, jkCtrlEnd, jkCtrlPageUp, jkCtrlPageDown,
|
||||||
# special keys
|
# special keys
|
||||||
jkDelete, jkBackspace, jkInsert, jkEnter,
|
jkDelete, jkInsert,
|
||||||
|
|
||||||
jkFinish, # jale keycodes exported end one below jkFinish
|
|
||||||
# non-exported jale keycodes come here:
|
|
||||||
jkContinue # represents an unfinished escape sequence
|
jkContinue # represents an unfinished escape sequence
|
||||||
|
|
||||||
var keysById*: Table[int, string]
|
|
||||||
var keysByName*: Table[string, int]
|
|
||||||
|
|
||||||
block:
|
|
||||||
# 1 - 26
|
|
||||||
for i in countup(1, 26): # iterate through lowercase letters
|
|
||||||
keysById[i] = "ctrl+" & $char(i + 96)
|
|
||||||
|
|
||||||
keysById[9] = "tab"
|
|
||||||
# keysById[8] will never get triggered because it's an escape seq
|
|
||||||
|
|
||||||
keysById[28] = r"ctrl+\"
|
|
||||||
keysById[29] = "ctrl+]"
|
|
||||||
keysByName[r"ctrl+\"] = 28
|
|
||||||
keysByName["ctrl+]"] = 29
|
|
||||||
|
|
||||||
for i in countup(1, 26):
|
|
||||||
keysByName[keysById[i]] = i
|
|
||||||
|
|
||||||
# jale keycodes:
|
|
||||||
for i in countup(int(jkStart) + 1, int(jkFinish) - 1):
|
|
||||||
var name: string = ($JaleKeycode(i))
|
|
||||||
name = name[2..name.high()].toLower()
|
|
||||||
keysByName[name] = i
|
|
||||||
keysById[i] = name
|
|
||||||
|
|
||||||
var escapeSeqs*: Table[int, JaleKeycode]
|
var escapeSeqs*: Table[int, JaleKeycode]
|
||||||
|
|
||||||
proc defEscSeq(keys: seq[int], id: JaleKeycode) =
|
proc defEscSeq(keys: seq[int], id: JaleKeycode) =
|
||||||
|
@ -58,6 +43,8 @@ proc defEscSeq(keys: seq[int], id: JaleKeycode) =
|
||||||
escapeSeqs[result] = id
|
escapeSeqs[result] = id
|
||||||
|
|
||||||
block:
|
block:
|
||||||
|
defEscSeq(@[127], jkAltBackspace)
|
||||||
|
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
defEscSeq(@[224], jkContinue)
|
defEscSeq(@[224], jkContinue)
|
||||||
|
|
||||||
|
@ -83,8 +70,6 @@ block:
|
||||||
defEscSeq(@[224, 117], jkCtrlEnd)
|
defEscSeq(@[224, 117], jkCtrlEnd)
|
||||||
|
|
||||||
# special keys
|
# special keys
|
||||||
defEscSeq(@[8], jkBackspace)
|
|
||||||
defEscSeq(@[13], jkEnter)
|
|
||||||
defEscSeq(@[224, 82], jkInsert)
|
defEscSeq(@[224, 82], jkInsert)
|
||||||
defEscSeq(@[224, 83], jkDelete)
|
defEscSeq(@[224, 83], jkDelete)
|
||||||
else:
|
else:
|
||||||
|
@ -154,24 +139,88 @@ block:
|
||||||
defEscSeq(@[27, 91, 50], jkContinue)
|
defEscSeq(@[27, 91, 50], jkContinue)
|
||||||
defEscSeq(@[27, 91, 51, 126], jkDelete)
|
defEscSeq(@[27, 91, 51, 126], jkDelete)
|
||||||
defEscSeq(@[27, 91, 50, 126], jkInsert)
|
defEscSeq(@[27, 91, 50, 126], jkInsert)
|
||||||
defEscSeq(@[13], jkEnter)
|
|
||||||
defEscSeq(@[127], jkBackspace)
|
|
||||||
defEscSeq(@[8], jkBackspace)
|
|
||||||
|
|
||||||
|
type IsKeyUTF8Results = enum
|
||||||
|
ikValid, ikUnfinished, ikOverlong, ikInvalid2,
|
||||||
|
ikInvalid3, ikInvalid4, ikInvalidUnknown
|
||||||
|
|
||||||
proc getChar: int =
|
func isContinuation(singleByte: uint8): bool =
|
||||||
getch().int
|
singleByte shr 6 == 0b10
|
||||||
|
|
||||||
proc getKey*: int =
|
func isValidUTF8(key: int): IsKeyUTF8Results =
|
||||||
# TODO unicode handling
|
const L = sizeof(int)
|
||||||
|
var bytes: array[L, uint8]
|
||||||
|
var key = key
|
||||||
|
var i = L - 1
|
||||||
|
|
||||||
|
# to not make any assumptions about how int is
|
||||||
|
# stored in memory
|
||||||
|
while key > 0:
|
||||||
|
bytes[i] = (key mod 256).uint8
|
||||||
|
key = key div 256
|
||||||
|
dec i
|
||||||
|
|
||||||
|
# https://github.com/nim-lang/Nim/blob/version-1-6/lib/pure/unicode.nim#L166
|
||||||
|
i = 0
|
||||||
|
while i < L:
|
||||||
|
if bytes[i] <= 127:
|
||||||
|
inc i
|
||||||
|
elif bytes[i] shr 5 == 0b110:
|
||||||
|
# 2 long
|
||||||
|
if bytes[i] < 0xc2: return ikOverlong # overlong
|
||||||
|
elif i + 1 >= L: return ikUnfinished
|
||||||
|
elif bytes[i+1].isContinuation: i += 2
|
||||||
|
else: return ikInvalid2
|
||||||
|
elif bytes[i] shr 4 == 0b1110:
|
||||||
|
# 3 long
|
||||||
|
if i + 2 >= L: return ikUnfinished
|
||||||
|
elif bytes[i+1].isContinuation() and bytes[i+2].isContinuation():
|
||||||
|
i += 3
|
||||||
|
else: return ikInvalid3
|
||||||
|
elif bytes[i] shr 3 == 0b11110:
|
||||||
|
# 4 long
|
||||||
|
if i+3 >= L: return ikUnfinished
|
||||||
|
elif bytes[i+1].isContinuation() and
|
||||||
|
bytes[i+2].isContinuation() and
|
||||||
|
bytes[i+3].isContinuation():
|
||||||
|
i += 4
|
||||||
|
else: return ikInvalid4
|
||||||
|
else:
|
||||||
|
return ikInvalidUnknown
|
||||||
|
return ikValid
|
||||||
|
|
||||||
|
type
|
||||||
|
GetKeyResult* = enum
|
||||||
|
gkControl, gkChar
|
||||||
|
|
||||||
|
proc getKey*: (GetKeyResult, int) =
|
||||||
|
# TODO move away from int approach completely
|
||||||
var key = 0
|
var key = 0
|
||||||
|
var bytes: string
|
||||||
while true:
|
while true:
|
||||||
key *= 256
|
key *= 256
|
||||||
key += getChar()
|
let newChar = getch()
|
||||||
|
key += newChar.int()
|
||||||
|
bytes &= newChar
|
||||||
if escapeSeqs.hasKey(key):
|
if escapeSeqs.hasKey(key):
|
||||||
if escapeSeqs[key] != jkContinue:
|
if escapeSeqs[key] != jkContinue:
|
||||||
key = escapeSeqs[key].int()
|
key = escapeSeqs[key].int()
|
||||||
break
|
return (gkControl, key)
|
||||||
|
elif key < 30 and key > 0:
|
||||||
|
# JaleKeycode has values from 1 to 29
|
||||||
|
# which are NOT in escapeSeqs to save typing
|
||||||
|
return (gkControl, key)
|
||||||
else:
|
else:
|
||||||
break
|
let validity = isValidUTF8(key)
|
||||||
return key
|
case validity:
|
||||||
|
of ikValid:
|
||||||
|
# TODO: find a better alternative to runeAt
|
||||||
|
return (gkChar, bytes.runeAt(0).int)
|
||||||
|
of ikUnfinished:
|
||||||
|
discard # continue looping
|
||||||
|
else:
|
||||||
|
# For now, in development builds, crash
|
||||||
|
# later it might be wise to just ignore
|
||||||
|
# bad bytes, might check what other line
|
||||||
|
# editors do
|
||||||
|
raise newException(ValueError, "Invalid UTF8 input, " & $validity)
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
# 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():
|
||||||
|
discard # merge two lines TODO
|
||||||
|
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:
|
||||||
|
discard # merge two lines TODO
|
||||||
|
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()
|
||||||
|
|
||||||
|
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
|
||||||
|
buf.content.insert(@[], buf.cursorY)
|
||||||
|
buf.cursorX = 0
|
||||||
|
|
||||||
|
proc getLineCount*(buf: TextBuffer): int =
|
||||||
|
buf.content.len()
|
Loading…
Reference in New Issue