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 | |
parent | f65667797376e65d8e19e1aa514d160210013540 (diff) | |
download | chawan-d7a05e7a58d0c2de3078fc9854534974c7e347d4.tar.gz |
Add line editing history, other lineedit fixes
It's still kind of broken...
-rw-r--r-- | res/config.toml | 2 | ||||
-rw-r--r-- | src/display/client.nim | 4 | ||||
-rw-r--r-- | src/display/pager.nim | 32 | ||||
-rw-r--r-- | src/io/lineedit.nim | 195 |
4 files changed, 137 insertions, 96 deletions
diff --git a/res/config.toml b/res/config.toml index f4cbba2a..c2c612fa 100644 --- a/res/config.toml +++ b/res/config.toml @@ -91,3 +91,5 @@ M-d = 'line.killWord()' C-a = 'line.begin()' C-e = 'line.end()' C-v = 'line.escape()' +C-p = 'line.prevHist()' +C-n = 'line.nextHist()' diff --git a/src/display/client.nim b/src/display/client.nim index 34477c1a..2fd8bb89 100644 --- a/src/display/client.nim +++ b/src/display/client.nim @@ -264,8 +264,12 @@ proc log(console: Console, ss: varargs[string]) {.jsfunc.} = console.err.write('\n') console.err.flush() +proc c_setvbuf(f: File, buf: pointer, mode: cint, size: csize_t): cint {. + importc: "setvbuf", header: "<stdio.h>", tags: [].} + proc inputLoop(client: Client) = let selector = client.selector + discard c_setvbuf(client.console.tty, nil, IONBF, 0) selector.registerHandle(int(client.console.tty.getFileHandle()), {Read}, nil) let sigwinch = selector.registerSignal(int(SIGWINCH), nil) let redrawtimer = client.selector.registerTimer(1000, false, nil) diff --git a/src/display/pager.nim b/src/display/pager.nim index 849dbcca..d60196cd 100644 --- a/src/display/pager.nim +++ b/src/display/pager.nim @@ -52,6 +52,7 @@ type display: FixedGrid redraw*: bool term*: Terminal + linehist: array[LineMode, LineHistory] iterator containers*(pager: Pager): Container = if pager.container != nil: @@ -114,28 +115,33 @@ proc searchPrev(pager: Pager) {.jsfunc.} = else: pager.container.cursorNextMatch(pager.regex.get, true) -proc setLineEdit(pager: Pager, edit: LineEdit, mode: LineMode) = - pager.lineedit = some(edit) +func attrs(pager: Pager): WindowAttributes = pager.term.attrs + +proc getLineHist(pager: Pager, mode: LineMode): LineHistory = + if pager.linehist[mode] == nil: + pager.linehist[mode] = newLineHistory() + return pager.linehist[mode] + +proc setLineEdit(pager: Pager, prompt: string, mode: LineMode, current = "", hide = false) = + pager.lineedit = some(readLine(prompt, pager.attrs.width, current = current, term = pager.term, hide = hide, hist = pager.getLineHist(mode))) pager.linemode = mode proc clearLineEdit(pager: Pager) = pager.lineedit = none(LineEdit) -func attrs(pager: Pager): WindowAttributes = pager.term.attrs - proc searchForward(pager: Pager) {.jsfunc.} = - pager.setLineEdit(readLine("/", pager.attrs.width, term = pager.term), SEARCH_F) + pager.setLineEdit("/", SEARCH_F) proc searchBackward(pager: Pager) {.jsfunc.} = - pager.setLineEdit(readLine("?", pager.attrs.width, term = pager.term), SEARCH_B) + pager.setLineEdit("?", SEARCH_B) proc isearchForward(pager: Pager) {.jsfunc.} = pager.container.pushCursorPos() - pager.setLineEdit(readLine("/", pager.attrs.width, term = pager.term), ISEARCH_F) + pager.setLineEdit("/", ISEARCH_F) proc isearchBackward(pager: Pager) {.jsfunc.} = pager.container.pushCursorPos() - pager.setLineEdit(readLine("?", pager.attrs.width, term = pager.term), ISEARCH_B) + pager.setLineEdit("?", ISEARCH_B) proc newPager*(config: Config, attrs: WindowAttributes, dispatcher: Dispatcher): Pager = let pager = Pager( @@ -479,7 +485,7 @@ proc readPipe*(pager: Pager, ctype: Option[string], fd: FileHandle) = pager.addContainer(container) proc command(pager: Pager) {.jsfunc.} = - pager.setLineEdit(readLine("COMMAND: ", pager.attrs.width, term = pager.term), COMMAND) + pager.setLineEdit("COMMAND: ", COMMAND) proc commandMode(pager: Pager) {.jsfunc.} = pager.commandmode = true @@ -527,7 +533,7 @@ proc updateReadLine*(pager: Pager) = of LOCATION: pager.loadURL(s) of USERNAME: pager.username = s - pager.setLineEdit(readLine("Password: ", pager.attrs.width, hide = true, term = pager.term), PASSWORD) + pager.setLineEdit("Password: ", PASSWORD, hide = true) of PASSWORD: let url = newURL(pager.container.source.location) url.username = pager.username @@ -571,7 +577,7 @@ proc load(pager: Pager, s = "") {.jsfunc.} = var url = s if url == "": url = pager.container.source.location.serialize() - pager.setLineEdit(readLine("URL: ", pager.attrs.width, current = url, term = pager.term), LOCATION) + pager.setLineEdit("URL: ", LOCATION, url) # Reload the page in a new buffer, then kill the previous buffer. proc reload(pager: Pager) {.jsfunc.} = @@ -585,7 +591,7 @@ proc click(pager: Pager) {.jsfunc.} = pager.container.click() proc authorize*(pager: Pager) = - pager.setLineEdit(readLine("Username: ", pager.attrs.width, term = pager.term), USERNAME) + pager.setLineEdit("Username: ", USERNAME) proc handleEvent0(pager: Pager, container: Container, event: ContainerEvent): bool = case event.t @@ -629,7 +635,7 @@ proc handleEvent0(pager: Pager, container: Container, event: ContainerEvent): bo pager.redraw = true of READ_LINE: if container == pager.container: - pager.setLineEdit(readLine(event.prompt, pager.attrs.width, current = event.value, hide = event.password, term = pager.term), BUFFER) + pager.setLineEdit("(BUFFER) " & event.prompt, BUFFER, event.value, hide = event.password) of READ_AREA: if container == pager.container: var s = event.tvalue 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) |