diff options
author | bptato <nincsnevem662@gmail.com> | 2023-09-14 01:41:47 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2023-09-14 02:01:21 +0200 |
commit | c1b8338045716b25d664c0b8dd91eac0cb76480e (patch) | |
tree | a9c0a6763f180c2b6dd380aa880253ffc7685d85 /src/display | |
parent | db0798acccbedcef4b16737f6be0cf7388cc0528 (diff) | |
download | chawan-c1b8338045716b25d664c0b8dd91eac0cb76480e.tar.gz |
move around more modules
* ips -> io/ * loader related stuff -> loader/ * tempfile -> extern/ * buffer, forkserver -> server/ * lineedit, window -> display/ * cell -> types/ * opt -> types/
Diffstat (limited to 'src/display')
-rw-r--r-- | src/display/lineedit.nim | 369 | ||||
-rw-r--r-- | src/display/term.nim | 6 | ||||
-rw-r--r-- | src/display/window.nim | 54 |
3 files changed, 426 insertions, 3 deletions
diff --git a/src/display/lineedit.nim b/src/display/lineedit.nim new file mode 100644 index 00000000..bf6a3ab5 --- /dev/null +++ b/src/display/lineedit.nim @@ -0,0 +1,369 @@ +import sequtils +import streams +import strutils +import unicode + +import bindings/quickjs +import display/term +import js/javascript +import types/cell +import types/color +import types/opt +import utils/twtstr + +import chakasu/charset +import chakasu/decoderstream +import chakasu/encoderstream + +type + LineEditState* = enum + EDIT, FINISH, CANCEL + + LineHistory* = ref object + lines: seq[string] + + LineEdit* = ref object + isnew*: bool #TODO hack + 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 + +jsDestructor(LineEdit) + +func newLineHistory*(): LineHistory = + return LineHistory() + +proc printesc(edit: LineEdit, rs: seq[Rune]) = + var dummy = 0 + edit.term.write(edit.term.processOutputString0(rs.items, true, dummy)) + +proc print(edit: LineEdit, s: string) = + var dummy = 0 + edit.term.write(edit.term.processOutputString(s, dummy)) + +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)) + +#TODO this is broken (e.g. it doesn't account for shift, but for other +# reasons too) +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.print('*'.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 drawPrompt*(edit: LineEdit) = + edit.term.write(edit.prompt) + +proc insertCharseq(edit: LineEdit, cs: var seq[Rune]) = + let escNext = edit.escNext + var i = 0 + for j in 0 ..< cs.len: + if cs[i].isAscii(): + let c = cast[char](cs[i]) + if not escNext and c in Controls or c in edit.disallowed: + continue + if i != j: + cs[i] = cs[j] + inc i + + 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.print('*'.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].width() + edit.news.delete(edit.cursor - 1..edit.cursor - 1) + dec edit.cursor + if edit.cursor == edit.news.len and edit.shift == 0: + edit.backward0(w) + edit.kill0(w) + else: + edit.fullRedraw() + +const buflen = 128 +var buf {.threadVar.}: array[buflen, uint32] +proc write*(edit: LineEdit, s: string, cs: Charset): bool = + let ss = newStringStream(s) + let ds = newDecoderStream(ss, cs = cs, buflen = buflen, + errormode = DECODER_ERROR_MODE_FATAL) + var cseq: seq[Rune] + while not ds.atEnd: + let n = ds.readData(buf) + for i in 0 ..< n div 4: + let r = cast[Rune](buf[i]) + cseq.add(r) + if ds.failed: + return false + edit.insertCharseq(cseq) + return true + +proc write(edit: LineEdit, s: string): bool {.jsfunc.} = + edit.write(s, CHARSET_UTF_8) + +proc delete(edit: LineEdit) {.jsfunc.} = + if edit.cursor >= 0 and edit.cursor < edit.news.len: + let w = edit.news[edit.cursor].width() + edit.news.delete(edit.cursor..edit.cursor) + if edit.cursor == edit.news.len and edit.shift == 0: + edit.kill0(w) + else: + edit.fullRedraw() + +proc escape(edit: LineEdit) {.jsfunc.} = + edit.escNext = true + +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.} = + if edit.cursor < edit.news.len: + edit.kill0() + edit.news.setLen(edit.cursor) + +proc backward(edit: LineEdit) {.jsfunc.} = + if edit.cursor > 0: + dec edit.cursor + if edit.cursor > edit.shift or edit.shift == 0: + edit.backward0(edit.news[edit.cursor].width()) + else: + edit.fullRedraw() + +proc forward(edit: LineEdit) {.jsfunc.} = + if edit.cursor < edit.news.len: + inc edit.cursor + if edit.news.width(edit.shift, edit.cursor) < edit.maxwidth: + var n = 1 + if edit.news.len > edit.cursor: + n = edit.news[edit.cursor].width() + edit.forward0(n) + else: + edit.fullRedraw() + +proc prevWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} = + let oc = edit.cursor + while edit.cursor > 0: + dec edit.cursor + if edit.news[edit.cursor].breaksWord(check): + break + if edit.cursor != oc: + if edit.cursor > edit.shift or edit.shift == 0: + edit.backward0(edit.news.width(edit.cursor, oc)) + else: + edit.fullRedraw() + +proc nextWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} = + let oc = edit.cursor + let ow = edit.news.width(edit.shift, edit.cursor) + while edit.cursor < edit.news.len: + inc edit.cursor + if edit.cursor < edit.news.len: + if edit.news[edit.cursor].breaksWord(check): + break + if edit.cursor != oc: + let dw = edit.news.width(oc, edit.cursor) + if ow + dw < edit.maxwidth: + edit.forward0(dw) + else: + edit.fullRedraw() + +proc clearWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} = + var i = edit.cursor + if i > 0: + # point to the previous character + dec i + while i > 0: + dec i + if edit.news[i].breaksWord(check): + inc i + break + if i != edit.cursor: + edit.news.delete(i..<edit.cursor) + edit.cursor = i + edit.fullRedraw() + +proc killWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} = + var i = edit.cursor + if i < edit.news.len and edit.news[i].breaksWord(check): + inc i + while i < edit.news.len: + if edit.news[i].breaksWord(check): + break + inc i + if i != edit.cursor: + edit.news.delete(edit.cursor..<i) + edit.fullRedraw() + +proc begin(edit: LineEdit) {.jsfunc.} = + if edit.cursor > 0: + if edit.shift == 0: + edit.backward0(edit.news.width(0, edit.cursor)) + edit.cursor = 0 + else: + edit.cursor = 0 + edit.fullRedraw() + +proc `end`(edit: LineEdit) {.jsfunc.} = + if edit.cursor < edit.news.len: + if edit.news.width(edit.shift, edit.news.len) < edit.maxwidth: + edit.forward0(edit.news.width(edit.cursor, edit.news.len)) + edit.cursor = edit.news.len + else: + edit.cursor = edit.news.len + edit.fullRedraw() + +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.begin() + edit.end() + edit.fullRedraw() + +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.begin() + edit.end() + edit.fullRedraw() + elif edit.histindex < edit.hist.lines.len: + inc edit.histindex + edit.news = edit.histtmp.toRunes() + edit.begin() + edit.end() + edit.fullRedraw() + edit.histtmp = "" + +proc readLine*(prompt: string, termwidth: int, current = "", + disallowed: set[char] = {}, hide = false, + term: Terminal, hist: LineHistory): LineEdit = + result = LineEdit( + prompt: prompt, + promptw: prompt.width(), + current: current, + news: current.toRunes(), + minlen: prompt.width(), + disallowed: disallowed, + hide: hide, + term: term, + isnew: true + ) + result.cursor = result.news.width() + 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) diff --git a/src/display/term.nim b/src/display/term.nim index 233c9cd4..60b5ff7b 100644 --- a/src/display/term.nim +++ b/src/display/term.nim @@ -8,12 +8,12 @@ import termios import unicode import bindings/termcap -import buffer/cell import config/config +import display/window import io/runestream -import io/window +import types/cell import types/color -import utils/opt +import types/opt import utils/twtstr import chakasu/charset diff --git a/src/display/window.nim b/src/display/window.nim new file mode 100644 index 00000000..278fc3fb --- /dev/null +++ b/src/display/window.nim @@ -0,0 +1,54 @@ +import terminal + +when defined(posix): + import termios + + +type + WindowAttributes* = object + width*: int + height*: int + ppc*: int # cell width + ppl*: int # cell height + cell_ratio*: float64 # ppl / ppc + width_px*: int + height_px*: int + +proc getWindowAttributes*(tty: File): WindowAttributes = + when defined(posix): + if tty.isatty(): + var win: IOctl_WinSize + if ioctl(cint(getOsFileHandle(tty)), TIOCGWINSZ, addr win) != -1: + var cols = win.ws_col + var rows = win.ws_row + if cols == 0: + cols = 80 + if rows == 0: + rows = 24 + # Filling the last row without raw mode breaks things. However, + # not supporting Windows means we can always have raw mode, so we can + # use all available columns. + result.width = int(cols) + result.height = int(rows) + result.ppc = int(win.ws_xpixel) div result.width + result.ppl = int(win.ws_ypixel) div result.height + # some terminal emulators (aka vte) don't set ws_xpixel or ws_ypixel. + # solution: use xterm. + if result.ppc == 0: + result.ppc = 9 + if result.ppl == 0: + result.ppl = 18 + result.width_px = result.width * result.ppc + result.height_px = result.height * result.ppl + result.cell_ratio = result.ppl / result.ppc + return + # for Windows. unused. + result.width = terminalWidth() - 1 + result.height = terminalHeight() + if result.height == 0: + result.height = 24 + result.ppc = 9 + result.ppl = 18 + result.cell_ratio = result.ppl / result.ppc + result.width_px = result.ppc * result.width + result.height_px = result.ppl * result.height |