diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/config/config.nim | 8 | ||||
-rw-r--r-- | src/css/values.nim | 2 | ||||
-rw-r--r-- | src/display/lineedit.nim | 2 | ||||
-rw-r--r-- | src/display/term.nim | 240 | ||||
-rw-r--r-- | src/display/winattrs.nim | 58 | ||||
-rw-r--r-- | src/html/dom.nim | 2 | ||||
-rw-r--r-- | src/html/env.nim | 2 | ||||
-rw-r--r-- | src/layout/engine.nim | 2 | ||||
-rw-r--r-- | src/layout/renderdocument.nim | 61 | ||||
-rw-r--r-- | src/local/client.nim | 7 | ||||
-rw-r--r-- | src/local/container.nim | 2 | ||||
-rw-r--r-- | src/local/pager.nim | 28 | ||||
-rw-r--r-- | src/server/buffer.nim | 2 | ||||
-rw-r--r-- | src/server/forkserver.nim | 2 | ||||
-rw-r--r-- | src/types/cell.nim | 208 |
15 files changed, 256 insertions, 370 deletions
diff --git a/src/config/config.nim b/src/config/config.nim index 1ae0a727..e7c0d0aa 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -121,6 +121,14 @@ type default_background_color* {.jsgetset.}: Opt[RGBColor] default_foreground_color* {.jsgetset.}: Opt[RGBColor] query_da1* {.jsgetset.}: bool + columns* {.jsgetset.}: int32 + lines* {.jsgetset.}: int32 + pixels_per_column* {.jsgetset.}: int32 + pixels_per_line* {.jsgetset.}: int32 + force_columns* {.jsgetset.}: bool + force_lines* {.jsgetset.}: bool + force_pixels_per_column* {.jsgetset.}: bool + force_pixels_per_line* {.jsgetset.}: bool Config* = ref ConfigObj ConfigObj* = object diff --git a/src/css/values.nim b/src/css/values.nim index c53825ee..e1282f57 100644 --- a/src/css/values.nim +++ b/src/css/values.nim @@ -6,7 +6,7 @@ import std/unicode import css/cssparser import css/selectorparser -import display/winattrs +import display/term import img/bitmap import layout/layoutunit import types/color diff --git a/src/display/lineedit.nim b/src/display/lineedit.nim index 6969171e..a69465a6 100644 --- a/src/display/lineedit.nim +++ b/src/display/lineedit.nim @@ -2,7 +2,7 @@ import std/strutils import std/unicode import bindings/quickjs -import display/winattrs +import display/term import js/javascript import types/cell import types/opt diff --git a/src/display/term.nim b/src/display/term.nim index 588951cd..a782fe99 100644 --- a/src/display/term.nim +++ b/src/display/term.nim @@ -9,7 +9,6 @@ import std/unicode import bindings/termcap import config/config -import display/winattrs import types/cell import types/color import types/opt @@ -20,8 +19,6 @@ import chagashi/charset import chagashi/encoder import chagashi/validator -export isatty - #TODO switch from termcap... type @@ -49,6 +46,14 @@ type funcstr: array[256, uint8] caps: array[TermcapCap, cstring] + WindowAttributes* = object + width*: int + height*: int + ppc*: int # cell width + ppl*: int # cell height + width_px*: int + height_px*: int + Terminal* = ref TerminalObj TerminalObj = object cs*: Charset @@ -79,6 +84,12 @@ template CSI(s: varargs[string, `$`]): string = # primary device attributes const DA1 = CSI("c") +# report xterm text area size in pixels +const GEOMPIXEL = CSI(14, "t") + +# report window size in chars +const GEOMCELL = CSI(18, "t") + # device control string template DCS(a, b: char, s: varargs[string]): string = "\eP" & a & b & s.join(';') & "\e\\" @@ -175,6 +186,10 @@ proc clearDisplay(term: Terminal): string = else: return ED() +proc isatty(fd: FileHandle): cint {.importc: "isatty", header: "<unistd.h>".} +proc isatty*(f: File): bool = + return isatty(f.getFileHandle()) != 0 + proc isatty(term: Terminal): bool = term.infile != nil and term.infile.isatty() and term.outfile.isatty() @@ -381,11 +396,6 @@ proc processFormat*(term: Terminal, format: var Format, cellf: Format): string = discard # nothing to do format = cellf -proc windowChange*(term: Terminal, attrs: WindowAttributes) = - term.attrs = attrs - term.canvas = newFixedGrid(attrs.width, attrs.height) - term.cleared = false - proc setTitle*(term: Terminal, title: string) = if term.set_title: let title = if Controls in title: @@ -504,7 +514,21 @@ proc writeGrid*(term: Terminal, grid: FixedGrid, x = 0, y = 0) = cell.format.fgcolor = grid[i].format.fgcolor j += cell[].width() +proc applyConfigDimensions(term: Terminal) = + # screen dimensions + if term.attrs.width == 0 or term.config.display.force_columns: + term.attrs.width = int(term.config.display.columns) + if term.attrs.height == 0 or term.config.display.force_lines: + term.attrs.height = int(term.config.display.lines) + if term.attrs.ppc == 0 or term.config.display.force_pixels_per_column: + term.attrs.ppc = int(term.config.display.pixels_per_column) + if term.attrs.ppl == 0 or term.config.display.force_pixels_per_line: + term.attrs.ppl = int(term.config.display.pixels_per_line) + term.attrs.width_px = term.attrs.ppc * term.attrs.width + term.attrs.height_px = term.attrs.ppl * term.attrs.height + proc applyConfig(term: Terminal) = + # colors, formatting if term.config.display.color_mode.isSome: term.colormode = term.config.display.color_mode.get elif term.isatty(): @@ -524,6 +548,7 @@ proc applyConfig(term: Terminal) = term.defaultBackground = term.config.display.default_background_color.get if term.config.display.default_foreground_color.isSome: term.defaultForeground = term.config.display.default_foreground_color.get + # charsets if term.config.encoding.display_charset.isSome: term.cs = term.config.encoding.display_charset.get else: @@ -536,10 +561,9 @@ proc applyConfig(term: Terminal) = if cs != CHARSET_UNKNOWN: term.cs = cs break + term.applyConfigDimensions() proc outputGrid*(term: Terminal) = - if term.config.display.force_clear: - term.applyConfig() term.outfile.write(term.resetFormat()) let samesize = term.canvas.width == term.pcanvas.width and term.canvas.height == term.pcanvas.height @@ -626,19 +650,32 @@ type attrs: set[QueryAttrs] fgcolor: Option[RGBColor] bgcolor: Option[RGBColor] + widthPx: int + heightPx: int + width: int + height: int -proc queryAttrs(term: Terminal): QueryResult = +proc queryAttrs(term: Terminal, windowOnly: bool): QueryResult = const tcapRGB = 0x524742 # RGB supported? - const outs = - XTGETFG & - XTGETBG & - XTGETTCAP("524742") & - DA1 - term.outfile.write(outs) + if not windowOnly: + const outs = + XTGETFG & + XTGETBG & + GEOMPIXEL & + GEOMCELL & + XTGETTCAP("524742") & + DA1 + term.outfile.write(outs) + else: + const outs = + GEOMPIXEL & + GEOMCELL & + DA1 + term.outfile.write(outs) result = QueryResult(success: false, attrs: {}) while true: template consume(term: Terminal): char = term.infile.readChar() - template fail = break + template fail = return template expect(term: Terminal, c: char) = if term.consume != c: fail @@ -652,20 +689,44 @@ proc queryAttrs(term: Terminal): QueryResult = case term.consume of '[': # CSI - term.expect '?' - var n = 0 - while (let c = term.consume; c != 'c'): - if c == ';': - case n - of 4: result.attrs.incl(qaSixel) - of 22: result.attrs.incl(qaAnsiColor) - else: discard - n = 0 - else: - n *= 10 - n += decValue(c) - result.success = true - break # DA1 returned; done + case (let c = term.consume; c) + of '?': # DA1 + var n = 0 + while (let c = term.consume; c != 'c'): + if c == ';': + case n + of 4: result.attrs.incl(qaSixel) + of 22: result.attrs.incl(qaAnsiColor) + else: discard + n = 0 + else: + n *= 10 + n += decValue(c) + result.success = true + break # DA1 returned; done + of '4', '8': # GEOMPIXEL, GEOMCELL + term.expect ';' + var height = 0 + var width = 0 + while (let c = term.consume; c != ';'): + if (let x = decValue(c); x != -1): + height *= 10 + height += x + else: + fail + while (let c = term.consume; c != 't'): + if (let x = decValue(c); x != -1): + width *= 10 + width += x + else: + fail + if c == '4': # GEOMSIZE + result.widthPx = width + result.heightPx = height + if c == '8': # GEOMCELL + result.width = width + result.height = height + else: fail of ']': # OSC term.expect '1' @@ -723,57 +784,87 @@ proc queryAttrs(term: Terminal): QueryResult = type TermStartResult* = enum tsrSuccess, tsrDA1Fail -proc detectTermAttributes(term: Terminal): TermStartResult = +# when windowOnly, only refresh window size. +proc detectTermAttributes(term: Terminal, windowOnly: bool): TermStartResult = result = tsrSuccess term.tname = getEnv("TERM") if term.tname == "": term.tname = "dosansi" - if term.isatty(): - if term.config.display.query_da1: - let r = term.queryAttrs() - if r.success: # DA1 success - if qaAnsiColor in r.attrs: - term.colormode = ANSI - if qaRGB in r.attrs: - term.colormode = TRUE_COLOR - # just assume the terminal doesn't choke on these. - term.formatmode = {FLAG_STRIKE, FLAG_OVERLINE} - if r.bgcolor.isSome: - term.defaultBackground = r.bgcolor.get - if r.fgcolor.isSome: - term.defaultForeground = r.fgcolor.get - else: - # something went horribly wrong. set result to DA1 fail, pager will - # alert the user - result = tsrDA1Fail - if term.colormode != TRUE_COLOR: - let colorterm = getEnv("COLORTERM") - if colorterm in ["24bit", "truecolor"]: + if not term.isatty(): + return + let fd = term.infile.getFileHandle() + var win: IOctl_WinSize + if ioctl(cint(fd), TIOCGWINSZ, addr win) != -1: + term.attrs.width = int(win.ws_col) + term.attrs.height = int(win.ws_row) + term.attrs.ppc = int(win.ws_xpixel) div term.attrs.width + term.attrs.ppl = int(win.ws_ypixel) div term.attrs.height + if term.config.display.query_da1: + let r = term.queryAttrs(windowOnly) + if r.success: # DA1 success + if r.width != 0: + term.attrs.width = r.width + if r.widthPx != 0: + term.attrs.ppc = r.widthPx div r.width + if r.height != 0: + term.attrs.height = r.height + if r.heightPx != 0: + term.attrs.ppl = r.heightPx div r.height + if windowOnly: + return + if qaAnsiColor in r.attrs: + term.colormode = ANSI + if qaRGB in r.attrs: term.colormode = TRUE_COLOR - when termcap_found: - term.loadTermcap() - if term.tc != nil: - term.smcup = term.hascap(ti) - if term.hascap(ZH): - term.formatmode.incl(FLAG_ITALIC) - if term.hascap(us): - term.formatmode.incl(FLAG_UNDERLINE) - if term.hascap(md): - term.formatmode.incl(FLAG_BOLD) - if term.hascap(mr): - term.formatmode.incl(FLAG_REVERSE) - if term.hascap(mb): - term.formatmode.incl(FLAG_BLINK) + # just assume the terminal doesn't choke on these. + term.formatmode = {FLAG_STRIKE, FLAG_OVERLINE} + if r.bgcolor.isSome: + term.defaultBackground = r.bgcolor.get + if r.fgcolor.isSome: + term.defaultForeground = r.fgcolor.get else: - term.smcup = true - term.formatmode = {low(FormatFlags)..high(FormatFlags)} + # something went horribly wrong. set result to DA1 fail, pager will + # alert the user + result = tsrDA1Fail + if windowOnly: + return + if term.colormode != TRUE_COLOR: + let colorterm = getEnv("COLORTERM") + if colorterm in ["24bit", "truecolor"]: + term.colormode = TRUE_COLOR + when termcap_found: + term.loadTermcap() + if term.tc != nil: + term.smcup = term.hascap(ti) + if term.hascap(ZH): + term.formatmode.incl(FLAG_ITALIC) + if term.hascap(us): + term.formatmode.incl(FLAG_UNDERLINE) + if term.hascap(md): + term.formatmode.incl(FLAG_BOLD) + if term.hascap(mr): + term.formatmode.incl(FLAG_REVERSE) + if term.hascap(mb): + term.formatmode.incl(FLAG_BLINK) + else: + term.smcup = true + term.formatmode = {low(FormatFlags)..high(FormatFlags)} + +proc windowChange*(term: Terminal) = + discard term.detectTermAttributes(windowOnly = true) + term.applyConfigDimensions() + term.canvas = newFixedGrid(term.attrs.width, term.attrs.height) + term.cleared = false proc start*(term: Terminal, infile: File): TermStartResult = term.infile = infile if term.isatty(): term.enableRawMode() - result = term.detectTermAttributes() + result = term.detectTermAttributes(windowOnly = false) + if result == tsrDA1Fail: + term.config.display.query_da1 = false term.applyConfig() + term.canvas = newFixedGrid(term.attrs.width, term.attrs.height) if term.smcup: term.write(term.enableAltScreen()) @@ -788,13 +879,10 @@ proc restart*(term: Terminal) = if term.smcup: term.write(term.enableAltScreen()) -proc newTerminal*(outfile: File, config: Config, attrs: WindowAttributes): - Terminal = - let term = Terminal( +proc newTerminal*(outfile: File, config: Config): Terminal = + return Terminal( outfile: outfile, config: config, defaultBackground: ColorsRGB["black"], defaultForeground: ColorsRGB["white"] ) - term.windowChange(attrs) - return term diff --git a/src/display/winattrs.nim b/src/display/winattrs.nim deleted file mode 100644 index 1fe07080..00000000 --- a/src/display/winattrs.nim +++ /dev/null @@ -1,58 +0,0 @@ -when defined(posix): - import std/termios - -type - WindowAttributes* = object - width*: int - height*: int - ppc*: int # cell width - ppl*: int # cell height - width_px*: int - height_px*: int - -proc isatty(fd: FileHandle): cint {.importc: "isatty", header: "<unistd.h>".} -proc isatty*(f: File): bool = - return isatty(f.getFileHandle()) != 0 - -proc getWindowAttributes*(tty: File): WindowAttributes = - if tty.isatty(): - var win: IOctl_WinSize - if ioctl(cint(getOsFileHandle(tty)), TIOCGWINSZ, addr win) != -1: - var cols = int(win.ws_col) - var rows = int(win.ws_row) - if cols == 0: - cols = 80 - if rows == 0: - rows = 24 - var ppc = int(win.ws_xpixel) div cols - var ppl = int(win.ws_ypixel) div rows - # some terminal emulators (aka vte) don't set ws_xpixel or ws_ypixel. - # solution: use xterm. - if ppc == 0: - ppc = 9 - if ppl == 0: - ppl = 18 - # 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. - return WindowAttributes( - width: cols, - height: rows, - ppc: ppc, - ppl: ppl, - width_px: cols * ppc, - height_px: rows * ppl - ) - # Fallback for ioctl failure - let height = 24 - let width = 80 - let ppc = 9 - let ppl = 18 - return WindowAttributes( - width: width, - height: height, - ppc: ppc, - ppl: ppl, - width_px: ppc * width, - height_px: ppl * height - ) diff --git a/src/html/dom.nim b/src/html/dom.nim index 1104ee90..d322049c 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -10,7 +10,7 @@ import css/cssparser import css/mediaquery import css/sheet import css/values -import display/winattrs +import display/term import html/catom import html/enums import html/event diff --git a/src/html/env.nim b/src/html/env.nim index 8a570b93..9e817ea0 100644 --- a/src/html/env.nim +++ b/src/html/env.nim @@ -2,7 +2,7 @@ import std/selectors import std/streams import bindings/quickjs -import display/winattrs +import display/term import html/catom import html/chadombuilder import html/dom diff --git a/src/layout/engine.nim b/src/layout/engine.nim index 613cd9c3..93a324b3 100644 --- a/src/layout/engine.nim +++ b/src/layout/engine.nim @@ -5,7 +5,7 @@ import std/unicode import css/stylednode import css/values -import display/winattrs +import display/term import layout/box import layout/layoutunit import utils/luwrap diff --git a/src/layout/renderdocument.nim b/src/layout/renderdocument.nim index 7526b111..0313ca2b 100644 --- a/src/layout/renderdocument.nim +++ b/src/layout/renderdocument.nim @@ -3,7 +3,7 @@ import std/unicode import css/stylednode import css/values -import display/winattrs +import display/term import layout/box import layout/engine import layout/layoutunit @@ -11,6 +11,63 @@ import types/cell import types/color import utils/strwidth +type + # A FormatCell *starts* a new terminal formatting context. + # If no FormatCell exists before a given cell, the default formatting is used. + FormatCell* = object + format*: Format + pos*: int + node*: StyledNode + + # Following properties should hold for `formats': + # * Position should be >= 0, <= str.width(). + # * The position of every FormatCell should be greater than the position + # of the previous FormatCell. + FlexibleLine* = object + str*: string + formats*: seq[FormatCell] + + FlexibleGrid* = seq[FlexibleLine] + +func findFormatN*(line: FlexibleLine, pos: int): int = + var i = 0 + while i < line.formats.len: + if line.formats[i].pos > pos: + break + inc i + return i + +func findFormat*(line: FlexibleLine, pos: int): FormatCell = + let i = line.findFormatN(pos) - 1 + if i != -1: + result = line.formats[i] + else: + result.pos = -1 + +func findNextFormat*(line: FlexibleLine, pos: int): FormatCell = + let i = line.findFormatN(pos) + if i < line.formats.len: + result = line.formats[i] + else: + result.pos = -1 + +proc addLine*(grid: var FlexibleGrid) = + grid.add(FlexibleLine()) + +proc addLines*(grid: var FlexibleGrid, n: int) = + grid.setLen(grid.len + n) + +proc insertFormat*(line: var FlexibleLine, i: int, cell: FormatCell) = + line.formats.insert(cell, i) + +proc insertFormat*(line: var FlexibleLine, pos, i: int, format: Format, + node: StyledNode = nil) = + line.insertFormat(i, FormatCell(format: format, node: node, pos: pos)) + +proc addFormat*(line: var FlexibleLine, pos: int, format: Format, + node: StyledNode = nil) = + line.formats.add(FormatCell(format: format, node: node, pos: pos)) + func toFormat(computed: CSSComputedValues): Format = if computed == nil: return Format() @@ -233,7 +290,7 @@ proc paintBackground(grid: var FlexibleGrid; color: CellColor; startx, for y in starty..<endy: # Make sure line.width() >= endx - let linewidth = grid[y].width() + let linewidth = grid[y].str.width() if linewidth < endx: grid[y].str &= ' '.repeat(endx - linewidth) diff --git a/src/local/client.nim b/src/local/client.nim index 71aaac8c..d088982f 100644 --- a/src/local/client.nim +++ b/src/local/client.nim @@ -18,7 +18,6 @@ import bindings/quickjs import config/config import display/lineedit import display/term -import display/winattrs import html/chadombuilder import html/dom import html/event @@ -437,8 +436,7 @@ proc inputLoop(client: Client) = client.handleError(event.fd) if Signal in event.events: assert event.fd == sigwinch - let attrs = getWindowAttributes(client.pager.infile) - client.pager.windowChange(attrs) + client.pager.windowChange() if selectors.Event.Timer in event.events: let r = client.timeouts.runTimeoutFd(event.fd) assert r @@ -690,8 +688,7 @@ proc newClient*(config: Config, forkserver: ForkServer): Client = let jsrt = newJSRuntime() JS_SetModuleLoaderFunc(jsrt, normalizeModuleName, clientLoadJSModule, nil) let jsctx = jsrt.newJSContext() - let attrs = getWindowAttributes(stdout) - let pager = newPager(config, attrs, forkserver, jsctx) + let pager = newPager(config, forkserver, jsctx) let client = Client( config: config, forkserver: forkserver, diff --git a/src/local/container.nim b/src/local/container.nim index 3bab3130..8008a896 100644 --- a/src/local/container.nim +++ b/src/local/container.nim @@ -7,7 +7,7 @@ when defined(posix): import std/posix import config/config -import display/winattrs +import display/term import extern/stdio import io/promise import io/serialize diff --git a/src/local/pager.nim b/src/local/pager.nim index 62746a4e..40b093ac 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -17,7 +17,6 @@ import config/mailcap import config/mimetypes import display/lineedit import display/term -import display/winattrs import extern/editor import extern/runproc import extern/stdio @@ -233,20 +232,17 @@ proc setPaths(pager: Pager): Err[string] = pager.cgiDir = cgiDir return ok() -proc newPager*(config: Config, attrs: WindowAttributes, forkserver: ForkServer, - ctx: JSContext): Pager = +proc newPager*(config: Config, forkserver: ForkServer, ctx: JSContext): Pager = let (mailcap, errs) = config.getMailcap() let pager = Pager( config: config, - display: newFixedGrid(attrs.width, attrs.height - 1), forkserver: forkserver, mailcap: mailcap, mimeTypes: config.getMimeTypes(), omnirules: config.getOmniRules(ctx), proxy: config.getProxy(), siteconf: config.getSiteConfig(ctx), - statusgrid: newFixedGrid(attrs.width), - term: newTerminal(stdout, config, attrs), + term: newTerminal(stdout, config), urimethodmap: config.getURIMethodMap() ) let r = pager.setPaths() @@ -265,6 +261,8 @@ proc launchPager*(pager: Pager, infile: File) = of tsrSuccess: discard of tsrDA1Fail: pager.alert("Failed to query DA1, please set display.query-da1 = false") + pager.display = newFixedGrid(pager.attrs.width, pager.attrs.height - 1) + pager.statusgrid = newFixedGrid(pager.attrs.width) func infile*(pager: Pager): File = return pager.term.infile @@ -458,7 +456,7 @@ proc newBuffer(pager: Pager, bufferConfig: BufferConfig, request: Request, pager.forkserver, bufferConfig, request, - pager.attrs, + pager.term.attrs, title, redirectdepth, canreinterpret, @@ -618,16 +616,18 @@ proc toggleSource(pager: Pager) {.jsfunc.} = pager.container.sourcepair = container pager.addContainer(container) -proc windowChange*(pager: Pager, attrs: WindowAttributes) = - if attrs == pager.attrs: +proc windowChange*(pager: Pager) = + let oldAttrs = pager.attrs + pager.term.windowChange() + if pager.attrs == oldAttrs: + #TODO maybe it's more efficient to let false positives through? return if pager.lineedit.isSome: - pager.lineedit.get.windowChange(attrs) - pager.term.windowChange(attrs) - pager.display = newFixedGrid(attrs.width, attrs.height - 1) - pager.statusgrid = newFixedGrid(attrs.width) + pager.lineedit.get.windowChange(pager.attrs) + pager.display = newFixedGrid(pager.attrs.width, pager.attrs.height - 1) + pager.statusgrid = newFixedGrid(pager.attrs.width) for container in pager.containers: - container.windowChange(attrs) + container.windowChange(pager.attrs) if pager.askprompt != "": pager.writeAskPrompt() pager.showAlerts() diff --git a/src/server/buffer.nim b/src/server/buffer.nim index aef50329..6f8beb34 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -19,7 +19,7 @@ import css/mediaquery import css/sheet import css/stylednode import css/values -import display/winattrs +import display/term import html/catom import html/chadombuilder import html/dom diff --git a/src/server/forkserver.nim b/src/server/forkserver.nim index a62c94df..3e9124d0 100644 --- a/src/server/forkserver.nim +++ b/src/server/forkserver.nim @@ -5,7 +5,7 @@ import std/streams import std/tables import config/config -import display/winattrs +import display/term import io/posixstream import io/serialize import io/serversocket diff --git a/src/types/cell.nim b/src/types/cell.nim index 2f2e5203..d66743c0 100644 --- a/src/types/cell.nim +++ b/src/types/cell.nim @@ -1,10 +1,5 @@ -import std/options -import std/tables - -import css/stylednode import types/color import utils/strwidth -import utils/twtstr type FormatFlags* = enum @@ -21,31 +16,14 @@ type bgcolor*: CellColor flags*: set[FormatFlags] - # A FormatCell *starts* a new terminal formatting context. - # If no FormatCell exists before a given cell, the default formatting is used. - FormatCell* = object - format*: Format - pos*: int - node*: StyledNode - SimpleFormatCell* = object format*: Format pos*: int - # Following properties should hold for `formats': - # * Position should be >= 0, <= str.width(). - # * The position of every FormatCell should be greater than the position - # of the previous FormatCell. - FlexibleLine* = object - str*: string - formats*: seq[FormatCell] - SimpleFlexibleLine* = object str*: string formats*: seq[SimpleFormatCell] - FlexibleGrid* = seq[FlexibleLine] - SimpleFlexibleGrid* = seq[SimpleFlexibleLine] FixedCell* = object @@ -77,13 +55,6 @@ const FormatCodes*: array[FormatFlags, tuple[s, e: uint8]] = [ FLAG_BLINK: (5u8, 25u8), ] -const FormatCodeMap = block: - var res: Table[uint8, tuple[flag: FormatFlags, reverse: bool]] - for x in FormatFlags: - res[FormatCodes[x][0]] = (x, false) - res[FormatCodes[x][1]] = (x, true) - res - template flag_template(format: Format, val: bool, flag: FormatFlags) = if val: format.flags.incl(flag) else: format.flags.excl(flag) @@ -99,14 +70,11 @@ template `blink=`*(f: var Format, b: bool) = flag_template f, b, FLAG_BLINK func newFixedGrid*(w: int, h: int = 1): FixedGrid = return FixedGrid(width: w, height: h, cells: newSeq[FixedCell](w * h)) -func width*(line: FlexibleLine): int = - return line.str.width() - func width*(cell: FixedCell): int = return cell.str.width() # Get the first format cell after pos, if any. -func findFormatN*(line: FlexibleLine|SimpleFlexibleLine, pos: int): int = +func findFormatN*(line: SimpleFlexibleLine, pos: int): int = var i = 0 while i < line.formats.len: if line.formats[i].pos > pos: @@ -114,13 +82,6 @@ func findFormatN*(line: FlexibleLine|SimpleFlexibleLine, pos: int): int = inc i return i -func findFormat*(line: FlexibleLine, pos: int): FormatCell = - let i = line.findFormatN(pos) - 1 - if i != -1: - result = line.formats[i] - else: - result.pos = -1 - func findFormat*(line: SimpleFlexibleLine, pos: int): SimpleFormatCell = let i = line.findFormatN(pos) - 1 if i != -1: @@ -128,176 +89,9 @@ func findFormat*(line: SimpleFlexibleLine, pos: int): SimpleFormatCell = else: result.pos = -1 -func findNextFormat*(line: FlexibleLine, pos: int): FormatCell = - let i = line.findFormatN(pos) - if i < line.formats.len: - result = line.formats[i] - else: - result.pos = -1 - func findNextFormat*(line: SimpleFlexibleLine, pos: int): SimpleFormatCell = let i = line.findFormatN(pos) if i < line.formats.len: result = line.formats[i] else: result.pos = -1 - -proc addLine*(grid: var FlexibleGrid) = - grid.add(FlexibleLine()) - -proc addLines*(grid: var FlexibleGrid, n: int) = - grid.setLen(grid.len + n) - -proc insertFormat*(line: var FlexibleLine, i: int, cell: FormatCell) = - line.formats.insert(cell, i) - -proc insertFormat*(line: var FlexibleLine, pos, i: int, format: Format, - node: StyledNode = nil) = - line.insertFormat(i, FormatCell(format: format, node: node, pos: pos)) - -proc addFormat*(line: var FlexibleLine, pos: int, format: Format, - node: StyledNode = nil) = - line.formats.add(FormatCell(format: format, node: node, pos: pos)) - -# https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf -type - AnsiCodeParseState* = enum - PARSE_START, PARSE_PARAMS, PARSE_INTERM, PARSE_FINAL, PARSE_DONE - - AnsiCodeParser* = object - state*: AnsiCodeParseState - params: string - -proc getParam(parser: AnsiCodeParser, i: var int, colon = false): string = - while i < parser.params.len and - not (parser.params[i] == ';' or colon and parser.params[i] == ':'): - result &= parser.params[i] - inc i - if i < parser.params.len: - inc i - -template getParamU8(parser: AnsiCodeParser, i: var int, - colon = false): uint8 = - if i >= parser.params.len: - return false - let u = parseUInt8(parser.getParam(i)) - if u.isNone: - return false - u.get - -proc parseSGRDefColor(parser: AnsiCodeParser, format: var Format, - i: var int, isfg: bool): bool = - let u = parser.getParamU8(i, colon = true) - template set_color(c: CellColor) = - if isfg: - format.fgcolor = c - else: - format.bgcolor = c - if u == 2: - let param0 = parser.getParamU8(i, colon = true) - if i < parser.params.len: - let r = param0 - let g = parser.getParamU8(i, colon = true) - let b = parser.getParamU8(i, colon = true) - set_color cellColor(rgb(r, g, b)) - else: - set_color cellColor(gray(param0)) - elif u == 5: - let param0 = parser.getParamU8(i, colon = true) - if param0 in 0u8..15u8: - set_color cellColor(ANSIColor(param0)) - elif param0 in 16u8..255u8: - set_color cellColor(EightBitColor(param0)) - else: - return false - -proc parseSGRColor(parser: AnsiCodeParser, format: var Format, - i: var int, u: uint8): bool = - if u in 30u8..37u8: - format.fgcolor = cellColor(ANSIColor(u - 30)) - elif u == 38: - return parser.parseSGRDefColor(format, i, isfg = true) - elif u == 39: - format.fgcolor = defaultColor - elif u in 40u8..47u8: - format.bgcolor = cellColor(ANSIColor(u - 40)) - elif u == 48: - return parser.parseSGRDefColor(format, i, isfg = false) - elif u == 49: - format.bgcolor = defaultColor - elif u in 90u8..97u8: - format.fgcolor = cellColor(ANSIColor(u - 82)) - elif u in 100u8..107u8: - format.bgcolor = cellColor(ANSIColor(u - 92)) - else: - return false - return true - -proc parseSGRAspect(parser: AnsiCodeParser, format: var Format, - i: var int): bool = - let u = parser.getParamU8(i) - if u in FormatCodeMap: - let entry = FormatCodeMap[u] - if entry.reverse: - format.flags.excl(entry.flag) - else: - format.flags.incl(entry.flag) - return true - elif u == 0: - format = Format() - return true - else: - return parser.parseSGRColor(format, i, u) - -proc parseSGR(parser: AnsiCodeParser, format: var Format) = - if parser.params.len == 0: - format = Format() - else: - var i = 0 - while i < parser.params.len: - if not parser.parseSGRAspect(format, i): - break - -proc parseControlFunction(parser: var AnsiCodeParser, format: var Format, - f: char) = - case f - of 'm': - parser.parseSGR(format) - else: discard # unknown - -proc reset*(parser: var AnsiCodeParser) = - parser.state = PARSE_START - parser.params = "" - -proc parseAnsiCode*(parser: var AnsiCodeParser, format: var Format, - c: char): bool = - case parser.state - of PARSE_START: - if 0x40 <= int(c) and int(c) <= 0x5F: - if c != '[': - #C1, TODO? - parser.state = PARSE_DONE - else: - parser.state = PARSE_PARAMS - else: - parser.state = PARSE_DONE - return true - of PARSE_PARAMS: - if 0x30 <= int(c) and int(c) <= 0x3F: - parser.params &= c - else: - parser.state = PARSE_INTERM - return parser.parseAnsiCode(format, c) - of PARSE_INTERM: - if 0x20 <= int(c) and int(c) <= 0x2F: - discard - else: - parser.state = PARSE_FINAL - return parser.parseAnsiCode(format, c) - of PARSE_FINAL: - parser.state = PARSE_DONE - if 0x40 <= int(c) and int(c) <= 0x7E: - parser.parseControlFunction(format, c) - else: - return true - of PARSE_DONE: discard |