From 3dd77ea1d9d634325a384c7f416400bbd596af33 Mon Sep 17 00:00:00 2001 From: Productive2 <48047721+Productive2@users.noreply.github.com> Date: Thu, 18 Feb 2021 21:31:03 +0100 Subject: [PATCH] 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) --- .gitignore | 3 +- defaults.nim | 49 ----------------- editor.nim | 67 +++++++++-------------- examples/editor.nim | 12 +++++ main.nim => examples/interactive.nim | 12 ++--- history.nim | 32 ----------- line.nim | 16 ++++-- multiline.nim | 30 +++++++++-- plugin/defaults.nim | 65 ++++++++++++++++++++++ plugin/editor_history.nim | 5 ++ plugin/history.nim | 80 ++++++++++++++++++++++++++++ 11 files changed, 232 insertions(+), 139 deletions(-) delete mode 100644 defaults.nim create mode 100644 examples/editor.nim rename main.nim => examples/interactive.nim (56%) delete mode 100644 history.nim create mode 100644 plugin/defaults.nim create mode 100644 plugin/editor_history.nim create mode 100644 plugin/history.nim diff --git a/.gitignore b/.gitignore index 26457b5..f8cd68c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -main +examples/interactive +examples/editor getkey diff --git a/defaults.nim b/defaults.nim deleted file mode 100644 index d0c3941..0000000 --- a/defaults.nim +++ /dev/null @@ -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) - diff --git a/editor.nim b/editor.nim index 5d8ef2e..772ba10 100644 --- a/editor.nim +++ b/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() diff --git a/examples/editor.nim b/examples/editor.nim new file mode 100644 index 0000000..a4b54ba --- /dev/null +++ b/examples/editor.nim @@ -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() + diff --git a/main.nim b/examples/interactive.nim similarity index 56% rename from main.nim rename to examples/interactive.nim index a3920cf..7c45664 100644 --- a/main.nim +++ b/examples/interactive.nim @@ -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") & ">" diff --git a/history.nim b/history.nim deleted file mode 100644 index 96ed9cc..0000000 --- a/history.nim +++ /dev/null @@ -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 - diff --git a/line.nim b/line.nim index d14902a..38972bc 100644 --- a/line.nim +++ b/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 = "" diff --git a/multiline.nim b/multiline.nim index 2a1cd71..0216ab4 100644 --- a/multiline.nim +++ b/multiline.nim @@ -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) diff --git a/plugin/defaults.nim b/plugin/defaults.nim new file mode 100644 index 0000000..5fa15ea --- /dev/null +++ b/plugin/defaults.nim @@ -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() + diff --git a/plugin/editor_history.nim b/plugin/editor_history.nim new file mode 100644 index 0000000..db7c344 --- /dev/null +++ b/plugin/editor_history.nim @@ -0,0 +1,5 @@ +import history +import ../editor +import ../multiline + + diff --git a/plugin/history.nim b/plugin/history.nim new file mode 100644 index 0000000..77dfe18 --- /dev/null +++ b/plugin/history.nim @@ -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 +