diff options
-rw-r--r-- | readme.md | 1 | ||||
-rw-r--r-- | src/bindings/notcurses.nim | 12 | ||||
-rw-r--r-- | src/buffer/buffer.nim | 48 | ||||
-rw-r--r-- | src/buffer/cell.nim | 53 | ||||
-rw-r--r-- | src/buffer/container.nim | 79 | ||||
-rw-r--r-- | src/css/values.nim | 36 | ||||
-rw-r--r-- | src/display/client.nim | 45 | ||||
-rw-r--r-- | src/display/pager.nim | 131 | ||||
-rw-r--r-- | src/io/term.nim | 294 | ||||
-rw-r--r-- | src/io/window.nim | 52 | ||||
-rw-r--r-- | src/layout/box.nim | 4 | ||||
-rw-r--r-- | src/layout/engine.nim | 10 | ||||
-rw-r--r-- | src/render/renderdocument.nim | 56 | ||||
-rw-r--r-- | src/utils/twtstr.nim | 19 |
14 files changed, 533 insertions, 307 deletions
diff --git a/readme.md b/readme.md index 5fb93cb2..b0b1a280 100644 --- a/readme.md +++ b/readme.md @@ -13,7 +13,6 @@ It also functions as a pager, similarly to w3m. 2. Install the following dependencies: - curl - quickjs - - notcurses 3. Use one of the following: - `make release` - normal release build - `make` - debug build diff --git a/src/bindings/notcurses.nim b/src/bindings/notcurses.nim index bb422a54..8e1cd718 100644 --- a/src/bindings/notcurses.nim +++ b/src/bindings/notcurses.nim @@ -18,6 +18,13 @@ const NCOPTION_DRAIN_INPUT* = 0x0100u64 NCOPTION_SCROLLING* = 0x0200u64 +const + NCDIRECT_OPTION_INHIBIT_SETLOCALE* = 0x0001u64 + NCDIRECT_OPTION_INHIBIT_CBREAK* = 0x0002u64 + NCDIRECT_OPTION_NO_QUIT_SIGHANDLERS* = 0x0008u64 + NCDIRECT_OPTION_VERBOSE* = 0x0010u64 + NCDIRECT_OPTION_VERY_VERBOSE* = 0x0020u64 + const NCOPTION_CLI_MODE = NCOPTION_NO_ALTERNATE_SCREEN or NCOPTION_NO_CLEAR_BITMAPS or NCOPTION_PRESERVE_CURSOR or @@ -48,9 +55,12 @@ type notcurses* = pointer + ncdirect* = pointer + {.push importc.} -proc notcurses_core_init*(opts: notcurses_options, fp: File): notcurses +proc ncdirect_core_init*(termtype: cstring, fp: File, flags: uint64): ncdirect +proc ncdirect_stop*(nc: ncdirect): cint {.pop.} {.pop.} diff --git a/src/buffer/buffer.nim b/src/buffer/buffer.nim index 9bd1d20a..11daa763 100644 --- a/src/buffer/buffer.nim +++ b/src/buffer/buffer.nim @@ -25,8 +25,8 @@ import io/process import io/request import io/serialize import io/socketstream -import io/term import js/regex +import io/window import layout/box import render/renderdocument import render/rendertext @@ -36,9 +36,9 @@ import utils/twtstr type BufferCommand* = enum - LOAD, RENDER, DRAW_BUFFER, WINDOW_CHANGE, GOTO_ANCHOR, READ_SUCCESS, - READ_CANCELED, CLICK, FIND_NEXT_LINK, FIND_PREV_LINK, FIND_NEXT_MATCH, - FIND_PREV_MATCH, GET_SOURCE, GET_LINES, MOVE_CURSOR + LOAD, RENDER, WINDOW_CHANGE, GOTO_ANCHOR, READ_SUCCESS, READ_CANCELED, + CLICK, FIND_NEXT_LINK, FIND_PREV_LINK, FIND_NEXT_MATCH, FIND_PREV_MATCH, + GET_SOURCE, GET_LINES, MOVE_CURSOR ContainerCommand* = enum SET_LINES, SET_NEEDS_AUTH, SET_CONTENT_TYPE, SET_REDIRECT, SET_TITLE, @@ -73,12 +73,11 @@ type bsource: BufferSource width: int height: int - attrs: TermAttributes + attrs: WindowAttributes document: Document viewport: Viewport prevstyled: StyledNode reshape: bool - nostatus: bool location: Url selector: Selector[int] istream: Stream @@ -286,7 +285,7 @@ proc gotoAnchor(buffer: Buffer) = inc i proc windowChange(buffer: Buffer) = - buffer.width = buffer.attrs.width - 1 + buffer.width = buffer.attrs.width buffer.height = buffer.attrs.height - 1 buffer.reshape = true @@ -421,7 +420,7 @@ proc render(buffer: Buffer) = case buffer.contenttype of "text/html": if buffer.viewport == nil: - buffer.viewport = Viewport(term: buffer.attrs) + buffer.viewport = Viewport(window: buffer.attrs) let ret = renderDocument(buffer.document, buffer.attrs, buffer.config.userstyle, buffer.viewport, buffer.prevstyled) buffer.lines = ret[0] buffer.prevstyled = ret[1] @@ -720,31 +719,6 @@ proc click(buffer: Buffer, cursorx, cursory: int) = else: restore_focus -proc drawBuffer(buffer: Buffer, ostream: Stream) = - var format = newFormat() - for line in buffer.lines: - if line.formats.len == 0: - ostream.swrite(line.str & "\n") - else: - var x = 0 - var i = 0 - var s = "" - for f in line.formats: - var outstr = "" - #assert f.pos < line.str.width(), "fpos " & $f.pos & "\nstr" & line.str & "\n" - while x < f.pos: - var r: Rune - fastRuneAt(line.str, i, r) - outstr &= r - x += r.width() - s &= outstr - s &= format.processFormat(f.format) - s &= line.str.substr(i) & format.processFormat(newFormat()) & "\n" - ostream.swrite(s) - ostream.flush() - ostream.swrite("") - ostream.flush() - proc readCommand(buffer: Buffer) = let istream = buffer.pistream let ostream = buffer.postream @@ -768,16 +742,14 @@ proc readCommand(buffer: Buffer) = of GET_LINES: var w: Slice[int] istream.sread(w) + if w.b < 0 or w.b > buffer.lines.high: + w.b = buffer.lines.high ostream.swrite(SET_LINES) ostream.swrite(buffer.lines.len) - w.b = min(buffer.lines.high, w.b) ostream.swrite(w) for y in w: ostream.swrite(buffer.lines[y]) - ostream.flush() ostream.flush() - of DRAW_BUFFER: - buffer.drawBuffer(ostream) of WINDOW_CHANGE: istream.sread(buffer.attrs) buffer.windowChange() @@ -877,7 +849,7 @@ proc runBuffer(buffer: Buffer, readf, writef: File) = quit(0) proc launchBuffer*(config: BufferConfig, source: BufferSource, - attrs: TermAttributes, readf, writef: File) = + attrs: WindowAttributes, readf, writef: File) = let buffer = new Buffer buffer.attrs = attrs buffer.windowChange() diff --git a/src/buffer/cell.nim b/src/buffer/cell.nim index 84efef46..7c6ec00a 100644 --- a/src/buffer/cell.nim +++ b/src/buffer/cell.nim @@ -51,9 +51,21 @@ type str*: string format*: Format - FixedGrid* = seq[FixedCell] - -const FormatCodes: array[FormatFlags, tuple[s: int, e: int]] = [ + FixedGrid* = object + width*, height*: int + cells*: seq[FixedCell] + +proc `[]=`*(grid: var FixedGrid, i: int, cell: FixedCell) = grid.cells[i] = cell +proc `[]=`*(grid: var FixedGrid, i: BackwardsIndex, cell: FixedCell) = grid.cells[i] = cell +proc `[]`*(grid: var FixedGrid, i: int): var FixedCell = grid.cells[i] +proc `[]`*(grid: var FixedGrid, i: BackwardsIndex): var FixedCell = grid.cells[i] +proc `[]`*(grid: FixedGrid, i: int): FixedCell = grid.cells[i] +proc `[]`*(grid: FixedGrid, i: BackwardsIndex): FixedCell = grid.cells[i] +iterator items*(grid: FixedGrid): FixedCell {.inline.} = + for cell in grid.cells: yield cell +proc len*(grid: FixedGrid): int = grid.cells.len + +const FormatCodes*: array[FormatFlags, tuple[s: int, e: int]] = [ FLAG_BOLD: (1, 22), FLAG_ITALIC: (3, 23), FLAG_UNDERLINE: (4, 24), @@ -80,7 +92,7 @@ func `==`*(a: FixedCell, b: FixedCell): bool = a.str == b.str func newFixedGrid*(w: int, h: int = 1): FixedGrid = - return newSeq[FixedCell](w * h) + return FixedGrid(width: w, height: h, cells: newSeq[FixedCell](w * h)) func width*(line: FlexibleLine): int = return line.str.width() @@ -286,36 +298,3 @@ proc parseAnsiCode*(format: var Format, stream: Stream) = if 0x40 <= int(c) and int(c) <= 0x7E: let final = c format.handleAnsiCode(final, params) - -proc processFormat*(format: var Format, cellf: Format): string = - for flag in FormatFlags: - if flag in format.flags and flag notin cellf.flags: - result &= SGR(FormatCodes[flag].e) - - if cellf.fgcolor != format.fgcolor: - var color = cellf.fgcolor - if color.rgb: - let rgb = color.rgbcolor - result &= SGR(38, 2, rgb.r, rgb.g, rgb.b) - elif color == defaultColor: - result &= SGR() - format = newFormat() - else: - result &= SGR(color.color) - - if cellf.bgcolor != format.bgcolor: - var color = cellf.bgcolor - if color.rgb: - let rgb = color.rgbcolor - result &= SGR(48, 2, rgb.r, rgb.g, rgb.b) - elif color == defaultColor: - result &= SGR() - format = newFormat() - else: - result &= SGR(color.color) - - for flag in FormatFlags: - if flag notin format.flags and flag in cellf.flags: - result &= SGR(FormatCodes[flag].s) - - format = cellf diff --git a/src/buffer/container.nim b/src/buffer/container.nim index 35071f1e..e8908986 100644 --- a/src/buffer/container.nim +++ b/src/buffer/container.nim @@ -13,7 +13,7 @@ import config/bufferconfig import config/config import io/request import io/serialize -import io/term +import io/window import js/regex import types/url import utils/twtstr @@ -29,7 +29,7 @@ type ContainerEventType* = enum NO_EVENT, FAIL, SUCCESS, NEEDS_AUTH, REDIRECT, ANCHOR, NO_ANCHOR, UPDATE, - STATUS, JUMP, READ_LINE, OPEN + READ_LINE, OPEN ContainerEvent* = object case t*: ContainerEventType @@ -48,7 +48,7 @@ type clear*: bool Container* = ref object - attrs*: TermAttributes + attrs*: WindowAttributes width*: int height*: int contenttype*: Option[string] @@ -83,7 +83,7 @@ proc c_setvbuf(f: File, buf: pointer, mode: cint, size: csize_t): cint {. importc: "setvbuf", header: "<stdio.h>", tags: [].} proc newBuffer*(config: Config, source: BufferSource, tty: FileHandle, ispipe = false): Container = - let attrs = getTermAttributes(stdout) + let attrs = getWindowAttributes(stdout) when defined(posix): var pipefd_in, pipefd_out: array[0..1, cint] if pipe(pipefd_in) == -1: @@ -274,10 +274,13 @@ macro writeCommand(container: Container, cmd: BufferCommand, args: varargs[typed result.add(quote do: `container`.ostream.swrite(`arg`)) result.add(quote do: `container`.ostream.flush()) +proc requestLines*(container: Container, w = container.lineWindow) = + container.writeCommand(GET_LINES, w) + proc setFromY*(container: Container, y: int) = if container.pos.fromy != y: container.pos.fromy = max(min(y, container.maxfromy), 0) - container.writeCommand(GET_LINES, container.lineWindow) + container.requestLines() container.redraw = true proc setFromX*(container: Container, x: int) = @@ -515,11 +518,16 @@ proc updateCursor(container: Container) = proc pushCursorPos*(container: Container) = container.bpos.add(container.pos) -proc popCursorPos*(container: Container) = +proc sendCursorPosition*(container: Container) = + container.writeCommand(MOVE_CURSOR, container.cursorx, container.cursory) + container.requestLines() + +proc popCursorPos*(container: Container, nojump = false) = container.pos = container.bpos.pop() container.updateCursor() - container.writeCommand(MOVE_CURSOR, container.cursorx, container.cursory) - container.writeCommand(GET_LINES, container.lineWindow) + if not nojump: + container.writeCommand(MOVE_CURSOR, container.cursorx, container.cursory) + container.requestLines() macro proxy(fun: typed) = let name = fun[0] # sym @@ -568,10 +576,11 @@ proc gotoAnchor*(container: Container, anchor: string) {.proxy.} = discard proc readCanceled*(container: Container) {.proxy.} = discard proc readSuccess*(container: Container, s: string) {.proxy.} = discard -proc render*(container: Container) = +proc render*(container: Container, noreq = false) = container.writeCommand(RENDER) container.jump = true # may jump to anchor - container.writeCommand(GET_LINES, container.lineWindow) + if not noreq: + container.requestLines() proc dupeBuffer*(container: Container, config: Config, location = none(URL), contenttype = none(string)): Container = let source = BufferSource( @@ -587,21 +596,9 @@ proc dupeBuffer*(container: Container, config: Config, location = none(URL), con proc click*(container: Container) = container.writeCommand(CLICK, container.cursorx, container.cursory) -proc drawBuffer*(container: Container) = - container.writeCommand(DRAW_BUFFER) - while true: - var s: string - container.istream.sread(s) - if s == "": break - try: - stdout.write(s) - except IOError: # couldn't write to stdout; it's probably just a broken pipe. - quit(1) - stdout.flushFile() - -proc windowChange*(container: Container, attrs: TermAttributes) = +proc windowChange*(container: Container, attrs: WindowAttributes) = container.attrs = attrs - container.width = attrs.width - 1 + container.width = attrs.width container.height = attrs.height - 1 container.writeCommand(WINDOW_CHANGE, attrs) @@ -610,9 +607,7 @@ proc clearSearchHighlights*(container: Container) = if container.highlights[i].clear: container.highlights.del(i) -proc handleEvent*(container: Container): ContainerEvent = - var cmd: ContainerCommand - container.istream.sread(cmd) +proc handleCommand(container: Container, cmd: ContainerCommand): ContainerEvent = case cmd of SET_LINES: var w: Slice[int] @@ -639,10 +634,8 @@ proc handleEvent*(container: Container): ContainerEvent = return ContainerEvent(t: REDIRECT) of SET_TITLE: container.istream.sread(container.title) - return ContainerEvent(t: STATUS) of SET_HOVER: container.istream.sread(container.hovertext) - return ContainerEvent(t: STATUS) of LOAD_DONE: container.istream.sread(container.code) if container.code != 0: @@ -664,18 +657,40 @@ proc handleEvent*(container: Container): ContainerEvent = container.istream.sread(x) container.istream.sread(y) container.istream.sread(ex) - if container.jump: + if x != -1 and y != -1 and container.jump: if container.hlon: + container.clearSearchHighlights() let hl = Highlight(x: x, y: y, endx: ex, endy: y, clear: true) container.highlights.add(hl) container.hlon = false container.setCursorXY(x, y) container.jump = false - return ContainerEvent(t: UPDATE) of OPEN: return ContainerEvent(t: OPEN, request: container.istream.readRequest()) of SOURCE_READY: if container.pipeto != nil: container.pipeto.load() of RESHAPE: - container.writeCommand(GET_LINES, container.lineWindow) + container.requestLines() + +# Synchronously read all lines in the buffer. +iterator readLines*(container: Container): SimpleFlexibleLine {.inline.} = + var cmd: ContainerCommand + container.requestLines(0 .. -1) + container.istream.sread(cmd) + while cmd != SET_LINES: + discard container.handleCommand(cmd) + container.istream.sread(cmd) + assert cmd == SET_LINES + var w: Slice[int] + container.istream.sread(container.numLines) + container.istream.sread(w) + var line: SimpleFlexibleLine + for y in 0 ..< w.len: + container.istream.sread(line) + yield line + +proc handleEvent*(container: Container): ContainerEvent = + var cmd: ContainerCommand + container.istream.sread(cmd) + return container.handleCommand(cmd) diff --git a/src/css/values.nim b/src/css/values.nim index f323ba8b..249bfa37 100644 --- a/src/css/values.nim +++ b/src/css/values.nim @@ -4,7 +4,7 @@ import strutils import css/cssparser import css/selectorparser -import io/term +import io/window import types/color import utils/twtstr @@ -247,26 +247,26 @@ macro `{}=`*(vals: CSSComputedValues, s: string, v: typed): untyped = func inherited(t: CSSPropertyType): bool = return InheritedArray[t] -func em_to_px(em: float64, term: TermAttributes): int = - int(em * float64(term.ppl)) +func em_to_px(em: float64, window: WindowAttributes): int = + int(em * float64(window.ppl)) -func ch_to_px(ch: float64, term: TermAttributes): int = - int(ch * float64(term.ppc)) +func ch_to_px(ch: float64, window: WindowAttributes): int = + int(ch * float64(window.ppc)) # 水 width, we assume it's 2 chars -func ic_to_px(ic: float64, term: TermAttributes): int = - int(ic * float64(term.ppc) * 2) +func ic_to_px(ic: float64, window: WindowAttributes): int = + int(ic * float64(window.ppc) * 2) # x-letter height, we assume it's em/2 -func ex_to_px(ex: float64, term: TermAttributes): int = - int(ex * float64(term.ppc) / 2) +func ex_to_px(ex: float64, window: WindowAttributes): int = + int(ex * float64(window.ppc) / 2) -func px*(l: CSSLength, term: TermAttributes, p: int): int {.inline.} = +func px*(l: CSSLength, window: WindowAttributes, p: int): int {.inline.} = case l.unit - of UNIT_EM, UNIT_REM: em_to_px(l.num, term) - of UNIT_CH: ch_to_px(l.num, term) - of UNIT_IC: ic_to_px(l.num, term) - of UNIT_EX: ex_to_px(l.num, term) + of UNIT_EM, UNIT_REM: em_to_px(l.num, window) + of UNIT_CH: ch_to_px(l.num, window) + of UNIT_IC: ic_to_px(l.num, window) + of UNIT_EX: ex_to_px(l.num, window) of UNIT_PERC: int(p / 100 * l.num) of UNIT_PX: int(l.num) of UNIT_CM: int(l.num * 37.8) @@ -274,10 +274,10 @@ func px*(l: CSSLength, term: TermAttributes, p: int): int {.inline.} = of UNIT_IN: int(l.num * 96) of UNIT_PC: int(l.num * 96 / 6) of UNIT_PT: int(l.num * 96 / 72) - of UNIT_VW: int(term.width_px / 100 * l.num) - of UNIT_VH: int(term.height_px / 100 * l.num) - of UNIT_VMIN: int(min(term.width_px, term.width_px) / 100 * l.num) - of UNIT_VMAX: int(max(term.width_px, term.width_px) / 100 * l.num) + of UNIT_VW: int(window.width_px / 100 * l.num) + of UNIT_VH: int(window.height_px / 100 * l.num) + of UNIT_VMIN: int(min(window.width_px, window.width_px) / 100 * l.num) + of UNIT_VMAX: int(max(window.width_px, window.width_px) / 100 * l.num) func listMarker*(t: CSSListStyleType, i: int): string = case t diff --git a/src/display/client.nim b/src/display/client.nim index 54fca3c1..e94a4d4f 100644 --- a/src/display/client.nim +++ b/src/display/client.nim @@ -8,6 +8,8 @@ import terminal when defined(posix): import posix +import std/exitprocs + import bindings/quickjs import buffer/container import css/sheet @@ -19,15 +21,15 @@ import io/lineedit import io/loader import io/request import io/term +import io/window import js/javascript import types/cookie import types/url -import utils/twtstr type Client* = ref ClientObj ClientObj* = object - attrs: TermAttributes + attrs: WindowAttributes feednext: bool s: string errormessage: string @@ -107,11 +109,7 @@ proc command(client: Client, src: string) = client.console.container.cursorLastLine() proc quit(client: Client, code = 0) {.jsfunc.} = - if stdout.isatty(): - print(HVP(getTermAttributes(stdout).height, 1)) - print('\n') - print(EL()) - stdout.showCursor() + client.pager.quit() when defined(posix): assert kill(client.loader.process, cint(SIGTERM)) == 0 quit(code) @@ -150,11 +148,6 @@ proc input(client: Client) = client.s = "" else: client.feedNext = false - if client.pager.container != nil: - if client.pager.lineedit.isNone: - client.pager.refreshStatusMsg() - client.pager.displayStatus() - client.pager.displayCursor() proc setTimeout[T: JSObject|string](client: Client, handler: T, timeout = 0): int {.jsfunc.} = let id = client.timeoutid @@ -211,8 +204,12 @@ proc clearInterval(client: Client, id: int) {.jsfunc.} = client.interval_fdis.del(interval.fdi) client.intervals.del(id) +let SIGWINCH {.importc, header: "<signal.h>", nodecl.}: cint + proc inputLoop(client: Client) = - client.pager.selector.registerHandle(int(client.console.tty.getFileHandle()), {Read}, nil) + let selector = client.pager.selector + selector.registerHandle(int(client.console.tty.getFileHandle()), {Read}, nil) + let sigwinch = selector.registerSignal(int(SIGWINCH), nil) while true: let events = client.pager.selector.select(-1) for event in events: @@ -226,6 +223,9 @@ proc inputLoop(client: Client) = let timeout = client.timeouts[id] timeout.handler() client.clearTimeout(id) + elif event.fd == sigwinch: + client.attrs = getWindowAttributes(client.console.tty) + client.pager.windowChange(client.attrs) else: let container = client.pager.fdmap[FileHandle(event.fd)] if not client.pager.handleEvent(container): @@ -236,11 +236,7 @@ proc inputLoop(client: Client) = if client.pager.scommand != "": client.command(client.pager.scommand) client.pager.scommand = "" - if client.pager.lineedit.isNone and client.pager.redraw or client.pager.container.redraw: - client.pager.refreshDisplay(client.pager.container) - client.pager.draw() - client.pager.redraw = false - client.pager.container.redraw = false + client.pager.draw() #TODO this is dumb proc readFile(client: Client, path: string): string {.jsfunc.} = @@ -254,6 +250,10 @@ proc writeFile(client: Client, path: string, content: string) {.jsfunc.} = writeFile(path, content) proc launchClient*(client: Client, pages: seq[string], ctype: Option[string], dump: bool) = + let pid = getpid() + addExitProc((proc() = + if pid == getpid(): + client.quit())) if client.config.startup != "": let s = readFile(client.config.startup) client.console.err = newFileStream(stderr) @@ -272,15 +272,14 @@ proc launchClient*(client: Client, pages: seq[string], ctype: Option[string], du client.pager.loadURL(page, ctype = ctype) if stdout.isatty() and not dump: - when defined(posix): - enableRawMode(client.console.tty.getFileHandle()) client.inputLoop() else: for msg in client.pager.status: eprint msg + let ostream = newFileStream(stdout) for container in client.pager.containers: - container.render() - container.drawBuffer() + container.render(true) + client.pager.drawBuffer(container, ostream) stdout.close() client.quit() @@ -328,7 +327,7 @@ proc sleep(client: Client, millis: int) {.jsfunc.} = proc newClient*(config: Config): Client = new(result) result.config = config - result.attrs = getTermAttributes(stdout) + result.attrs = getWindowAttributes(stdout) result.loader = newFileLoader() var tty: File if stdin.isatty(): diff --git a/src/display/pager.nim b/src/display/pager.nim index d4a2dc99..456e6474 100644 --- a/src/display/pager.nim +++ b/src/display/pager.nim @@ -3,7 +3,6 @@ import os import selectors import streams import tables -import terminal import unicode import buffer/buffer @@ -13,6 +12,7 @@ import config/config import io/lineedit import io/request import io/term +import io/window import js/javascript import js/regex import types/url @@ -24,7 +24,7 @@ type SEARCH_B, ISEARCH_F, ISEARCH_B Pager* = ref object - attrs: TermAttributes + attrs: WindowAttributes commandMode*: bool container*: Container lineedit*: Option[LineEdit] @@ -42,9 +42,8 @@ type fdmap*: Table[FileHandle, Container] icpos: CursorPosition display: FixedGrid - bheight*: int - bwidth*: int redraw*: bool + term*: Terminal iterator containers*(pager: Pager): Container = if pager.container != nil: @@ -135,15 +134,21 @@ proc isearchBackward(pager: Pager) {.jsfunc.} = pager.container.pushCursorPos() pager.setLineEdit(readLine("?", pager.attrs.width, config = pager.config, tty = pager.tty), ISEARCH_B) -proc newPager*(config: Config, attrs: TermAttributes, tty: File): Pager = +func attrs*(pager: Pager): WindowAttributes = pager.term.attrs +func bwidth(pager: Pager): int = pager.attrs.width +func bheight(pager: Pager): int = pager.attrs.height - 1 + +proc newPager*(config: Config, attrs: WindowAttributes, tty: File): Pager = new(result) result.config = config result.attrs = attrs result.tty = tty result.selector = newSelector[Container]() - result.bwidth = attrs.width - 1 # writing to the last column is a bad idea it seems - result.bheight = attrs.height - 1 result.display = newFixedGrid(result.bwidth, result.bheight) + result.term = newTerminal(tty, stdout) + +proc quit*(pager: Pager, code = 0) = + pager.term.quit() proc clearDisplay(pager: Pager) = pager.display = newFixedGrid(pager.bwidth, pager.bheight) @@ -177,7 +182,7 @@ proc refreshDisplay*(pager: Pager, container = pager.container) = let pw = w fastRuneAt(line.str, i, r) w += r.width() - if w > container.fromx + pager.bwidth: + if w > container.fromx + pager.display.width: break # die on exceeding the width limit if nf.pos != -1 and nf.pos <= pw: cf = nf @@ -186,13 +191,13 @@ proc refreshDisplay*(pager: Pager, container = pager.container) = if cf.pos != -1: pager.display[dls + k].format = cf.format let tk = k + r.width() - while k < tk and k < pager.bwidth - 1: + while k < tk and k < pager.display.width - 1: inc k # Finally, override cell formatting for highlighted cells. - let hls = container.findHighlights(by) + let hls = container.findHighlights(container.fromy + by) let aw = container.width - (startw - container.fromx) # actual width for hl in hls: - let area = hl.colorArea(by, startw .. startw + aw) + let area = hl.colorArea(container.fromy + by, startw .. startw + aw) for i in area: pager.display[dls + i - startw].format = hlformat inc by @@ -201,7 +206,7 @@ func generateStatusMessage*(pager: Pager): string = var format = newFormat() var w = 0 for cell in pager.statusmsg: - result &= format.processFormat(cell.format) + result &= pager.term.processFormat(format, cell.format) result &= cell.str w += cell.width() if w < pager.bwidth: @@ -239,55 +244,55 @@ func generateStatusOutput(pager: Pager): string = else: return pager.generateStatusMessage() -func generateFullOutput(pager: Pager): string = - var x = 0 - var w = 0 - var format = newFormat() - result &= HVP(1, 1) - for cell in pager.display: - if x >= pager.bwidth: - result &= EL() - result &= "\r\n" - x = 0 - w = 0 - result &= format.processFormat(cell.format) - result &= cell.str - w += cell.width() - inc x - result &= EL() - result &= "\r\n" - -proc displayCursor*(pager: Pager) = - if pager.container == nil: return - print(HVP(pager.container.acursory + 1, pager.container.acursorx + 1)) - stdout.flushFile() - proc displayStatus*(pager: Pager) = if pager.lineedit.isNone: pager.statusMode() print(pager.generateStatusOutput()) stdout.flushFile() -proc displayPage*(pager: Pager) = - stdout.hideCursor() - print(SGR()) - print(pager.generateFullOutput()) - pager.displayStatus() - pager.displayCursor() - stdout.showCursor() - if pager.lineedit.isSome: - pager.statusMode() - pager.lineedit.get.writePrompt() - pager.lineedit.get.fullRedraw() - stdout.flushFile() +proc drawBuffer*(pager: Pager, container: Container, ostream: Stream) = + var format = newFormat() + for line in container.readLines: + if line.formats.len == 0: + ostream.write(line.str & "\n") + else: + var x = 0 + var i = 0 + var s = "" + for f in line.formats: + var outstr = "" + #assert f.pos < line.str.width(), "fpos " & $f.pos & "\nstr" & line.str & "\n" + while x < f.pos: + var r: Rune + fastRuneAt(line.str, i, r) + outstr &= r + x += r.width() + s &= outstr + s &= pager.term.processFormat(format, f.format) + s &= line.str.substr(i) & pager.term.processFormat(format, newFormat()) & "\n" + ostream.write(s) + ostream.flush() proc redraw(pager: Pager) {.jsfunc.} = pager.redraw = true proc draw*(pager: Pager) = - pager.refreshDisplay() + pager.term.hideCursor() + if pager.redraw or pager.container != nil and pager.container.redraw: + pager.refreshDisplay() + pager.term.outputGrid(pager.display) pager.refreshStatusMsg() - pager.displayPage() + pager.displayStatus() + if pager.lineedit.isSome: + pager.statusMode() + pager.lineedit.get.writePrompt() + pager.lineedit.get.fullRedraw() + elif pager.container != nil: + pager.term.setCursor(pager.container.acursorx, pager.container.acursory) + pager.term.showCursor() + pager.term.flush() + pager.redraw = false + pager.container.redraw = false proc registerContainer*(pager: Pager, container: Container) = pager.fdmap[container.ifd] = container @@ -398,6 +403,11 @@ proc toggleSource*(pager: Pager) {.jsfunc.} = pager.container.sourcepair = container pager.container.children.add(container) +proc windowChange*(pager: Pager, attrs: WindowAttributes) = + pager.attrs = attrs + for container in pager.containers: + container.windowChange(attrs) + # Load request in a new buffer. proc gotoURL*(pager: Pager, request: Request, prevurl = none(URL), ctype = none(string), replace: Container = nil) = if prevurl.isnone or not prevurl.get.equals(request.url, true) or @@ -490,8 +500,7 @@ proc updateReadLineISearch(pager: Pager, linemode: LineMode) = of EDIT: let x = $lineedit.news if x != "": pager.iregex = compileSearchRegex(x) - pager.container.clearSearchHighlights() - pager.container.popCursorPos() + pager.container.popCursorPos(true) if pager.iregex.isSome: if linemode == ISEARCH_F: pager.container.cursorNextMatch(pager.iregex.get, true) @@ -499,9 +508,7 @@ proc updateReadLineISearch(pager: Pager, linemode: LineMode) = pager.container.cursorPrevMatch(pager.iregex.get, true) pager.container.hlon = true pager.container.pushCursorPos() - pager.displayPage() - pager.statusMode() - pager.lineedit.get.fullRedraw() + pager.draw() of FINISH: if pager.iregex.isSome: pager.regex = pager.iregex @@ -582,8 +589,6 @@ proc handleEvent*(pager: Pager, container: Container): bool = pager.gotoURL(newRequest(container.retry.pop()), ctype = container.contenttype) else: pager.setStatusMessage("Couldn't load " & $container.source.location & " (error code " & $container.code & ")") - pager.displayStatus() - pager.displayCursor() if pager.container == nil: return false of SUCCESS: @@ -606,28 +611,14 @@ proc handleEvent*(pager: Pager, container: Container): bool = of REDIRECT: let redirect = container.redirect.get pager.setStatusMessage("Redirecting to " & $redirect) - pager.displayStatus() - pager.displayCursor() pager.gotoURL(newRequest(redirect), some(pager.container.source.location), replace = pager.container) of ANCHOR: pager.addContainer(pager.dupeContainer(container, container.redirect)) of NO_ANCHOR: pager.setStatusMessage("Couldn't find anchor " & container.redirect.get.anchor) - pager.displayStatus() - pager.displayCursor() of UPDATE: if container == pager.container: pager.redraw = true - of JUMP: - if container == pager.container: - pager.refreshStatusMsg() - pager.displayStatus() - pager.displayCursor() - of STATUS: - if container == pager.container: - pager.refreshStatusMsg() - pager.displayStatus() - pager.displayCursor() of READ_LINE: if container == pager.container: pager.setLineEdit(readLine(event.prompt, pager.bwidth, current = event.value, hide = event.password, config = pager.config, tty = pager.tty), BUFFER) diff --git a/src/io/term.nim b/src/io/term.nim index 1eacd237..d39273d0 100644 --- a/src/io/term.nim +++ b/src/io/term.nim @@ -1,16 +1,243 @@ +import os +import streams +import strutils import terminal -import std/exitprocs +import bindings/notcurses +import buffer/cell +import io/window +import types/color +import utils/twtstr type - TermAttributes* = object - width*: int - height*: int - ppc*: int # cell width - ppl*: int # cell height - cell_ratio*: float64 # ppl / ppc - width_px*: int - height_px*: int + ColorMode = enum + MONOCHROME, ANSI, EIGHT_BIT, TRUE_COLOR + + FormatMode = set[FormatFlags] + + Terminal* = ref TerminalObj + TerminalObj = object + infile: File + outfile: File + nc*: ncdirect + cleared: bool + prevgrid: FixedGrid + attrs*: WindowAttributes + colormode: ColorMode + formatmode: FormatMode + smcup: bool + + TermInfo = ref object + +proc `=destroy`(term: var TerminalObj) = + if term.nc != nil: + #discard ncdirect_stop(term.nc) + term.nc = nil + +template CSI*(s: varargs[string, `$`]): string = + var r = "\e[" + var first = true + for x in s: + if not first: + r &= ";" + first = false + r &= x + r + +template DECSET(s: varargs[string, `$`]): string = + var r = "\e[?" + var first = true + for x in s: + if not first: + r &= ";" + first = false + r &= x + r & "h" + +template DECRST(s: varargs[string, `$`]): string = + var r = "\e[?" + var first = true + for x in s: + if not first: + r &= ";" + first = false + r &= x + r & "l" + +template SMCUP(): string = DECSET(1049) +template RMCUP(): string = DECRST(1049) + +template SGR*(s: varargs[string, `$`]): string = + CSI(s) & "m" + +template HVP*(s: varargs[string, `$`]): string = + CSI(s) & "f" + +template EL*(s: varargs[string, `$`]): string = + CSI(s) & "K" + +proc processFormat*(term: Terminal, format: var Format, cellf: Format): string = + for flag in FormatFlags: + if flag in format.flags and flag notin cellf.flags: + result &= SGR(FormatCodes[flag].e) + + if cellf.fgcolor != format.fgcolor: + var color = cellf.fgcolor + if color.rgb: + let rgb = color.rgbcolor + result &= SGR(38, 2, rgb.r, rgb.g, rgb.b) + elif color == defaultColor: + result &= SGR() + format = newFormat() + else: + result &= SGR(color.color) + + if cellf.bgcolor != format.bgcolor: + var color = cellf.bgcolor + if color.rgb: + let rgb = color.rgbcolor + result &= SGR(48, 2, rgb.r, rgb.g, rgb.b) + elif color == defaultColor: + result &= SGR() + format = newFormat() + else: + result &= SGR(color.color) + + for flag in FormatFlags: + if flag notin format.flags and flag in cellf.flags: + result &= SGR(FormatCodes[flag].s) + + format = cellf + +proc updateWindow*(term: Terminal) = + term.attrs = getWindowAttributes(term.outfile) + +proc findTermInfoDirs(termenv: string): seq[string] = + let tienv = getEnv("TERMINFO") + if tienv != "": + if dirExists(tienv): + return @[tienv] + else: + let home = getEnv("HOME") + if home != "": + result.add(home & '/' & ".terminfo") + let tidirsenv = getEnv("TERMINFO_DIRS") + if tidirsenv != "": + for s in tidirsenv.split({':'}): + if s == "": + result.add("/usr/share/terminfo") + else: + result.add(s) + return result + result.add("/usr/share/terminfo") + +proc findFile(dir: string, file: string): string = + var stack = dir + for f in walkDirRec(dir, followFilter = {pcDir, pcLinkToDir}): + if f == file: + return f + +proc parseTermInfo(s: Stream): TermInfo = + let magic = s.readInt16() + #TODO do we really want this? + s.close() + +proc getTermInfo(termenv: string): TermInfo = + let tipaths = findTermInfoDirs(termenv) + for tipath in tipaths: + let f = findFile(tipath, termenv) + if f != "": + return parseTermInfo(newFileStream(f)) + +proc getCursorPos(term: Terminal): (int, int) = + term.outfile.write(CSI("6n")) + term.outfile.flushFile() + var c = term.infile.readChar() + while true: + while c != '\e': + c = term.infile.readChar() + c = term.infile.readChar() + if c == '[': break + var tmp = "" + while (let c = term.infile.readChar(); c != ';'): + tmp &= c + result[1] = parseInt32(tmp) + tmp = "" + while (let c = term.infile.readChar(); c != 'R'): + tmp &= c + result[0] = parseInt32(tmp) + +proc detectTermAttributes*(term: Terminal) = + term.colormode = ANSI + let colorterm = getEnv("COLORTERM") + case colorterm + of "24bit", "truecolor": term.colormode = TRUE_COLOR + #TODO terminfo/termcap? + +func generateFullOutput(term: Terminal, grid: FixedGrid): string = + var x = 0 + var format = newFormat() + result &= HVP(1, 1) + for cell in grid.cells: + if x >= grid.width - 1: + result &= EL() + result &= "\r\n" + x = 0 + result &= term.processFormat(format, cell.format) + result &= cell.str + inc x + result &= EL() + +func generateSwapOutput(term: Terminal, grid: FixedGrid): string = + var format = newFormat() + let curr = grid.cells + let prev = term.prevgrid.cells + var i = 0 + var x = 0 + var y = 0 + var line = "" + var lr = false + while i < curr.len: + if x >= grid.width - 1: + if lr: + result &= HVP(y + 1, 1) + result &= EL() + result &= line + lr = false + x = 0 + inc y + line = "" + lr = lr or (curr[i] != prev[i]) + line &= term.processFormat(format, curr[i].format) + line &= curr[i].str + inc i + inc x + if lr: + result &= HVP(y + 1, 1) + result &= EL() + result &= line + lr = false + +proc hideCursor*(term: Terminal) = + term.outfile.hideCursor() + +proc showCursor*(term: Terminal) = + term.outfile.showCursor() + +proc flush*(term: Terminal) = + term.outfile.flushFile() + +proc outputGrid*(term: Terminal, grid: FixedGrid) = + term.outfile.write(SGR()) + if not term.cleared: + term.outfile.write(term.generateFullOutput(grid)) + term.cleared = true + else: + term.outfile.write(term.generateSwapOutput(grid)) + term.prevgrid = grid + +proc setCursor*(term: Terminal, x, y: int) = + term.outfile.write(HVP(y + 1, x + 1)) when defined(posix): import posix @@ -24,7 +251,6 @@ when defined(posix): proc enableRawMode*(fileno: FileHandle) = stdin_fileno = fileno - addExitProc(disableRawMode) discard tcGetAttr(fileno, addr orig_termios) var raw = orig_termios raw.c_iflag = raw.c_iflag and not (BRKINT or ICRNL or INPCK or ISTRIP or IXON) @@ -52,27 +278,29 @@ else: proc restoreStdin*(flags: cint) = discard -proc getTermAttributes*(tty: File): TermAttributes = - if tty.isatty(): +proc isatty*(term: Terminal): bool = + term.infile.isatty() and term.outfile.isatty() + +proc quit*(term: Terminal) = + if term.isatty(): when defined(posix): - var win: IOctl_WinSize - if ioctl(cint(getOsFileHandle(tty)), TIOCGWINSZ, addr win) != -1: - result.ppc = int(win.ws_xpixel) div int(win.ws_col) - result.ppl = int(win.ws_ypixel) div int(win.ws_row) - # some terminals don't like it when we fill the last cell. #TODO make this optional - result.width = int(win.ws_col) - 1 - result.height = int(win.ws_row) - result.width_px = result.width * result.ppc - result.height_px = result.height * result.ppl - result.cell_ratio = result.ppl / result.ppc - return - #fail - 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 + disableRawMode() + if term.smcup: + term.outfile.write(RMCUP()) + else: + term.outfile.write(HVP(term.attrs.height, 1) & '\n') + term.outfile.showCursor() + term.outfile.flushFile() + +proc newTerminal*(infile, outfile: File, force_minimal = false): Terminal = + let term = new Terminal + term.infile = infile + term.outfile = outfile + when defined(posix): + if term.isatty(): + enableRawMode(infile.getFileHandle()) + if not force_minimal: + term.detectTermAttributes() + term.smcup = true + term.outfile.write(SMCUP()) + return term diff --git a/src/io/window.nim b/src/io/window.nim new file mode 100644 index 00000000..95da9e70 --- /dev/null +++ b/src/io/window.nim @@ -0,0 +1,52 @@ +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 = + if tty.isatty(): + when defined(posix): + 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 + # some terminals don't like it when we fill the last cell. #TODO make this optional + result.width = int(cols) - 1 + 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 + #fail + 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 diff --git a/src/layout/box.nim b/src/layout/box.nim index 204abc00..4875ffda 100644 --- a/src/layout/box.nim +++ b/src/layout/box.nim @@ -3,7 +3,7 @@ import options import css/stylednode import css/values import html/dom -import io/term +import io/window type #LayoutUnit* = distinct int32 @@ -22,7 +22,7 @@ type neg*: int Viewport* = ref object - term*: TermAttributes + window*: WindowAttributes root*: BlockBox BoxBuilder* = ref object of RootObj diff --git a/src/layout/engine.nim b/src/layout/engine.nim index 7b651e4f..09103129 100644 --- a/src/layout/engine.nim +++ b/src/layout/engine.nim @@ -5,13 +5,13 @@ import css/stylednode import css/values import html/tags import html/dom -import io/term +import io/window import layout/box import utils/twtstr # Build phase func px(l: CSSLength, viewport: Viewport, p = 0): int {.inline.} = - return px(l, viewport.term, p) + return px(l, viewport.window, p) type InlineState = object ictx: InlineContext @@ -25,13 +25,13 @@ func whitespacepre(computed: CSSComputedValues): bool {.inline.} = computed{"white-space"} in {WHITESPACE_PRE, WHITESPACE_PRE_WRAP} func cellwidth(viewport: Viewport): int {.inline.} = - viewport.term.ppc + viewport.window.ppc func cellwidth(ictx: InlineContext): int {.inline.} = ictx.viewport.cellwidth func cellheight(viewport: Viewport): int {.inline.} = - viewport.term.ppl + viewport.window.ppl func cellheight(ictx: InlineContext): int {.inline.} = ictx.viewport.cellheight @@ -411,7 +411,7 @@ proc newListItem(parent: BlockBox, builder: ListItemBoxBuilder): ListItemBox = result.shrink = result.computed{"width"}.auto and parent.shrink proc newBlockBox(viewport: Viewport, box: BlockBoxBuilder): BlockBox = - result = newFlowRootBox(viewport, box, viewport.term.width_px) + result = newFlowRootBox(viewport, box, viewport.window.width_px) proc newInlineBlock(viewport: Viewport, builder: InlineBlockBoxBuilder, parentWidth: int, parentHeight = none(int)): InlineBlockBox = new(result) diff --git a/src/render/renderdocument.nim b/src/render/renderdocument.nim index 44b369f7..06901ba6 100644 --- a/src/render/renderdocument.nim +++ b/src/render/renderdocument.nim @@ -7,7 +7,7 @@ import css/sheet import css/stylednode import css/values import html/dom -import io/term +import io/window import layout/box import layout/engine import types/color @@ -128,13 +128,13 @@ proc setText(lines: var FlexibleGrid, linestr: string, cformat: ComputedFormat, assert lines[y].formats[fi].pos <= nx # That's it! -proc setRowWord(lines: var FlexibleGrid, word: InlineWord, x, y: int, term: TermAttributes) = +proc setRowWord(lines: var FlexibleGrid, word: InlineWord, x, y: int, window: WindowAttributes) = var r: Rune - var y = (y + word.offset.y) div term.ppl # y cell + var y = (y + word.offset.y) div window.ppl # y cell if y < 0: return # y is outside the canvas, no need to draw - var x = (x + word.offset.x) div term.ppc # x cell + var x = (x + word.offset.x) div window.ppc # x cell var i = 0 while x < 0 and i < word.str.len: fastRuneAt(word.str, i, r) @@ -144,12 +144,12 @@ proc setRowWord(lines: var FlexibleGrid, word: InlineWord, x, y: int, term: Term lines.setText(linestr, word.format, x, y) -proc setSpacing(lines: var FlexibleGrid, spacing: InlineSpacing, x, y: int, term: TermAttributes) = - var y = (y + spacing.offset.y) div term.ppl # y cell +proc setSpacing(lines: var FlexibleGrid, spacing: InlineSpacing, x, y: int, window: WindowAttributes) = + var y = (y + spacing.offset.y) div window.ppl # y cell if y < 0: return # y is outside the canvas, no need to draw - var x = (x + spacing.offset.x) div term.ppc # x cell - let width = spacing.width div term.ppc # cell width + var x = (x + spacing.offset.x) div window.ppc # x cell + let width = spacing.width div window.ppc # cell width if x + width < 0: return # highest x is outside the canvas, no need to draw var i = 0 @@ -160,11 +160,11 @@ proc setSpacing(lines: var FlexibleGrid, spacing: InlineSpacing, x, y: int, term lines.setText(linestr, spacing.format, x, y) -proc paintBackground(lines: var FlexibleGrid, color: CSSColor, startx, starty, endx, endy: int, term: TermAttributes) = +proc paintBackground(lines: var FlexibleGrid, color: CSSColor, startx, starty, endx, endy: int, window: WindowAttributes) = let color = color.cellColor() - var starty = starty div term.ppl - var endy = endy div term.ppl + var starty = starty div window.ppl + var endy = endy div window.ppl if starty > endy: swap(starty, endy) @@ -173,9 +173,9 @@ proc paintBackground(lines: var FlexibleGrid, color: CSSColor, startx, starty, e if starty < 0: starty = 0 if starty == endy: return # height is 0, no need to paint - var startx = startx div term.ppc + var startx = startx div window.ppc - var endx = endx div term.ppc + var endx = endx div window.ppc if endy < 0: endy = 0 if startx > endx: @@ -237,40 +237,40 @@ proc paintBackground(lines: var FlexibleGrid, color: CSSColor, startx, starty, e if lines[y].formats[fi].pos >= startx: lines[y].formats[fi].format.bgcolor = color -proc renderBlockContext(grid: var FlexibleGrid, ctx: BlockBox, x, y: int, term: TermAttributes) +proc renderBlockContext(grid: var FlexibleGrid, ctx: BlockBox, x, y: int, window: WindowAttributes) -proc renderInlineContext(grid: var FlexibleGrid, ctx: InlineContext, x, y: int, term: TermAttributes) = +proc renderInlineContext(grid: var FlexibleGrid, ctx: InlineContext, x, y: int, window: WindowAttributes) = let x = x + ctx.offset.x let y = y + ctx.offset.y for line in ctx.lines: let x = x + line.offset.x let y = y + line.offset.y - let r = y div term.ppl + let r = y div window.ppl while grid.len <= r: grid.addLine() for atom in line.atoms: if atom of InlineBlockBox: let iblock = InlineBlockBox(atom) - grid.renderBlockContext(iblock.bctx, x + iblock.offset.x, y + iblock.offset.y, term) + grid.renderBlockContext(iblock.bctx, x + iblock.offset.x, y + iblock.offset.y, window) elif atom of InlineWord: let word = InlineWord(atom) - grid.setRowWord(word, x, y, term) + grid.setRowWord(word, x, y, window) elif atom of InlineSpacing: let spacing = InlineSpacing(atom) - grid.setSpacing(spacing, x, y, term) + grid.setSpacing(spacing, x, y, window) -proc renderTable(grid: var FlexibleGrid, ctx: TableBox, x, y: int, term: TermAttributes) = +proc renderTable(grid: var FlexibleGrid, ctx: TableBox, x, y: int, window: WindowAttributes) = for row in ctx.nested: assert row.computed{"display"} == DISPLAY_TABLE_ROW for cell in row.nested: let x = x + row.offset.x let y = y + row.offset.y assert cell.computed{"display"} == DISPLAY_TABLE_CELL - grid.renderBlockContext(cell, x, y, term) + grid.renderBlockContext(cell, x, y, window) -proc renderBlockContext(grid: var FlexibleGrid, ctx: BlockBox, x, y: int, term: TermAttributes) = +proc renderBlockContext(grid: var FlexibleGrid, ctx: BlockBox, x, y: int, window: WindowAttributes) = var stack = newSeqOfCap[(BlockBox, int, int)](100) stack.add((ctx, x, y)) @@ -280,29 +280,29 @@ proc renderBlockContext(grid: var FlexibleGrid, ctx: BlockBox, x, y: int, term: y += ctx.offset.y if ctx.computed{"background-color"}.rgba.a != 0: #TODO color blending - grid.paintBackground(ctx.computed{"background-color"}, x, y, x + ctx.width, y + ctx.height, term) + grid.paintBackground(ctx.computed{"background-color"}, x, y, x + ctx.width, y + ctx.height, window) if ctx of ListItemBox: let ctx = ListItemBox(ctx) if ctx.marker != nil: - grid.renderInlineContext(ctx.marker, x - ctx.marker.maxwidth, y, term) + grid.renderInlineContext(ctx.marker, x - ctx.marker.maxwidth, y, window) if ctx.inline != nil: assert ctx.nested.len == 0 - grid.renderInlineContext(ctx.inline, x, y, term) + grid.renderInlineContext(ctx.inline, x, y, window) elif ctx.computed{"display"} == DISPLAY_TABLE: #TODO INLINE_TABLE - grid.renderTable(TableBox(ctx), x, y, term) + grid.renderTable(TableBox(ctx), x, y, window) else: for i in countdown(ctx.nested.high, 0): stack.add((ctx.nested[i], x, y)) const css = staticRead"res/ua.css" let uastyle = css.parseStylesheet() -proc renderDocument*(document: Document, term: TermAttributes, userstyle: CSSStylesheet, layout: var Viewport, previousStyled: StyledNode): (FlexibleGrid, StyledNode) = +proc renderDocument*(document: Document, window: WindowAttributes, userstyle: CSSStylesheet, layout: var Viewport, previousStyled: StyledNode): (FlexibleGrid, StyledNode) = let styledNode = document.applyStylesheets(uastyle, userstyle, previousStyled) result[1] = styledNode layout.renderLayout(document, styledNode) result[0].setLen(0) - result[0].renderBlockContext(layout.root, 0, 0, term) + result[0].renderBlockContext(layout.root, 0, 0, window) if result[0].len == 0: result[0].addLine() diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim index 3636da14..2123daeb 100644 --- a/src/utils/twtstr.nim +++ b/src/utils/twtstr.nim @@ -628,25 +628,6 @@ proc expandPath*(path: string): string = if p != nil: result = $p.pw_dir / path.substr(i) -template CSI*(s: varargs[string, `$`]): string = - var r = "\e[" - var first = true - for x in s: - if not first: - r &= ";" - first = false - r &= x - r - -template SGR*(s: varargs[string, `$`]): string = - CSI(s) & "m" - -template HVP*(s: varargs[string, `$`]): string = - CSI(s) & "f" - -template EL*(s: varargs[string, `$`]): string = - CSI(s) & "K" - iterator split*(s: seq[Rune], sep: Rune): seq[Rune] = var i = 0 var prev = 0 |