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:
let title = if Controls in title:
title.replaceControls()
else:
title
term.outfile.write(XTERM_TITLE(title))
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"]
)