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:
parent
ad508cd9f3
commit
3dd77ea1d9
|
@ -1,2 +1,3 @@
|
|||
main
|
||||
examples/interactive
|
||||
examples/editor
|
||||
getkey
|
||||
|
|
49
defaults.nim
49
defaults.nim
|
@ -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)
|
||||
|
67
editor.nim
67
editor.nim
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
@ -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") & ">"
|
||||
|
32
history.nim
32
history.nim
|
@ -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
|
||||
|
16
line.nim
16
line.nim
|
@ -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 = ""
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import history
|
||||
import ../editor
|
||||
import ../multiline
|
||||
|
||||
|
|
@ -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
|
||||
|
Loading…
Reference in New Issue