diff options
author | bptato <nincsnevem662@gmail.com> | 2024-03-14 20:57:45 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-03-14 21:05:16 +0100 |
commit | d26766c4c4015990703e84e8136f96d222edbc97 (patch) | |
tree | 7f412f8ca98d2b04323da5cf2fd607efbd6c408d /src/display/term.nim | |
parent | a8f05f18fdd64485c26b453e62e8073b50e271ef (diff) | |
download | chawan-d26766c4c4015990703e84e8136f96d222edbc97.tar.gz |
Move around some modules
* extern -> gone, runproc absorbed by pager, others moved into io/ * display -> local/ (where else would we display?) * xhr -> html/ * move out WindowAttributes from term, so we don't depend on local from server
Diffstat (limited to 'src/display/term.nim')
-rw-r--r-- | src/display/term.nim | 906 |
1 files changed, 0 insertions, 906 deletions
diff --git a/src/display/term.nim b/src/display/term.nim deleted file mode 100644 index cb96a385..00000000 --- a/src/display/term.nim +++ /dev/null @@ -1,906 +0,0 @@ -import std/options -import std/os -import std/posix -import std/streams -import std/strutils -import std/tables -import std/termios -import std/unicode - -import bindings/termcap -import config/config -import types/cell -import types/color -import types/opt -import utils/strwidth -import utils/twtstr - -import chagashi/charset -import chagashi/encoder -import chagashi/validator - -#TODO switch from termcap... - -type - TermcapCap = enum - ce # clear till end of line - cd # clear display - cm # cursor move - ti # terminal init (=smcup) - te # terminal end (=rmcup) - so # start standout mode - md # start bold mode - us # start underline mode - mr # start reverse mode - mb # start blink mode - ZH # start italic mode - ue # end underline mode - se # end standout mode - me # end all formatting modes - vs # enhance cursor - vi # make cursor invisible - ve # reset cursor to normal - - Termcap = ref object - bp: array[1024, uint8] - 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 - config: Config - infile*: File - outfile: File - cleared: bool - canvas: FixedGrid - pcanvas: FixedGrid - attrs*: WindowAttributes - colormode: ColorMode - formatmode: FormatMode - smcup: bool - tc: Termcap - tname: string - set_title: bool - stdin_unblocked: bool - orig_flags: cint - orig_flags2: cint - orig_termios: Termios - defaultBackground: RGBColor - defaultForeground: RGBColor - -# control sequence introducer -template CSI(s: varargs[string, `$`]): string = - "\e[" & s.join(';') - -# 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") - -# allow shift-key to override mouse protocol -const XTSHIFTESCAPE = CSI(">0s") - -# device control string -template DCS(a, b: char, s: varargs[string]): string = - "\eP" & a & b & s.join(';') & "\e\\" - -template XTGETTCAP(s: varargs[string, `$`]): string = - DCS('+', 'q', s) - -# OS command -template OSC(s: varargs[string, `$`]): string = - "\e]" & s.join(';') & '\a' - -template XTERM_TITLE(s: string): string = - OSC(0, s) - -const XTGETFG = OSC(10, "?") # get foreground color -const XTGETBG = OSC(11, "?") # get background color - -# DEC set -template DECSET(s: varargs[string, `$`]): string = - "\e[?" & s.join(';') & 'h' - -# DEC reset -template DECRST(s: varargs[string, `$`]): string = - "\e[?" & s.join(';') & 'l' - -# alt screen -const SMCUP = DECSET(1049) -const RMCUP = DECRST(1049) - -# mouse tracking -const SGRMOUSEBTNON = DECSET(1002, 1006) -const SGRMOUSEBTNOFF = DECRST(1002, 1006) - -when not termcap_found: - const CNORM = DECSET(25) - const CIVIS = DECRST(25) - template HVP(s: varargs[string, `$`]): string = - CSI(s) & "f" - template EL(): string = - CSI() & "K" - template ED(): string = - CSI() & "J" - - proc write(term: Terminal, s: string) = - term.outfile.write(s) -else: - func hascap(term: Terminal, c: TermcapCap): bool = term.tc.caps[c] != nil - func cap(term: Terminal, c: TermcapCap): string = $term.tc.caps[c] - func ccap(term: Terminal, c: TermcapCap): cstring = term.tc.caps[c] - - var goutfile: File - proc putc(c: char): cint {.cdecl.} = - goutfile.write(c) - - proc write(term: Terminal, s: cstring) = - discard tputs(s, 1, putc) - - proc write(term: Terminal, s: string) = - term.write(cstring(s)) - -template SGR*(s: varargs[string, `$`]): string = - CSI(s) & "m" - -#TODO a) this should be customizable b) these defaults sucks -const ANSIColorMap = [ - rgb(0, 0, 0), - rgb(205, 0, 0), - rgb(0, 205, 0), - rgb(205, 205, 0), - rgb(0, 0, 238), - rgb(205, 0, 205), - rgb(0, 205, 205), - rgb(229, 229, 229), - rgb(127, 127, 127), - rgb(255, 0, 0), - rgb(0, 255, 0), - rgb(255, 255, 0), - rgb(92, 92, 255), - rgb(255, 0, 255), - rgb(0, 255, 255), - rgb(255, 255, 255) -] - -proc flush*(term: Terminal) = - term.outfile.flushFile() - -proc cursorGoto(term: Terminal, x, y: int): string = - when termcap_found: - return $tgoto(term.ccap cm, cint(x), cint(y)) - else: - return HVP(y + 1, x + 1) - -proc clearEnd(term: Terminal): string = - when termcap_found: - return term.cap ce - else: - return EL() - -proc clearDisplay(term: Terminal): string = - when termcap_found: - return term.cap cd - 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() - -proc anyKey*(term: Terminal) = - if term.isatty(): - term.outfile.write("[Hit any key]") - discard term.infile.readChar() - -proc resetFormat(term: Terminal): string = - when termcap_found: - if term.isatty(): - return term.cap me - return SGR() - -proc startFormat(term: Terminal, flag: FormatFlags): string = - when termcap_found: - if term.isatty(): - case flag - of FLAG_BOLD: return term.cap md - of FLAG_UNDERLINE: return term.cap us - of FLAG_REVERSE: return term.cap mr - of FLAG_BLINK: return term.cap mb - of FLAG_ITALIC: return term.cap ZH - else: discard - return SGR(FormatCodes[flag].s) - -proc endFormat(term: Terminal, flag: FormatFlags): string = - when termcap_found: - if flag == FLAG_UNDERLINE and term.isatty(): - return term.cap ue - return SGR(FormatCodes[flag].e) - -proc setCursor*(term: Terminal, x, y: int) = - term.write(term.cursorGoto(x, y)) - -proc enableAltScreen(term: Terminal): string = - when termcap_found: - if term.hascap ti: - return term.cap ti - return SMCUP - -proc disableAltScreen(term: Terminal): string = - when termcap_found: - if term.hascap te: - return term.cap te - return RMCUP - -func mincontrast(term: Terminal): int32 = - return term.config.display.minimum_contrast - -proc getRGB(a: CellColor, termDefault: RGBColor): RGBColor = - case a.t - of ctNone: - return termDefault - of ctANSI: - if a.color >= 16: - return EightBitColor(a.color).toRGB() - return ANSIColorMap[a.color] - of ctRGB: - return a.rgbcolor - -# Use euclidian distance to quantize RGB colors. -proc approximateANSIColor(rgb, termDefault: RGBColor): CellColor = - var a = 0i32 - var n = -1 - for i in -1 .. ANSIColorMap.high: - let color = if i >= 0: - ANSIColorMap[i] - else: - termDefault - if color == rgb: - return if i == -1: defaultColor else: ANSIColor(i).cellColor() - let x = int32(color.r) - int32(rgb.r) - let y = int32(color.g) - int32(rgb.g) - let z = int32(color.b) - int32(rgb.b) - let xx = x * x - let yy = y * y - let zz = z * z - let b = xx + yy + zz - if i == -1 or b < a: - n = i - a = b - return if n == -1: defaultColor else: ANSIColor(n).cellColor() - -# Return a fgcolor contrasted to the background by term.mincontrast. -proc correctContrast(term: Terminal, bgcolor, fgcolor: CellColor): CellColor = - let contrast = term.mincontrast - let cfgcolor = fgcolor - let bgcolor = getRGB(bgcolor, term.defaultBackground) - let fgcolor = getRGB(fgcolor, term.defaultForeground) - let bgY = int(bgcolor.Y) - var fgY = int(fgcolor.Y) - let diff = abs(bgY - fgY) - if diff < contrast: - if bgY > fgY: - fgY = bgY - contrast - if fgY < 0: - fgY = bgY + contrast - if fgY > 255: - fgY = 0 - else: - fgY = bgY + contrast - if fgY > 255: - fgY = bgY - contrast - if fgY < 0: - fgY = 255 - let newrgb = YUV(cast[uint8](fgY), fgcolor.U, fgcolor.V) - case term.colormode - of TRUE_COLOR: - return cellColor(newrgb) - of ANSI: - return approximateANSIColor(newrgb, term.defaultForeground) - of EIGHT_BIT: - return cellColor(newrgb.toEightBit()) - of MONOCHROME: - doAssert false - return cfgcolor - -template ansiSGR(n: uint8, bgmod: int): string = - if n < 8: - SGR(30 + bgmod + n) - else: - SGR(82 + bgmod + n) - -template eightBitSGR(n: uint8, bgmod: int): string = - if n < 16: - ansiSGR(n, bgmod) - else: - SGR(38 + bgmod, 5, n) - -template rgbSGR(rgb: RGBColor, bgmod: int): string = - SGR(38 + bgmod, 2, rgb.r, rgb.g, rgb.b) - -proc processFormat*(term: Terminal, format: var Format, cellf: Format): string = - for flag in FormatFlags: - if flag in term.formatmode: - if flag in format.flags and flag notin cellf.flags: - result &= term.endFormat(flag) - if flag notin format.flags and flag in cellf.flags: - result &= term.startFormat(flag) - var cellf = cellf - case term.colormode - of ANSI: - # quantize - if cellf.bgcolor.t == ctANSI and cellf.bgcolor.color > 15: - cellf.bgcolor = cellf.fgcolor.eightbit.toRGB().cellColor() - if cellf.bgcolor.t == ctRGB: - cellf.bgcolor = approximateANSIColor(cellf.bgcolor.rgbcolor, - term.defaultBackground) - if cellf.fgcolor.t == ctANSI and cellf.fgcolor.color > 15: - cellf.fgcolor = cellf.fgcolor.eightbit.toRGB().cellColor() - if cellf.fgcolor.t == ctRGB: - if cellf.bgcolor.t == ctNone: - cellf.fgcolor = approximateANSIColor(cellf.fgcolor.rgbcolor, - term.defaultForeground) - else: - # ANSI fgcolor + bgcolor at the same time is broken - cellf.fgcolor = defaultColor - # correct - cellf.fgcolor = term.correctContrast(cellf.bgcolor, cellf.fgcolor) - # print - case cellf.fgcolor.t - of ctNone: result &= SGR(39) - of ctANSI: result &= ansiSGR(cellf.fgcolor.color, 0) - else: assert false - case cellf.bgcolor.t - of ctNone: result &= SGR(49) - of ctANSI: result &= ansiSGR(cellf.bgcolor.color, 10) - else: assert false - of EIGHT_BIT: - # quantize - if cellf.bgcolor.t == ctRGB: - cellf.bgcolor = cellf.bgcolor.rgbcolor.toEightBit().cellColor() - if cellf.fgcolor.t == ctRGB: - cellf.fgcolor = cellf.fgcolor.rgbcolor.toEightBit().cellColor() - # correct - cellf.fgcolor = term.correctContrast(cellf.bgcolor, cellf.fgcolor) - # print - case cellf.fgcolor.t - of ctNone: result &= SGR(39) - of ctANSI: result &= eightBitSGR(cellf.fgcolor.color, 0) - of ctRGB: assert false - case cellf.bgcolor.t - of ctNone: result &= SGR(49) - of ctANSI: result &= eightBitSGR(cellf.bgcolor.color, 10) - of ctRGB: assert false - of TRUE_COLOR: - # correct - cellf.fgcolor = term.correctContrast(cellf.bgcolor, cellf.fgcolor) - # print - if cellf.fgcolor != format.fgcolor: - case cellf.fgcolor.t - of ctNone: result &= SGR(39) - of ctANSI: result &= eightBitSGR(cellf.fgcolor.color, 0) - of ctRGB: result &= rgbSGR(cellf.fgcolor.rgbcolor, 0) - if cellf.bgcolor != format.bgcolor: - case cellf.bgcolor.t - of ctNone: result &= SGR(49) - of ctANSI: result &= eightBitSGR(cellf.bgcolor.color, 10) - of ctRGB: result &= rgbSGR(cellf.bgcolor.rgbcolor, 10) - of MONOCHROME: - discard # nothing to do - format = cellf - -proc setTitle*(term: Terminal, title: string) = - if term.set_title: - term.outfile.write(XTERM_TITLE(title.replaceControls())) - -proc enableMouse*(term: Terminal) = - term.write(XTSHIFTESCAPE & SGRMOUSEBTNON) - -proc disableMouse*(term: Terminal) = - term.write(SGRMOUSEBTNOFF) - -proc processOutputString*(term: Terminal, str: string, w: var int): string = - if str.validateUTF8Surr() != -1: - return "?" - # twidth wouldn't work here, the view may start at the nth character. - # pager must ensure tabs are converted beforehand. - w += str.notwidth() - let str = if Controls in str: - str.replaceControls() - else: - str - if term.cs == CHARSET_UTF_8: - # The output encoding matches the internal representation. - return str - else: - # Output is not utf-8, so we must encode it first. - var success = false - return newTextEncoder(term.cs).encodeAll(str, success) - -proc generateFullOutput(term: Terminal, grid: FixedGrid): string = - var format = Format() - result &= term.cursorGoto(0, 0) - result &= term.resetFormat() - result &= term.clearDisplay() - for y in 0 ..< grid.height: - if y != 0: - result &= "\r\n" - var w = 0 - for x in 0 ..< grid.width: - while w < x: - result &= " " - inc w - let cell = grid[y * grid.width + x] - result &= term.processFormat(format, cell.format) - result &= term.processOutputString(cell.str, w) - -proc generateSwapOutput(term: Terminal, grid, prev: FixedGrid): string = - var vy = -1 - for y in 0 ..< grid.height: - var w = 0 - var change = false - # scan for changes, and set cx to x of the first change - var cx = 0 - # if there is a change, we have to start from the last x with - # a string (otherwise we might overwrite a double-width char) - var lastx = 0 - for x in 0 ..< grid.width: - let i = y * grid.width + x - if grid[i].str != "": - lastx = x - if grid[i] != prev[i]: - change = true - cx = lastx - w = lastx - break - if change: - if cx == 0 and vy != -1: - while vy < y: - result &= "\r\n" - inc vy - else: - result &= term.cursorGoto(cx, y) - vy = y - result &= term.resetFormat() - var format = Format() - for x in cx ..< grid.width: - while w < x: # if previous cell had no width, catch up with x - result &= ' ' - inc w - let cell = grid[y * grid.width + x] - result &= term.processFormat(format, cell.format) - result &= term.processOutputString(cell.str, w) - if w < grid.width: - result &= term.clearEnd() - -proc hideCursor*(term: Terminal) = - when termcap_found: - term.write(term.ccap vi) - else: - term.write(CIVIS) - -proc showCursor*(term: Terminal) = - when termcap_found: - term.write(term.ccap ve) - else: - term.write(CNORM) - -func emulateOverline(term: Terminal): bool = - term.config.display.emulate_overline and - FLAG_OVERLINE notin term.formatmode and FLAG_UNDERLINE in term.formatmode - -proc writeGrid*(term: Terminal, grid: FixedGrid, x = 0, y = 0) = - for ly in y ..< y + grid.height: - for lx in x ..< x + grid.width: - let i = ly * term.canvas.width + lx - term.canvas[i] = grid[(ly - y) * grid.width + (lx - x)] - let isol = FLAG_OVERLINE in term.canvas[i].format.flags - if i >= term.canvas.width and isol and term.emulateOverline: - let w = grid[(ly - y) * grid.width + (lx - x)].width() - let s = i - term.canvas.width - var j = s - while j < term.canvas.len and j < s + w: - let cell = addr term.canvas[j] - cell.format.flags.incl(FLAG_UNDERLINE) - if cell.str == "": - cell.str = " " - if cell.str == " ": - let i = (ly - y) * grid.width + (lx - x) - 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(): - let colorterm = getEnv("COLORTERM") - if colorterm in ["24bit", "truecolor"]: - term.colormode = TRUE_COLOR - if term.config.display.format_mode.isSome: - term.formatmode = term.config.display.format_mode.get - for fm in FormatFlags: - if fm in term.config.display.no_format_mode: - term.formatmode.excl(fm) - if term.isatty(): - if term.config.display.alt_screen.isSome: - term.smcup = term.config.display.alt_screen.get - term.set_title = term.config.display.set_title - if term.config.display.default_background_color.isSome: - 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: - term.cs = DefaultCharset - for s in ["LC_ALL", "LC_CTYPE", "LANG"]: - let env = getEnv(s) - if env == "": - continue - let cs = getLocaleCharset(env) - if cs != CHARSET_UNKNOWN: - term.cs = cs - break - term.applyConfigDimensions() - -proc outputGrid*(term: Terminal) = - term.outfile.write(term.resetFormat()) - let samesize = term.canvas.width == term.pcanvas.width and - term.canvas.height == term.pcanvas.height - if term.config.display.force_clear or not term.cleared or not samesize: - term.outfile.write(term.generateFullOutput(term.canvas)) - term.cleared = true - else: - term.outfile.write(term.generateSwapOutput(term.canvas, term.pcanvas)) - if not samesize: - term.pcanvas.width = term.canvas.width - term.pcanvas.height = term.canvas.height - term.pcanvas.cells.setLen(term.canvas.cells.len) - for i in 0 ..< term.canvas.cells.len: - term.pcanvas[i] = term.canvas[i] - -proc clearCanvas*(term: Terminal) = - term.cleared = false - -# see https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html -proc disableRawMode(term: Terminal) = - let fd = term.infile.getFileHandle() - discard tcSetAttr(fd, TCSAFLUSH, addr term.orig_termios) - -proc enableRawMode(term: Terminal) = - let fd = term.infile.getFileHandle() - discard tcGetAttr(fd, addr term.orig_termios) - var raw = term.orig_termios - raw.c_iflag = raw.c_iflag and not (BRKINT or ICRNL or INPCK or ISTRIP or IXON) - raw.c_oflag = raw.c_oflag and not (OPOST) - raw.c_cflag = raw.c_cflag or CS8 - raw.c_lflag = raw.c_lflag and not (ECHO or ICANON or ISIG or IEXTEN) - discard tcSetAttr(fd, TCSAFLUSH, addr raw) - -proc unblockStdin*(term: Terminal) = - if term.isatty(): - let fd = term.infile.getFileHandle() - term.orig_flags = fcntl(fd, F_GETFL, 0) - let flags = term.orig_flags or O_NONBLOCK - discard fcntl(fd, F_SETFL, flags) - term.stdin_unblocked = true - -proc restoreStdin*(term: Terminal) = - if term.stdin_unblocked: - let fd = term.infile.getFileHandle() - discard fcntl(fd, F_SETFL, term.orig_flags) - term.stdin_unblocked = false - -proc quit*(term: Terminal) = - if term.isatty(): - term.disableRawMode() - if term.smcup: - term.write(term.disableAltScreen()) - else: - term.write(term.cursorGoto(0, term.attrs.height - 1) & - term.resetFormat() & "\n") - if term.config.input.use_mouse: - term.disableMouse() - term.showCursor() - term.cleared = false - if term.stdin_unblocked: - let fd = term.infile.getFileHandle() - term.orig_flags2 = fcntl(fd, F_GETFL, 0) - discard fcntl(fd, F_SETFL, term.orig_flags2 and (not O_NONBLOCK)) - term.stdin_unblocked = false - else: - term.orig_flags2 = -1 - term.flush() - -when termcap_found: - proc loadTermcap(term: Terminal) = - assert goutfile == nil - goutfile = term.outfile - let tc = new Termcap - if tgetent(cast[cstring](addr tc.bp), cstring(term.tname)) == 1: - term.tc = tc - for id in TermcapCap: - tc.caps[id] = tgetstr(cstring($id), cast[ptr cstring](addr tc.funcstr)) - else: - raise newException(Defect, "Failed to load termcap description for terminal " & term.tname) - -type - QueryAttrs = enum - qaAnsiColor, qaRGB, qaSixel - - QueryResult = object - success: bool - attrs: set[QueryAttrs] - fgcolor: Option[RGBColor] - bgcolor: Option[RGBColor] - widthPx: int - heightPx: int - width: int - height: int - -proc queryAttrs(term: Terminal, windowOnly: bool): QueryResult = - const tcapRGB = 0x524742 # RGB supported? - 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 = return - template expect(term: Terminal, c: char) = - if term.consume != c: - fail - template expect(term: Terminal, s: string) = - for c in s: - term.expect c - template skip_until(term: Terminal, c: char) = - while (let cc = term.consume; cc != c): - discard - term.expect '\e' - case term.consume - of '[': - # CSI - 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' - let c = term.consume - if c notin {'0', '1'}: fail - term.expect ';' - if term.consume == 'r' and term.consume == 'g' and term.consume == 'b': - term.expect ':' - template eat_color(tc: char): uint8 = - var val = 0u8 - var i = 0 - while (let c = term.consume; c != tc): - let v0 = hexValue(c) - if i > 4 or v0 == -1: fail # wat - let v = uint8(v0) - if i == 0: # 1st place - val = (v shl 4) or v - elif i == 1: # 2nd place - val = (val xor 0xF) or v - # all other places are irrelevant - inc i - val - let r = eat_color '/' - let g = eat_color '/' - let b = eat_color '\a' - if c == '0': - result.fgcolor = some(rgb(r, g, b)) - else: - result.bgcolor = some(rgb(r, g, b)) - else: - # not RGB, give up - term.skip_until '\a' - of 'P': - # DCS - let c = term.consume - if c notin {'0', '1'}: - fail - term.expect "+r" - if c == '1': - var id = 0 - while (let c = term.consume; c != '='): - if c notin AsciiHexDigit: - fail - id *= 0x10 - id += hexValue(c) - term.skip_until '\e' # ST (1) - if id == tcapRGB: - result.attrs.incl(qaRGB) - else: # 0 - term.expect '\e' # ST (1) - term.expect '\\' # ST (2) - else: - fail - -type TermStartResult* = enum - tsrSuccess, tsrDA1Fail - -# 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 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 - # 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 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(windowOnly = false) - if result == tsrDA1Fail: - term.config.display.query_da1 = false - if term.isatty() and term.config.input.use_mouse: - term.enableMouse() - term.applyConfig() - term.canvas = newFixedGrid(term.attrs.width, term.attrs.height) - if term.smcup: - term.write(term.enableAltScreen()) - -proc restart*(term: Terminal) = - if term.isatty(): - term.enableRawMode() - if term.orig_flags2 != -1: - let fd = term.infile.getFileHandle() - discard fcntl(fd, F_SETFL, term.orig_flags2) - term.orig_flags2 = 0 - term.stdin_unblocked = true - if term.config.input.use_mouse: - term.enableMouse() - if term.smcup: - term.write(term.enableAltScreen()) - -proc newTerminal*(outfile: File, config: Config): Terminal = - return Terminal( - outfile: outfile, - config: config, - defaultBackground: ColorsRGB["black"], - defaultForeground: ColorsRGB["white"] - ) |