Creation of plugins

moved defaults to plugins, cleaned it a bit up
moved all history related things to plugins
history wip
changed defaults
created examples dir, added multiline single file editing example (no
file working yet)
This commit is contained in:
Productive2 2021-02-18 21:31:03 +01:00
parent ad508cd9f3
commit 3dd77ea1d9
11 changed files with 232 additions and 139 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
main
examples/interactive
examples/editor
getkey

View File

@ -1,49 +0,0 @@
import editor
import keycodes
import multiline
import event
import tables
import templates
proc populateDefaults*(editor: LineEditor) =
editor.bindEvent(jeKeypress):
if editor.lastKeystroke > 31 and editor.lastKeystroke < 127:
let ch = char(editor.lastKeystroke)
editor.content.insert($ch)
editor.bindKey("left"):
editor.content.left()
editor.bindKey("right"):
editor.content.right()
editor.bindKey("up"):
editor.content.up()
editor.bindKey("down"):
editor.content.down()
editor.bindKey("home"):
editor.content.home()
editor.bindKey("end"):
editor.content.`end`()
editor.bindKey("pageup"):
editor.content.vhome()
editor.bindKey("pagedown"):
editor.content.vend()
editor.bindKey("backspace"):
editor.content.backspace()
editor.bindKey("delete"):
editor.content.delete()
editor.bindKey("enter"):
if editor.content.Y() == editor.content.high() and editor.content.getLine(editor.content.high()) == "":
editor.finish()
else:
editor.content.enter()
editor.bindKey("ctrl+c"):
editor.finish()
editor.events.call(jeQuit)
editor.bindKey("ctrl+d"):
if editor.content.getContent() == "":
editor.finish()
editor.events.call(jeQuit)
editor.bindKey("shiftup"):
editor.historyMove(-1)
editor.bindKey("shiftdown"):
editor.historyMove(1)

View File

@ -11,18 +11,23 @@ import renderer
type
JaleEvent* = enum
jeKeypress, jeQuit, jeFinish, jeHistoryChange
jeKeypress, jeQuit, jeFinish, jePreRead, jePostRead
LineEditor* = ref object
content*: Multiline
historyIndex*: int # TODO to be private
history*: seq[Multiline] # TODO to be private
# permanents
keystrokes*: Event[int]
events*: Event[JaleEvent]
prompt*: string
# permanent internals: none
# per-read contents
content*: Multiline
lastKeystroke*: int
# per-read internals
finished: bool
rendered: int # how many lines were printed last full refresh
forceRedraw: bool
# getter/setter sorts
@ -34,48 +39,29 @@ proc finish*(le: LineEditor) =
# can be overwritten to false, inside the event
le.events.call(jeFinish)
proc forceRedraw*(le: LineEditor) =
le.forceRedraw = true
# constructor
proc newLineEditor*: LineEditor =
new(result)
result.content = newMultiline()
result.history = @[]
result.history.add(result.content)
result.historyIndex = 0
result.keystrokes.new()
result.events.new()
result.prompt = ""
result.rendered = 0
result.lastKeystroke = -1
result.forceRedraw = false
# priv/pub methods
proc historyMove*(editor: LineEditor, delta: int) =
# broken behaviour: history should not be modified, it should clone
# the modified one that is eventually submitted and add it to the end
# TODO
if editor.historyIndex + delta < 0:
editor.content = editor.history[0]
editor.historyIndex = 0
elif editor.historyIndex + delta >= editor.history.high():
editor.content = editor.history[editor.history.high()]
editor.historyIndex = editor.history.high()
else:
editor.content = editor.history[editor.historyIndex + delta]
editor.historyIndex += delta
editor.events.call(jeHistoryChange)
proc reset(editor: LineEditor) =
editor.unfinish()
editor.rendered = 0
proc flush*(editor: LineEditor) =
# kinda like reset, it moves the current element one down in history
# and adds a new one
editor.reset()
editor.content = newMultiline()
editor.history.add(editor.content)
editor.historyIndex = editor.history.high()
editor.lastKeystroke = -1
editor.forceRedraw = false
proc render(editor: LineEditor, line: int = -1, hscroll: bool = true) =
var y = line
@ -122,18 +108,13 @@ proc moveCursorToEnd(editor: LineEditor) =
cursorDown(editor.content.high() - editor.content.Y)
write stdout, "\n"
# TODO don't use globals, but allow for event removal
var histchange = false
proc changeHistory =
histchange = true
proc read*(editor: LineEditor): string =
editor.events.call(jePreRead)
# starts at the top, full render moves it into the right y
editor.fullRender()
# TODO: must remove event at end
# otherwise there'll be more instances of the same
editor.events.add(jeHistoryChange, changeHistory)
while not editor.finished:
@ -149,17 +130,19 @@ proc read*(editor: LineEditor): string =
editor.keystrokes.call(key)
editor.events.call(jeKeypress)
# redraw everything if y changed
if histchange or preY != editor.content.Y:
if editor.forceRedraw or preY != editor.content.Y:
# move to the top
if preY > 0:
cursorUp(preY)
# move to the right y
editor.fullRender()
if histchange:
histchange = false
if editor.forceRedraw:
editor.forceRedraw = false
editor.events.call(jePostRead)
# move cursor to end
editor.moveCursorToEnd()
editor.reset()
return editor.content.getContent()

12
examples/editor.nim Normal file
View File

@ -0,0 +1,12 @@
import ../plugin/defaults
import ../editor
import ../strutils
import ../templates
import terminal
let e = newLineEditor()
e.prompt = ""
e.populateDefaults(enterSubmits = false)
let input = e.read()

View File

@ -1,7 +1,7 @@
import defaults
import editor
import strutils
import templates
import ../plugin/defaults
import ../editor
import ../strutils
import ../templates
var keep = true
@ -12,10 +12,8 @@ e.bindEvent(jeQuit):
e.prompt = "> "
e.populateDefaults()
while keep:
let input = e.read()
echo "history index: " & $e.historyIndex
e.flush()
echo "history len: " & $e.history.len()
echo "output:<" & input.replace("\n", "\\n") & ">"

View File

@ -1,32 +0,0 @@
# history.nim
import multiline
type HistoryElement* = ref object
original*: Multiline
current*: Multiline
type History* = ref object
elements: seq[HistoryElement]
index: int
lowestTouchedIndex: int
proc newHistoryElement*(og: Multiline): HistoryElement =
new(result)
result.original = og
new(result.current) # TODO deepcopy
proc delta*(h: History, amt: int): Multiline =
discard # move up/down in history and return reference to current
# also update lowest touched index
proc clean*(h: History) =
discard # restore originals to current
# from lowest touched index to the top
proc save*(h: History, path: string) =
discard # convert it to string, save
proc loadHistory*(path: string): History =
discard # create new history from file

View File

@ -3,7 +3,7 @@
import strformat
type
Line* = ref object
Line* = object
content: string
# getter
@ -16,9 +16,15 @@ proc content*(line: Line): string =
proc newLine*: Line =
Line(content: "")
proc newLine*(str: string): Line =
Line(content: str)
proc copy*(l: Line): Line =
Line(content: l.content)
# methods
proc insert*(line: Line, str: string, pos: int) =
proc insert*(line: var Line, str: string, pos: int) =
if pos > line.content.high():
line.content &= str
elif pos == 0:
@ -26,7 +32,7 @@ proc insert*(line: Line, str: string, pos: int) =
else:
line.content = line.content[0..pos-1] & str & line.content[pos..line.content.high()]
proc delete*(line: Line, start: int, finish: int) =
proc delete*(line: var Line, start: int, finish: int) =
if start > finish or start < 0 or finish > line.content.high():
raise newException(CatchableError, &"Invalid arguments for Line.delete: start {start}, finish {finish} for line of length {line.content.len()}")
var result = ""
@ -36,7 +42,7 @@ proc delete*(line: Line, start: int, finish: int) =
result &= line.content[finish+1..line.content.high()]
line.content = result
proc range*(line: Line, start: int, finish: int): string =
proc range*(line: var Line, start: int, finish: int): string =
if start > finish or start < 0 or finish > line.content.high():
raise newException(CatchableError, &"Invalid arguments for Line.range: start {start}, finish {finish} for line of length {line.content.len()}")
result = line.content[start..finish]
@ -47,5 +53,5 @@ proc len*(line: Line): int =
proc high*(line: Line): int =
line.content.high()
proc clearLine*(line: Line) =
proc clearLine*(line: var Line) =
line.content = ""

View File

@ -2,6 +2,8 @@
import line
import strutils
type
Multiline* = ref object
lines: seq[Line]
@ -24,6 +26,13 @@ proc newMultiline*: Multiline =
result.x = 0
result.y = 0
proc copy*(ml: Multiline): Multiline =
new(result)
for l in ml.lines:
result.lines.add(l.copy())
result.x = ml.x
result.y = ml.y
# methods
proc lineLen*(ml: Multiline): int =
@ -139,8 +148,23 @@ proc getLine*(ml: Multiline, line: int = -1): string =
else:
""
proc getContent*(ml: Multiline): string =
proc serialize*(ml: Multiline, sep: string = r"\n", replaceBS: bool = true): string =
# replaceBS = replace backslash
for line in ml.lines:
result &= line.content & "\n"
result[0..result.high()-1] # cut finishing newline
if replaceBS:
result &= line.content.replace(r"\", r"\\") & sep
else:
result &= line.content & sep
result[0..result.high() - sep.len()]
proc deserialize*(str: string, sep: string = r"\n"): Multiline =
result = newMultiline()
for line in str.split(sep):
result.lines.add(newLine(line.replace(r"\\", r"\")))
result.y = result.high()
result.x = result.lineLen()
proc getContent*(ml: Multiline): string =
ml.serialize(sep = "\n", replaceBS = false)

65
plugin/defaults.nim Normal file
View File

@ -0,0 +1,65 @@
import ../editor
import ../keycodes
import ../multiline
import ../event
import ../templates
import tables
proc bindInput*(editor: LineEditor) =
editor.bindEvent(jeKeypress):
if editor.lastKeystroke > 31 and editor.lastKeystroke < 127:
let ch = char(editor.lastKeystroke)
editor.content.insert($ch)
proc bindTerminate*(editor: LineEditor) =
editor.bindKey("ctrl+c"):
editor.finish()
editor.bindKey("ctrl+d"):
if editor.content.getContent() == "":
editor.finish()
proc populateDefaults*(editor: LineEditor, enterSubmits = true, shiftForVerticalMove = true) =
editor.bindInput()
editor.bindTerminate()
editor.bindKey("left"):
editor.content.left()
editor.bindKey("right"):
editor.content.right()
if shiftForVerticalMove:
editor.bindKey("shiftup"):
editor.content.up()
editor.bindKey("shiftdown"):
editor.content.down()
editor.bindKey("ctrlpageup"):
editor.content.vhome()
editor.bindKey("ctrlpagedown"):
editor.content.vend()
else:
editor.bindKey("up"):
editor.content.up()
editor.bindKey("down"):
editor.content.down()
editor.bindKey("pageup"):
editor.content.vhome()
editor.bindKey("pagedown"):
editor.content.vend()
editor.bindKey("home"):
editor.content.home()
editor.bindKey("end"):
editor.content.`end`()
editor.bindKey("backspace"):
editor.content.backspace()
editor.bindKey("delete"):
editor.content.delete()
if enterSubmits:
editor.bindKey("ctrldown"):
editor.content.enter()
editor.bindKey("enter"):
editor.finish()
else:
editor.bindKey("enter"):
editor.content.enter()

View File

@ -0,0 +1,5 @@
import history
import ../editor
import ../multiline

80
plugin/history.nim Normal file
View File

@ -0,0 +1,80 @@
# history.nim
import ../multiline
import os
type HistoryElement* = ref object
original*: Multiline
current*: Multiline
temp: bool
type History* = ref object
elements: seq[HistoryElement]
index: int
lowestTouchedIndex: int
proc newHistory*: History =
new(result)
result.index = 0
result.lowestTouchedIndex = 0
result.elements = @[]
proc delta*(h: History, amt: int): Multiline =
# move up/down in history and return reference to current
# also update lowest touched index
if h.index + amt <= 0:
h.index = 0
elif h.index + amt >= h.elements.high():
h.index = h.elements.high()
else:
h.index += amt
if h.lowestTouchedIndex > h.index:
h.lowestTouchedIndex = h.index
h.elements[h.index]
proc clean*(h: History) =
# restore originals to current
# from lowest touched index to the top
if h.lowestTouchedIndex <= h.elements.high():
for i in countup(h.lowestTouchedIndex, h.elements.high()):
if h.elements[i].temp:
h.elements.delete(i)
else:
h.elements[i].current = h.elements[i].original.copy()
h.lowestTouchedIndex = h.elements.len()
proc newEntry*(h: History, ml: Multiline, temp: bool = false) =
h.elements.add(History(original: ml, current: ml.copy(), temp: temp))
proc save*(h: History, path: string) =
# discards currents and temps, only saves originals
if dirExists(path):
raise newException(CatchableError, "Attempt to save history to " & path & ", but a directory at that path exists")
let file = open(path, fmWrite)
for el in h.elements:
if el.temp:
continue
file.writeLine(el.original.serialize())
file.close()
proc loadHistory*(path: string): History =
if not fileExists(path):
raise newException(CatchableError, "Attempt to read history from a non-existant file")
let file = open(path, fmRead)
let h = newHistory()
var line: string
while readLine(file, line):
h.newEntry(line.deserialize())
file.close()
return h