diff options
author | bptato <nincsnevem662@gmail.com> | 2024-02-25 20:25:04 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-02-25 20:25:04 +0100 |
commit | 92fbf479f8bf6db6ad0655e5b71a0b58a45a2213 (patch) | |
tree | 47b7f2c23ebabbf94e48476615014b52762542f3 | |
parent | 4df71520f39ed7992f78484701467e513a34c5dc (diff) | |
download | chawan-92fbf479f8bf6db6ad0655e5b71a0b58a45a2213.tar.gz |
term: improve pixels-per-column/line detection
Some terminal emulators (AKA vte) refuse to set ws_xpixel and ws_ypixel in the TIOCGWINSZ ioctl, so we now query for CSI 14 t as well. (Also CSI 18 t for good measure, just in case we can't ioctl for some reason.) Also added some fallback (optionally forced) config values for width, height, ppc, and ppl. (This is especially useful in dump mode.)
-rw-r--r-- | doc/config.md | 16 | ||||
-rw-r--r-- | res/config.toml | 8 | ||||
-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 |
17 files changed, 280 insertions, 370 deletions
diff --git a/doc/config.md b/doc/config.md index 7e43300c..e0208e16 100644 --- a/doc/config.md +++ b/doc/config.md @@ -432,6 +432,22 @@ output will most likely look horrible. (Except, obviously, if your terminal does not support Primary Device Attributes.)</td> </tr> +<tr> +<td>columns, lines, pixels-per-column, pixels-per-line</td> +<td>number</td> +<td>Fallback values for the number of columns, lines, pixels per +column, and pixels per line for the cases where it cannot be determined +automatically. (For example, these values are used in dump mode.)</td> +</tr> + +<tr> +<td>force-columns, force-lines, force-pixels-per-column, +force-pixels-per-line</td> +<td>boolean</td> +<td>Force-set columns, lines, pixels per column, or pixels per line to the +fallback values provided above.</td> +</tr> + </table> ## Omnirule diff --git a/res/config.toml b/res/config.toml index e8f651db..d95b8b2a 100644 --- a/res/config.toml +++ b/res/config.toml @@ -66,6 +66,14 @@ set-title = true default-background-color = "auto" default-foreground-color = "auto" query-da1 = true +columns = 80 +lines = 24 +pixels-per-column = 9 +pixels-per-line = 18 +force-columns = false +force-lines = false +force-pixels-per-column = false +force-pixels-per-line = false [[omnirule]] match = '^ddg:' 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 |