import unicode import strutils import sequtils import sugar import bindings/quickjs import buffer/cell import display/term import js/javascript import types/color import utils/twtstr type LineEditState* = enum EDIT, FINISH, CANCEL LineHistory* = ref object lines: seq[string] LineEdit* = ref object news*: seq[Rune] prompt*: string promptw: int current: string state*: LineEditState escNext*: bool cursor: int shift: int minlen: int maxwidth: int displen: int disallowed: set[char] hide: bool term: Terminal hist: LineHistory histindex: int histtmp: string func newLineHistory*(): LineHistory = return LineHistory() 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) template kill0(edit: LineEdit) = let w = min(edit.news.width(edit.cursor), edit.displen) edit.kill0(w) proc backward0(state: LineEdit, i: int) = state.term.cursorBackward(i) proc forward0(state: LineEdit, i: int) = state.term.cursorForward(i) proc begin0(edit: LineEdit) = edit.term.cursorBegin() edit.forward0(edit.minlen) proc space(edit: LineEdit, i: int) = edit.term.write(' '.repeat(i)) proc generateOutput*(edit: LineEdit): FixedGrid = result = newFixedGrid(edit.promptw + edit.maxwidth) var x = 0 for r in edit.prompt.runes(): result[x].str &= $r x += r.width() if edit.hide: for r in edit.news: let w = r.width() result[x].str = '*'.repeat(w) x += w if x >= result.width: break else: for r in edit.news: result[x].str &= $r x += r.width() if x >= result.width: break var s = "" for c in result: s &= c.str proc getCursorX*(edit: LineEdit): int = return edit.promptw + edit.news.width(edit.shift, edit.cursor) proc redraw(state: LineEdit) = if state.shift + state.displen > state.news.len: state.displen = state.news.len - state.shift var dispw = state.news.width(state.shift, state.shift + state.displen) while dispw > state.maxwidth - 1: dispw -= state.news[state.shift + state.displen - 1].width() dec state.displen state.begin0() let os = state.news.substr(state.shift, state.shift + state.displen) if state.hide: state.printesc('*'.repeat(os.width())) else: state.printesc(os) state.space(max(state.maxwidth - state.minlen - os.width(), 0)) state.begin0() state.forward0(state.news.width(state.shift, state.cursor)) proc zeroShiftRedraw(state: LineEdit) = state.shift = 0 state.displen = state.news.len state.redraw() proc fullRedraw(state: LineEdit) = state.displen = state.news.len if state.cursor > state.shift: var shiftw = state.news.width(state.shift, state.cursor) while shiftw > state.maxwidth - 1: inc state.shift shiftw -= state.news[state.shift].width() else: state.shift = max(state.cursor - 1, 0) state.redraw() 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 edit.cursor >= edit.news.len and edit.news.width(edit.shift, edit.cursor) + cs.width() < edit.maxwidth: edit.news &= cs edit.cursor += cs.len if edit.hide: edit.printesc('*'.repeat(cs.width())) else: edit.printesc(cs) else: edit.news.insert(cs, edit.cursor) edit.cursor += cs.len edit.fullRedraw() proc cancel(edit: LineEdit) {.jsfunc.} = edit.state = CANCEL 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.} = if edit.cursor > 0: let w = edit.news[edit.cursor - 1