diff options
author | bptato <nincsnevem662@gmail.com> | 2022-11-29 12:28:04 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2022-11-29 12:28:04 +0100 |
commit | d7a05e7a58d0c2de3078fc9854534974c7e347d4 (patch) | |
tree | 7e5bc4327631ac4106d885b5d5e74bfb1cfa3d64 /src/io | |
parent | f65667797376e65d8e19e1aa514d160210013540 (diff) | |
download | chawan-d7a05e7a58d0c2de3078fc9854534974c7e347d4.tar.gz |
Add line editing history, other lineedit fixes
It's still kind of broken...
Diffstat (limited to 'src/io')
-rw-r--r-- | src/io/lineedit.nim | 195 |
1 files changed, 112 insertions, 83 deletions
diff --git a/src/io/lineedit.nim b/src/io/lineedit.nim index 2cd76fff..d19fe71e 100644 --- a/src/io/lineedit.nim +++ b/src/io/lineedit.nim @@ -14,6 +14,9 @@ type LineEditState* = enum EDIT, FINISH, CANCEL + LineHistory* = ref object + lines: seq[string] + LineEdit* = ref object news*: seq[Rune] prompt*: string @@ -24,39 +27,17 @@ type cursor: int shift: int minlen: int - maxlen: int + maxwidth: int displen: int disallowed: set[char] hide: bool term: Terminal + hist: LineHistory + histindex: int + histtmp: string -proc printesc(edit: LineEdit, rs: seq[Rune]) = - var s = "" - var format = newFormat() - var cformat = newFormat() - cformat.fgcolor = ColorsANSIFg[4] # blue - var dformat = newFormat() # reset - for r in rs: - if r.isControlChar(): - s &= edit.term.processFormat(format, cformat) - else: - s &= edit.term.processFormat(format, dformat) - s &= r - edit.term.write(s) - -proc printesc(edit: LineEdit, s: string) = - var s = "" - var format = newFormat() - var cformat = newFormat() - cformat.fgcolor = ColorsANSIFg[4] # blue - var dformat = newFormat() # reset - for r in s.runes: - if r.isControlChar(): - s &= edit.term.processFormat(format, cformat) - else: - s &= edit.term.processFormat(format, dformat) - s &= r - edit.term.write(s) +func newLineHistory*(): LineHistory = + return LineHistory() func lwidth(r: Rune): int = if r.isControlChar(): @@ -84,6 +65,33 @@ func lwidth(s: seq[Rune], min: int): int = result += lwidth(s[i]) inc i +const colorFormat = (func(): Format = + result = newFormat() + result.fgcolor = ColorsANSIFg[4] # blue +)() +const defaultFormat = newFormat() +proc printesc(edit: LineEdit, rs: seq[Rune]) = + var s = "" + var format = newFormat() + for r in rs: + if r.isControlChar(): + s &= edit.term.processFormat(format, colorFormat) + else: + s &= edit.term.processFormat(format, defaultFormat) + s &= r + edit.term.write(s) + +proc printesc(edit: LineEdit, s: string) = + var s = "" + var format = newFormat() + for r in s.runes: + if r.isControlChar(): + s &= edit.term.processFormat(format, colorFormat) + else: + s &= edit.term.processFormat(format, defaultFormat) + s &= r + edit.term.write(s) + template kill0(edit: LineEdit, i: int) = edit.space(i) edit.backward0(i) @@ -106,7 +114,7 @@ proc space(edit: LineEdit, i: int) = edit.term.write(' '.repeat(i)) proc generateOutput*(edit: LineEdit): FixedGrid = - result = newFixedGrid(edit.promptw + edit.maxlen) + result = newFixedGrid(edit.promptw + edit.maxwidth) let os = edit.news.substr(edit.shift, edit.shift + edit.displen) var x = 0 for r in edit.prompt.runes(): @@ -114,8 +122,9 @@ proc generateOutput*(edit: LineEdit): FixedGrid = x += r.lwidth() if edit.hide: for r in os: - result[x].str = "*" - x += r.lwidth() + let w = r.lwidth() + result[x].str = '*'.repeat(w) + x += w if x >= result.width: break else: for r in os: @@ -127,10 +136,10 @@ proc getCursorX*(edit: LineEdit): int = return edit.promptw + edit.news.lwidth(edit.shift, edit.cursor) proc redraw(state: LineEdit) = - var dispw = state.news.lwidth(state.shift, state.shift + state.displen) if state.shift + state.displen > state.news.len: state.displen = state.news.len - state.shift - while dispw > state.maxlen - 1: + var dispw = state.news.lwidth(state.shift, state.shift + state.displen) + while dispw > state.maxwidth - 1: dispw -= state.news[state.shift + state.displen - 1].lwidth() dec state.displen state.begin0() @@ -139,52 +148,55 @@ proc redraw(state: LineEdit) = state.printesc('*'.repeat(os.lwidth())) else: state.printesc(os) - state.space(max(state.maxlen - state.minlen - os.lwidth(), 0)) + state.space(max(state.maxwidth - state.minlen - os.lwidth(), 0)) state.begin0() state.forward0(state.news.lwidth(state.shift, state.cursor)) proc zeroShiftRedraw(state: LineEdit) = state.shift = 0 - state.displen = state.maxlen - 1 + state.displen = state.news.len state.redraw() -proc fullRedraw*(state: LineEdit) = - state.displen = state.maxlen - 1 +proc fullRedraw(state: LineEdit) = + state.displen = state.news.len if state.cursor > state.shift: var shiftw = state.news.lwidth(state.shift, state.cursor) - while shiftw > state.maxlen - 1: + while shiftw > state.maxwidth - 1: inc state.shift shiftw -= state.news[state.shift].lwidth() else: state.shift = max(state.cursor - 1, 0) state.redraw() -proc insertCharseq(state: LineEdit, cs: var seq[Rune], disallowed: set[char]) = - let escNext = state.escNext - cs.keepIf((r) => (escNext or not r.isControlChar) and not (r.isAscii and char(r) in disallowed)) - state.escNext = false +proc insertCharseq(edit: LineEdit, cs: var seq[Rune]) = + let escNext = edit.escNext + cs.keepIf((r) => (escNext or not r.isControlChar) and not (r.isAscii and char(r) in edit.disallowed)) + edit.escNext = false if cs.len == 0: return - if state.cursor >= state.news.len and state.news.lwidth(state.shift, state.cursor) + cs.lwidth() < state.displen: - state.news &= cs - state.cursor += cs.len - if state.hide: - state.printesc('*'.repeat(cs.lwidth())) + if edit.cursor >= edit.news.len and edit.news.lwidth(edit.shift, edit.cursor) + cs.lwidth() < edit.maxwidth: + edit.news &= cs + edit.cursor += cs.len + if edit.hide: + edit.printesc('*'.repeat(cs.lwidth())) else: - state.printesc(cs) + edit.printesc(cs) else: - state.news.insert(cs, state.cursor) - state.cursor += cs.len - state.fullRedraw() + edit.news.insert(cs, edit.cursor) + edit.cursor += cs.len + edit.fullRedraw() -proc cancel*(edit: LineEdit) {.jsfunc.} = +proc cancel(edit: LineEdit) {.jsfunc.} = edit.state = CANCEL -proc submit*(edit: LineEdit) {.jsfunc.} = +proc submit(edit: LineEdit) {.jsfunc.} = + let s = $edit.news + if edit.hist.lines.len == 0 or s != edit.hist.lines[^1]: + edit.hist.lines.add(s) edit.state = FINISH -proc backspace*(edit: LineEdit) {.jsfunc.} = +proc backspace(edit: LineEdit) {.jsfunc.} = if edit.cursor > 0: let w = edit.news[edit.cursor - 1].lwidth() edit.news.delete(edit.cursor - 1..edit.cursor - 1) @@ -198,10 +210,10 @@ proc backspace*(edit: LineEdit) {.jsfunc.} = proc write*(edit: LineEdit, s: string): bool {.jsfunc.} = if validateUtf8(s) == -1: var cs = s.toRunes() - edit.insertCharseq(cs, edit.disallowed) + edit.insertCharseq(cs) return true -proc delete*(edit: LineEdit) {.jsfunc.} = +proc delete(edit: LineEdit) {.jsfunc.} = if edit.cursor >= 0 and edit.cursor < edit.news.len: let w = edit.news[edit.cursor].lwidth() edit.news.delete(edit.cursor..edit.cursor) @@ -210,21 +222,21 @@ proc delete*(edit: LineEdit) {.jsfunc.} = else: edit.fullRedraw() -proc escape*(edit: LineEdit) {.jsfunc.} = +proc escape(edit: LineEdit) {.jsfunc.} = edit.escNext = true -proc clear*(edit: LineEdit) {.jsfunc.} = +proc clear(edit: LineEdit) {.jsfunc.} = if edit.cursor > 0: edit.news.delete(0..edit.cursor - 1) edit.cursor = 0 edit.zeroShiftRedraw() -proc kill*(edit: LineEdit) {.jsfunc.} = +proc kill(edit: LineEdit) {.jsfunc.} = if edit.cursor < edit.news.len: edit.kill0() edit.news.setLen(edit.cursor) -proc backward*(edit: LineEdit) {.jsfunc.} = +proc backward(edit: LineEdit) {.jsfunc.} = if edit.cursor > 0: dec edit.cursor if edit.cursor > edit.shift or edit.shift == 0: @@ -232,10 +244,10 @@ proc backward*(edit: LineEdit) {.jsfunc.} = else: edit.fullRedraw() -proc forward*(edit: LineEdit) {.jsfunc.} = +proc forward(edit: LineEdit) {.jsfunc.} = if edit.cursor < edit.news.len: inc edit.cursor - if edit.news.lwidth(edit.shift, edit.cursor) < edit.displen: + if edit.news.lwidth(edit.shift, edit.cursor) < edit.maxwidth: var n = 1 if edit.news.len > edit.cursor: n = edit.news[edit.cursor].lwidth() @@ -243,7 +255,7 @@ proc forward*(edit: LineEdit) {.jsfunc.} = else: edit.fullRedraw() -proc prevWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = +proc prevWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = let oc = edit.cursor while edit.cursor > 0: dec edit.cursor @@ -255,8 +267,9 @@ proc prevWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = else: edit.fullRedraw() -proc nextWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = +proc nextWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = let oc = edit.cursor + let ow = edit.news.lwidth(edit.shift, edit.cursor) while edit.cursor < edit.news.len: inc edit.cursor if edit.cursor < edit.news.len: @@ -264,12 +277,12 @@ proc nextWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = break if edit.cursor != oc: let dw = edit.news.lwidth(oc, edit.cursor) - if oc + dw - edit.shift < edit.displen: + if ow + dw < edit.maxwidth: edit.forward0(dw) else: edit.fullRedraw() -proc clearWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = +proc clearWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = var i = edit.cursor if i > 0: # point to the previous character @@ -284,7 +297,7 @@ proc clearWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = edit.cursor = i edit.fullRedraw() -proc killWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = +proc killWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = var i = edit.cursor if i < edit.news.len and edit.news[i].breaksWord(check): inc i @@ -296,35 +309,49 @@ proc killWord*(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = edit.news.delete(edit.cursor..<i) edit.fullRedraw() -proc begin*(edit: LineEdit) {.jsfunc.} = +proc begin(edit: LineEdit) {.jsfunc.} = if edit.cursor > 0: if edit.shift == 0: edit.backward0(edit.news.lwidth(0, edit.cursor)) + edit.cursor = 0 else: + edit.cursor = 0 edit.fullRedraw() - edit.cursor = 0 -proc `end`*(edit: LineEdit) {.jsfunc.} = +proc `end`(edit: LineEdit) {.jsfunc.} = if edit.cursor < edit.news.len: - if edit.news.lwidth(edit.shift, edit.news.len) < edit.maxlen: + if edit.news.lwidth(edit.shift, edit.news.len) < edit.maxwidth: edit.forward0(edit.news.lwidth(edit.cursor, edit.news.len)) + edit.cursor = edit.news.len else: + edit.cursor = edit.news.len edit.fullRedraw() - edit.cursor = edit.news.len -proc writePrompt*(edit: LineEdit) = - edit.printesc(edit.prompt) +proc prevHist(edit: LineEdit) {.jsfunc.} = + if edit.histindex > 0: + if edit.news.len > 0: + edit.histtmp = $edit.news + dec edit.histindex + edit.news = edit.hist.lines[edit.histindex].toRunes() + edit.end() + edit.fullRedraw() -proc writeStart*(edit: LineEdit) = - edit.writePrompt() - if edit.hide: - edit.printesc('*'.repeat(edit.current.lwidth())) - else: - edit.printesc(edit.current) +proc nextHist(edit: LineEdit) {.jsfunc.} = + if edit.histindex + 1 < edit.hist.lines.len: + inc edit.histindex + edit.news = edit.hist.lines[edit.histindex].toRunes() + edit.end() + edit.fullRedraw() + elif edit.histindex < edit.hist.lines.len: + inc edit.histindex + edit.news = edit.histtmp.toRunes() + edit.end() + edit.fullRedraw() + edit.histtmp = "" proc readLine*(prompt: string, termwidth: int, current = "", disallowed: set[char] = {}, hide = false, - term: Terminal): LineEdit = + term: Terminal, hist: LineHistory): LineEdit = result = LineEdit( prompt: prompt, promptw: prompt.lwidth(), @@ -336,8 +363,10 @@ proc readLine*(prompt: string, termwidth: int, current = "", term: term ) result.cursor = result.news.lwidth() - result.maxlen = termwidth - result.promptw - result.displen = result.maxlen - 1 + result.maxwidth = termwidth - result.promptw + result.displen = result.cursor + result.hist = hist + result.histindex = result.hist.lines.len proc addLineEditModule*(ctx: JSContext) = ctx.registerType(LineEdit) |