diff options
author | bptato <nincsnevem662@gmail.com> | 2024-08-17 01:52:31 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-08-17 02:08:31 +0200 |
commit | f5f841dc7528098c2a07bd06280fa9d1c4ea3164 (patch) | |
tree | b108c8b8668182b25ab3670df3878dd639712d72 | |
parent | ad66c25c72ce2b2ae43e3d7a848836fdea4ee572 (diff) | |
download | chawan-f5f841dc7528098c2a07bd06280fa9d1c4ea3164.tar.gz |
term: don't panic if termcap tgetent fails
If TERM is unrecognized by termcap, retry as dosansi. When that fails, just fall back to the non-termcap code path. (There is no reason to panic without termcap; it's just one of the several capability detection mechanisms we use.)
-rw-r--r-- | src/bindings/termcap.nim | 19 | ||||
-rw-r--r-- | src/local/term.nim | 165 | ||||
-rw-r--r-- | src/main.nim | 11 |
3 files changed, 110 insertions, 85 deletions
diff --git a/src/bindings/termcap.nim b/src/bindings/termcap.nim index 6d3d0e37..2f5bb701 100644 --- a/src/bindings/termcap.nim +++ b/src/bindings/termcap.nim @@ -1,5 +1,6 @@ import std/os -const termlib = (proc(): string = + +const Termlib* = (proc(): string = const libs = [ "terminfo", "mytinfo", "termlib", "termcap", "tinfo", "ncurses", "curses" ] @@ -15,18 +16,18 @@ const termlib = (proc(): string = for dir in dirs: if fileExists(dir & "/lib" & lib & ".a"): return "-l" & lib + return "" )() -when termlib != "": - {.passl: termlib.} + +const TermcapFound* = Termlib != "" + +when TermcapFound: + {.passl: Termlib.} {.push importc, cdecl.} - const termcap_found* = true - proc tgetent*(bp: cstring; name: cstring): cint + proc tgetent*(bp, name: cstring): cint proc tgetnum*(id: cstring): cint proc tgetflag*(id: cstring): cint proc tgetstr*(id: cstring; area: ptr cstring): cstring proc tgoto*(cap: cstring; x, y: cint): cstring - proc tputs*(str: cstring; len: cint; putc: proc(c: char): cint {.cdecl.}): - cint + proc tputs*(s: cstring; len: cint; putc: proc(c: char): cint {.cdecl.}): cint {.pop.} -else: - const termcap_found* = false diff --git a/src/local/term.nim b/src/local/term.nim index 57f665ff..82a892dd 100644 --- a/src/local/term.nim +++ b/src/local/term.nim @@ -88,11 +88,10 @@ type imageMode*: ImageMode smcup: bool tc: Termcap - tname: string - set_title: bool + setTitle: bool stdinUnblocked: bool stdinWasUnblocked: bool - orig_termios: Termios + origTermios: Termios defaultBackground: RGBColor defaultForeground: RGBColor ibuf*: string # buffer for chars when we can't process them @@ -137,6 +136,22 @@ const XTNUMREGS = XTSMGRAPHICS(1, 1, 0) # image dimensions const XTIMGDIMS = XTSMGRAPHICS(2, 1, 0) +# horizontal & vertical position +template HVP(s: varargs[string, `$`]): string = + CSI(s) & "f" + +# erase line +template EL(): string = + CSI() & "K" + +# erase display +template ED(): string = + CSI() & "J" + +# select graphic rendition +template SGR*(s: varargs[string, `$`]): string = + CSI(s) & "m" + # device control string const DCSSTART = "\eP" @@ -179,6 +194,10 @@ const RMCUP = DECRST(1049) const SGRMOUSEBTNON = DECSET(1002, 1006) const SGRMOUSEBTNOFF = DECRST(1002, 1006) +# show/hide cursor +const CNORM = DECSET(25) +const CIVIS = DECRST(25) + # application program command # This is only used in kitty images, and join()'ing kilobytes of base64 @@ -188,19 +207,7 @@ const ST = "\e\\" const KITTYQUERY = APC & "Gi=1,a=q;" & ST -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: +when TermcapFound: 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] @@ -213,7 +220,13 @@ else: discard tputs(s, 1, putc) proc write(term: Terminal; s: string) = - term.write(cstring(s)) + if term.tc != nil: + term.write(cstring(s)) + else: + term.outfile.write(s) +else: + proc write(term: Terminal; s: string) = + term.outfile.write(s) proc readChar*(term: Terminal): char = if term.ibuf.len == 0: @@ -222,29 +235,26 @@ proc readChar*(term: Terminal): char = result = term.ibuf[0] term.ibuf.delete(0..0) -template SGR*(s: varargs[string, `$`]): string = - CSI(s) & "m" - 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) + when TermcapFound: + if term.tc != nil: + return $tgoto(term.ccap cm, cint(x), cint(y)) + return HVP(y + 1, x + 1) proc clearEnd(term: Terminal): string = - when termcap_found: - return term.cap ce - else: - return EL() + when TermcapFound: + if term.tc != nil: + return term.cap ce + return EL() proc clearDisplay(term: Terminal): string = - when termcap_found: - return term.cap cd - else: - return ED() + when TermcapFound: + if term.tc != nil: + return term.cap cd + return ED() proc isatty*(file: File): bool = return file.getFileHandle().isatty() != 0 @@ -260,14 +270,14 @@ proc anyKey*(term: Terminal; msg = "[Hit any key]") = discard term.istream.sreadChar() proc resetFormat(term: Terminal): string = - when termcap_found: - if term.isatty(): + when TermcapFound: + if term.tc != nil: return term.cap me return SGR() proc startFormat(term: Terminal; flag: FormatFlag): string = - when termcap_found: - if term.isatty(): + when TermcapFound: + if term.tc != nil: case flag of ffBold: return term.cap md of ffUnderline: return term.cap us @@ -278,8 +288,8 @@ proc startFormat(term: Terminal; flag: FormatFlag): string = return SGR(FormatCodes[flag].s) proc endFormat(term: Terminal; flag: FormatFlag): string = - when termcap_found: - if term.isatty(): + when TermcapFound: + if term.tc != nil: case flag of ffUnderline: return term.cap ue of ffItalic: return term.cap ZR @@ -294,14 +304,14 @@ proc setCursor*(term: Terminal; x, y: int) = term.cursory = y proc enableAltScreen(term: Terminal): string = - when termcap_found: - if term.hascap ti: + when TermcapFound: + if term.tc != nil and term.hascap ti: return term.cap ti return SMCUP proc disableAltScreen(term: Terminal): string = - when termcap_found: - if term.hascap te: + when TermcapFound: + if term.tc != nil and term.hascap te: return term.cap te return RMCUP @@ -472,7 +482,7 @@ proc processFormat*(term: Terminal; format: var Format; cellf: Format): string = format = cellf proc setTitle*(term: Terminal; title: string) = - if term.set_title: + if term.setTitle: term.outfile.write(XTSETTITLE(title.replaceControls())) proc enableMouse*(term: Terminal) = @@ -546,16 +556,18 @@ proc generateSwapOutput(term: Terminal): string = term.lineDamage[y] = term.attrs.width proc hideCursor*(term: Terminal) = - when termcap_found: - term.write(term.ccap vi) - else: - term.write(CIVIS) + when TermcapFound: + if term.tc != nil: + term.write(term.ccap vi) + return + term.write(CIVIS) proc showCursor*(term: Terminal) = - when termcap_found: - term.write(term.ccap ve) - else: - term.write(CNORM) + when TermcapFound: + if term.tc != nil: + term.write(term.ccap ve) + return + term.write(CNORM) proc writeGrid*(term: Terminal; grid: FixedGrid; x = 0, y = 0) = for ly in y ..< y + grid.height: @@ -603,7 +615,7 @@ proc applyConfig(term: Terminal) = 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 + term.setTitle = 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: @@ -882,11 +894,11 @@ proc clearCanvas*(term: Terminal) = # see https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html proc disableRawMode(term: Terminal) = - discard tcSetAttr(term.istream.fd, TCSAFLUSH, addr term.orig_termios) + discard tcSetAttr(term.istream.fd, TCSAFLUSH, addr term.origTermios) proc enableRawMode(term: Terminal) = - discard tcGetAttr(term.istream.fd, addr term.orig_termios) - var raw = term.orig_termios + discard tcGetAttr(term.istream.fd, addr term.origTermios) + var raw = term.origTermios 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 @@ -913,7 +925,7 @@ proc quit*(term: Terminal) = else: term.write(term.cursorGoto(0, term.attrs.height - 1) & term.resetFormat() & "\n") - if term.set_title: + if term.setTitle: term.write(XTPOPTITLE) term.showCursor() term.clearCanvas() @@ -922,20 +934,23 @@ proc quit*(term: Terminal) = term.stdinWasUnblocked = true term.flush() -when termcap_found: +when TermcapFound: 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: + var tname = getEnv("TERM") + if tname == "": + tname = "dosansi" + let tc = Termcap() + var res = tgetent(cast[cstring](addr tc.bp), cstring(tname)) + if res == 0: # retry as dosansi + res = tgetent(cast[cstring](addr tc.bp), "dosansi") + if res > 0: # success + assert goutfile == nil + goutfile = term.outfile term.tc = tc for id in TermcapCap: tc.caps[id] = tgetstr(cstring($id), cast[ptr cstring](addr tc.funcstr)) for id in TermcapCapNumeric: tc.numCaps[id] = tgetnum(cstring($id)) - else: - raise newException(Defect, - "Failed to load termcap description for terminal " & term.tname) type QueryAttrs = enum @@ -1152,12 +1167,9 @@ type TermStartResult* = enum # 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" + var res = tsrSuccess if not term.isatty(): - return + return res var win: IOctl_WinSize if ioctl(term.istream.fd, TIOCGWINSZ, addr win) != -1: term.attrs.width = int(win.ws_col) @@ -1203,14 +1215,14 @@ proc detectTermAttributes(term: Terminal; windowOnly: bool): TermStartResult = else: # something went horribly wrong. set result to DA1 fail, pager will # alert the user - result = tsrDA1Fail + res = tsrDA1Fail if windowOnly: - return + return res if term.colorMode != cmTrueColor: let colorterm = getEnv("COLORTERM") if colorterm in ["24bit", "truecolor"]: term.colorMode = cmTrueColor - when termcap_found: + when TermcapFound: term.loadTermcap() if term.tc != nil: term.smcup = term.hascap ti @@ -1229,9 +1241,10 @@ proc detectTermAttributes(term: Terminal; windowOnly: bool): TermStartResult = term.formatMode.incl(ffReverse) if term.hascap mb: term.formatMode.incl(ffBlink) - else: - term.smcup = true - term.formatMode = {FormatFlag.low..FormatFlag.high} + return res + term.smcup = true + term.formatMode = {FormatFlag.low..FormatFlag.high} + return res type MouseInputType* = enum @@ -1319,7 +1332,7 @@ proc windowChange*(term: Terminal) = proc initScreen(term: Terminal) = # note: deinit happens in quit() - if term.set_title: + if term.setTitle: term.write(XTPUSHTITLE) if term.smcup: term.write(term.enableAltScreen()) diff --git a/src/main.nim b/src/main.nim index 64c91dee..c53950a3 100644 --- a/src/main.nim +++ b/src/main.nim @@ -4,6 +4,7 @@ import std/options import std/os import std/posix +import bindings/termcap import chagashi/charset import config/chapath import config/config @@ -29,6 +30,11 @@ const ChaVersionStr = block: s &= "not sandboxed" else: s &= "sandboxed" + s &= ", " + when TermcapFound: + s &= "has termcap" + else: + s &= "no termcap" s & ")\n" const ChaVersionStrLong = block: @@ -42,6 +48,11 @@ const ChaVersionStrLong = block: s &= "not sandboxed" else: s &= "sandboxed by " & $SandboxMode + s &= ", " + when TermcapFound: + s &= "termcap library " & Termlib + else: + s &= "no termcap" s & ")\n" proc help(i: int) = |