From ed00e87415c2bac1ff70b705f6e7c380f7b69123 Mon Sep 17 00:00:00 2001 From: Productive2 <48047721+Productive2@users.noreply.github.com> Date: Thu, 18 Feb 2021 23:58:47 +0100 Subject: [PATCH] History plugin finished not extensively tested yet --- .gitignore | 3 +- editor.nim | 19 ++++--- ...{interactive.nim => interactive_basic.nim} | 0 examples/interactive_history.nim | 23 +++++++++ plugin/editor_history.nim | 50 +++++++++++++++++++ plugin/history.nim | 29 +++++++++-- 6 files changed, 112 insertions(+), 12 deletions(-) rename examples/{interactive.nim => interactive_basic.nim} (100%) create mode 100644 examples/interactive_history.nim diff --git a/.gitignore b/.gitignore index f8cd68c..eb0650a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -examples/interactive examples/editor +examples/interactive_basic +examples/interactive_history getkey diff --git a/editor.nim b/editor.nim index 99d5181..1773775 100644 --- a/editor.nim +++ b/editor.nim @@ -13,6 +13,9 @@ type JaleEvent* = enum jeKeypress, jeQuit, jeFinish, jePreRead, jePostRead + EditorState = enum + esOutside, esTyping, esFinishing, esQuitting + LineEditor* = ref object # permanents keystrokes*: Event[int] @@ -25,22 +28,22 @@ type content*: Multiline lastKeystroke*: int # per-read internals - finished: bool + state: EditorState rendered: int # how many lines were printed last full refresh forceRedraw: bool # getter/setter sorts proc unfinish*(le: LineEditor) = - le.finished = false + le.state = esTyping proc finish*(le: LineEditor) = - le.finished = true + le.state = esFinishing # can be overwritten to false, inside the event le.events.call(jeFinish) proc quit*(le: LineEditor) = - le.finished = true + le.state = esQuitting le.events.call(jeQuit) proc forceRedraw*(le: LineEditor) = @@ -57,11 +60,14 @@ proc newLineEditor*: LineEditor = result.rendered = 0 result.lastKeystroke = -1 result.forceRedraw = false + result.state = esOutside # priv/pub methods proc reset(editor: LineEditor) = - editor.unfinish() + ## Resets state to outside, resets internal rendering details + ## resets last keystroke, creates new contents + editor.state = esOutside editor.rendered = 0 editor.content = newMultiline() editor.lastKeystroke = -1 @@ -114,12 +120,13 @@ proc moveCursorToEnd(editor: LineEditor) = proc read*(editor: LineEditor): string = + editor.state = esTyping editor.events.call(jePreRead) # starts at the top, full render moves it into the right y editor.fullRender() - while not editor.finished: + while editor.state == esTyping: # refresh current line every time editor.render() diff --git a/examples/interactive.nim b/examples/interactive_basic.nim similarity index 100% rename from examples/interactive.nim rename to examples/interactive_basic.nim diff --git a/examples/interactive_history.nim b/examples/interactive_history.nim new file mode 100644 index 0000000..c4ab374 --- /dev/null +++ b/examples/interactive_history.nim @@ -0,0 +1,23 @@ +import ../plugin/defaults +import ../plugin/history +import ../plugin/editor_history +import ../editor +import ../strutils +import ../templates + +var keep = true + +let e = newLineEditor() + +e.bindEvent(jeQuit): + keep = false + +e.prompt = "> " +e.populateDefaults() +let h = e.plugHistory() +e.bindHistory(h) + +while keep: + let input = e.read() + echo "output:<" & input.replace("\n", "\\n") & ">" + diff --git a/plugin/editor_history.nim b/plugin/editor_history.nim index db7c344..ba82c6a 100644 --- a/plugin/editor_history.nim +++ b/plugin/editor_history.nim @@ -1,5 +1,55 @@ import history import ../editor import ../multiline +import ../templates + +import options + +proc plugHistory*(ed: LineEditor): History = + # adds hooks to events + # after reading finished, it adds to history + # before reading, it adds the temporary input to the history + let hist = newHistory() + ed.bindEvent(jeFinish): + hist.clean() + hist.newEntry(ed.content) + + ed.bindEvent(jePreRead): + hist.newEntry(ed.content, temp = true) + discard hist.toEnd() + + return hist +proc bindHistory*(ed: LineEditor, h: History, useShift: bool = false) = + ## Adds history keybindings to editor (up, down, pg up/down) + ## Works with the history provided + ## if useShift is true, then the up/down keys and page up/down + ## will remain free, and shift+up/down and ctrl+pg up/down + ## will be used + + let upkey = if useShift: "shiftup" else: "up" + let downkey = if useShift: "shiftdown" else: "down" + let homekey = if useShift: "ctrlpageup" else: "pageup" + let endkey = if useShift: "ctrlpagedown" else: "pagedown" + + ed.bindKey(upkey): + let res = h.delta(-1) + if res.isSome(): + ed.content = res.get() + + ed.bindKey(downkey): + let res = h.delta(1) + if res.isSome(): + ed.content = res.get() + + ed.bindKey(homekey): + let res = h.toStart() + if res.isSome(): + ed.content = res.get() + + ed.bindKey(endKey): + let res = h.toStart() + if res.isSome(): + ed.content = res.get() + diff --git a/plugin/history.nim b/plugin/history.nim index 77dfe18..0d8aee1 100644 --- a/plugin/history.nim +++ b/plugin/history.nim @@ -3,6 +3,7 @@ import ../multiline import os +import options type HistoryElement* = ref object original*: Multiline @@ -20,20 +21,38 @@ proc newHistory*: History = result.lowestTouchedIndex = 0 result.elements = @[] -proc delta*(h: History, amt: int): Multiline = +template newIndex(h: History): Option[Multiline] = + if h.lowestTouchedIndex > h.index: + h.lowestTouchedIndex = h.index + + some(h.elements[h.index].current) + +proc delta*(h: History, amt: int): Option[Multiline] = # move up/down in history and return reference to current # also update lowest touched index + if h.elements.len() == 0: + return none[Multiline]() + if h.index + amt <= 0: h.index = 0 elif h.index + amt >= h.elements.high(): h.index = h.elements.high() else: h.index += amt + h.newIndex() - if h.lowestTouchedIndex > h.index: - h.lowestTouchedIndex = h.index - h.elements[h.index] +proc toEnd*(h: History): Option[Multiline] = + if h.elements.len() == 0: + return none[Multiline]() + h.index = h.elements.high() + h.newIndex() + +proc toStart*(h: History): Option[Multiline] = + if h.elements.len() == 0: + return none[Multiline]() + h.index = 0 + h.newIndex() proc clean*(h: History) = # restore originals to current @@ -49,7 +68,7 @@ proc clean*(h: History) = 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)) + h.elements.add(HistoryElement(original: ml, current: ml.copy(), temp: temp)) proc save*(h: History, path: string) = # discards currents and temps, only saves originals