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 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 type
JaleEvent* = enum JaleEvent* = enum
jeKeypress, jeQuit, jeFinish, jeHistoryChange jeKeypress, jeQuit, jeFinish, jePreRead, jePostRead
LineEditor* = ref object LineEditor* = ref object
content*: Multiline # permanents
historyIndex*: int # TODO to be private
history*: seq[Multiline] # TODO to be private
keystrokes*: Event[int] keystrokes*: Event[int]
events*: Event[JaleEvent] events*: Event[JaleEvent]
prompt*: string prompt*: string
# permanent internals: none
# per-read contents
content*: Multiline
lastKeystroke*: int lastKeystroke*: int
# per-read internals
finished: bool finished: bool
rendered: int # how many lines were printed last full refresh rendered: int # how many lines were printed last full refresh
forceRedraw: bool
# getter/setter sorts # getter/setter sorts
@ -34,48 +39,29 @@ proc finish*(le: LineEditor) =
# can be overwritten to false, inside the event # can be overwritten to false, inside the event
le.events.call(jeFinish) le.events.call(jeFinish)
proc forceRedraw*(le: LineEditor) =
le.forceRedraw = true
# constructor # constructor
proc newLineEditor*: LineEditor = proc newLineEditor*: LineEditor =
new(result) new(result)
result.content = newMultiline() result.content = newMultiline()
result.history = @[]
result.history.add(result.content)
result.historyIndex = 0
result.keystrokes.new() result.keystrokes.new()
result.events.new() result.events.new()
result.prompt = "" result.prompt = ""
result.rendered = 0 result.rendered = 0
result.lastKeystroke = -1
result.forceRedraw = false
# priv/pub methods # 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) = proc reset(editor: LineEditor) =
editor.unfinish() editor.unfinish()
editor.rendered = 0 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.content = newMultiline()
editor.history.add(editor.content) editor.lastKeystroke = -1
editor.historyIndex = editor.history.high() editor.forceRedraw = false
proc render(editor: LineEditor, line: int = -1, hscroll: bool = true) = proc render(editor: LineEditor, line: int = -1, hscroll: bool = true) =
var y = line var y = line
@ -122,18 +108,13 @@ proc moveCursorToEnd(editor: LineEditor) =
cursorDown(editor.content.high() - editor.content.Y) cursorDown(editor.content.high() - editor.content.Y)
write stdout, "\n" 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 = proc read*(editor: LineEditor): string =
editor.events.call(jePreRead)
# starts at the top, full render moves it into the right y # starts at the top, full render moves it into the right y
editor.fullRender() 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: while not editor.finished:
@ -149,17 +130,19 @@ proc read*(editor: LineEditor): string =
editor.keystrokes.call(key) editor.keystrokes.call(key)
editor.events.call(jeKeypress) editor.events.call(jeKeypress)
# redraw everything if y changed # redraw everything if y changed
if histchange or preY != editor.content.Y: if editor.forceRedraw or preY != editor.content.Y:
# move to the top # move to the top
if preY > 0: if preY > 0:
cursorUp(preY) cursorUp(preY)
# move to the right y # move to the right y
editor.fullRender() editor.fullRender()
if histchange: if editor.forceRedraw:
histchange = false editor.forceRedraw = false
editor.events.call(jePostRead)
# move cursor to end # move cursor to end
editor.moveCursorToEnd() editor.moveCursorToEnd()
editor.reset() editor.reset()
return editor.content.getContent() 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 ../plugin/defaults
import editor import ../editor
import strutils import ../strutils
import templates import ../templates
var keep = true var keep = true
@ -12,10 +12,8 @@ e.bindEvent(jeQuit):
e.prompt = "> " e.prompt = "> "
e.populateDefaults() e.populateDefaults()
while keep: while keep:
let input = e.read() let input = e.read()
echo "history index: " & $e.historyIndex
e.flush()
echo "history len: " & $e.history.len()
echo "output:<" & input.replace("\n", "\\n") & ">" 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 import strformat
type type
Line* = ref object Line* = object
content: string content: string
# getter # getter
@ -16,9 +16,15 @@ proc content*(line: Line): string =
proc newLine*: Line = proc newLine*: Line =
Line(content: "") Line(content: "")
proc newLine*(str: string): Line =
Line(content: str)
proc copy*(l: Line): Line =
Line(content: l.content)
# methods # methods
proc insert*(line: Line, str: string, pos: int) = proc insert*(line: var Line, str: string, pos: int) =
if pos > line.content.high(): if pos > line.content.high():
line.content &= str line.content &= str
elif pos == 0: elif pos == 0:
@ -26,7 +32,7 @@ proc insert*(line: Line, str: string, pos: int) =
else: else:
line.content = line.content[0..pos-1] & str & line.content[pos..line.content.high()] 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(): 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()}") raise newException(CatchableError, &"Invalid arguments for Line.delete: start {start}, finish {finish} for line of length {line.content.len()}")
var result = "" var result = ""
@ -36,7 +42,7 @@ proc delete*(line: Line, start: int, finish: int) =
result &= line.content[finish+1..line.content.high()] result &= line.content[finish+1..line.content.high()]
line.content = result 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(): 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()}") raise newException(CatchableError, &"Invalid arguments for Line.range: start {start}, finish {finish} for line of length {line.content.len()}")
result = line.content[start..finish] result = line.content[start..finish]
@ -47,5 +53,5 @@ proc len*(line: Line): int =
proc high*(line: Line): int = proc high*(line: Line): int =
line.content.high() line.content.high()
proc clearLine*(line: Line) = proc clearLine*(line: var Line) =
line.content = "" line.content = ""

View File

@ -2,6 +2,8 @@
import line import line
import strutils
type type
Multiline* = ref object Multiline* = ref object
lines: seq[Line] lines: seq[Line]
@ -24,6 +26,13 @@ proc newMultiline*: Multiline =
result.x = 0 result.x = 0
result.y = 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 # methods
proc lineLen*(ml: Multiline): int = proc lineLen*(ml: Multiline): int =
@ -139,8 +148,23 @@ proc getLine*(ml: Multiline, line: int = -1): string =
else: 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: for line in ml.lines:
result &= line.content & "\n" if replaceBS:
result[0..result.high()-1] # cut finishing newline 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