Advertises support for truecolor and styled underlines. The truecolor properties are used by at least tmux and neovim.
470 lines
12 KiB
Python
470 lines
12 KiB
Python
#!/usr/bin/env python
|
|
# vim:fileencoding=utf-8
|
|
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
|
|
|
import re
|
|
from binascii import unhexlify, hexlify
|
|
|
|
|
|
def safe_print(*a, **k):
|
|
try:
|
|
print(*a, **k)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
names = 'xterm-kitty', 'KovIdTTY'
|
|
|
|
termcap_aliases = {
|
|
'TN': 'name'
|
|
}
|
|
|
|
bool_capabilities = {
|
|
# auto_right_margin (terminal has automatic margins)
|
|
'am',
|
|
# can_change (terminal can redefine existing colors)
|
|
'ccc',
|
|
# has_meta key (i.e. sets the eight bit)
|
|
'km',
|
|
# prtr_silent (printer will not echo on screen)
|
|
'mc5i',
|
|
# move_insert_mode (safe to move while in insert mode)
|
|
'mir',
|
|
# move_standout_mode (safe to move while in standout mode)
|
|
'msgr',
|
|
# no_pad_char (pad character does not exist)
|
|
'npc',
|
|
# eat_newline_glitch (newline ignored after 80 columns)
|
|
'xenl',
|
|
# has extra status line (window title)
|
|
'hs',
|
|
# Terminfo extension used by tmux to detect true color support (non-standard)
|
|
'Tc',
|
|
# Indicates support for styled and colored underlines (non-standard) as
|
|
# described at:
|
|
# https://github.com/kovidgoyal/kitty/blob/master/protocol-extensions.asciidoc
|
|
'Su',
|
|
|
|
# The following are entries that we dont use
|
|
# # background color erase
|
|
# 'bce',
|
|
}
|
|
|
|
termcap_aliases.update({
|
|
'am': 'am',
|
|
'cc': 'ccc',
|
|
'km': 'km',
|
|
'5i': 'mc5i',
|
|
'mi': 'mir',
|
|
'ms': 'msgr',
|
|
'NP': 'npc',
|
|
'xn': 'xenl',
|
|
'hs': 'hs',
|
|
})
|
|
|
|
numeric_capabilities = {
|
|
# maximum number of colors on screen
|
|
'colors': 256,
|
|
'cols': 80,
|
|
'lines': 24,
|
|
# tabs initially every # spaces
|
|
'it': 8,
|
|
# maximum number of color-pairs on the screen
|
|
'pairs': 32767,
|
|
}
|
|
|
|
termcap_aliases.update({
|
|
'Co': 'colors',
|
|
'pa': 'pairs',
|
|
'li': 'lines',
|
|
'co': 'cols',
|
|
'it': 'it',
|
|
})
|
|
|
|
string_capabilities = {
|
|
# graphics charset pairs
|
|
'acsc': r'++\,\,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~',
|
|
# The audible bell character
|
|
'bel': r'^G',
|
|
# Escape code for bold
|
|
'bold': r'\E[1m',
|
|
# Back tab
|
|
'cbt': r'\E[Z',
|
|
'kcbt': r'\E[Z',
|
|
# Make cursor invisible
|
|
'civis': r'\E[?25l',
|
|
# Clear screen
|
|
'clear': r'\E[H\E[2J',
|
|
# Make cursor appear normal
|
|
'cnorm': r'\E[?12l\E[?25h',
|
|
# Carriage return
|
|
'cr': r'^M', # CR (carriage return \r)
|
|
# Change scroll region
|
|
'csr': r'\E[%i%p1%d;%p2%dr',
|
|
# Move cursor to the left by the specified amount
|
|
'cub': r'\E[%p1%dD',
|
|
'cub1': r'^H', # BS (backspace)
|
|
# Move cursor down specified number of lines
|
|
'cud': r'\E[%p1%dB',
|
|
'cud1': r'^J', # LF (line-feed \n)
|
|
# Move cursor to the right by the specified amount
|
|
'cuf': r'\E[%p1%dC',
|
|
'cuf1': r'\E[C',
|
|
# Move cursor up specified number of lines
|
|
'cuu': r'\E[%p1%dA',
|
|
'cuu1': r'\E[A',
|
|
# Move cursor to specified location
|
|
'cup': r'\E[%i%p1%d;%p2%dH',
|
|
# Make cursor very visible
|
|
'cvvis': r'\E[?12;25h',
|
|
# Delete the specified number of characters
|
|
'dch': r'\E[%p1%dP',
|
|
'dch1': r'\E[P',
|
|
# Turn on half bright mode
|
|
'dim': r'\E[2m',
|
|
# Delete the specified number of lines
|
|
'dl': r'\E[%p1%dM',
|
|
'dl1': r'\E[M',
|
|
# Erase specified number of characters
|
|
'ech': r'\E[%p1%dX',
|
|
# Clear to end of screen
|
|
'ed': r'\E[J',
|
|
# Clear to end of line
|
|
'el': r'\E[K',
|
|
# Clear to start of line
|
|
'el1': r'\E[1K',
|
|
# visible bell
|
|
'flash': r'\E[?5h$<100/>\E[?5l',
|
|
# Home cursor
|
|
'home': r'\E[H',
|
|
# Move cursor to column
|
|
'hpa': r'\E[%i%p1%dG',
|
|
# Move to next tab
|
|
'ht': r'^I',
|
|
# Set tabstop at current position
|
|
'hts': r'\EH',
|
|
# Insert specified number of characters
|
|
'ich': r'\E[%p1%d@',
|
|
# Insert specified number of lines
|
|
'il': r'\E[%p1%dL',
|
|
'il1': r'\E[L',
|
|
# scroll up by specified amount
|
|
'ind': r'^J',
|
|
'indn': r'\E[%p1%dS',
|
|
# initialize color (set dynamic colors)
|
|
'initc': r'\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\',
|
|
# Set all colors to original values
|
|
'oc': r'\E]104\007',
|
|
# turn on blank mode (characters invisible)
|
|
# 'invis': r'\E[8m',
|
|
# Backspace
|
|
'kbs': r'\177',
|
|
# Left
|
|
'kcub1': r'\EOD',
|
|
# Down
|
|
'kcud1': r'\EOB',
|
|
# Right
|
|
'kcuf1': r'\EOC',
|
|
# Up
|
|
'kcuu1': r'\EOA',
|
|
# Function keys
|
|
'kf1': r'\EOP',
|
|
'kf2': r'\EOQ',
|
|
'kf3': r'\EOR',
|
|
'kf4': r'\EOS',
|
|
'kf5': r'\E[15~',
|
|
'kf6': r'\E[17~',
|
|
'kf7': r'\E[18~',
|
|
'kf8': r'\E[19~',
|
|
'kf9': r'\E[20~',
|
|
'kf10': r'\E[21~',
|
|
'kf11': r'\E[23~',
|
|
'kf12': r'\E[24~',
|
|
# Home
|
|
'khome': r'\EOH',
|
|
# End
|
|
'kend': r'\EOF',
|
|
# Insert character key
|
|
'kich1': r'\E[2~',
|
|
# Delete character key
|
|
'kdch1': r'\E[3~',
|
|
# Mouse event has occurred
|
|
'kmous': r'\E[M',
|
|
# Page down
|
|
'knp': r'\E[6~',
|
|
# Page up
|
|
'kpp': r'\E[5~',
|
|
# Scroll backwards (reverse index)
|
|
'kri': r'\E[1;2A',
|
|
# scroll forwards (index)
|
|
'kind': r'\E[1;2B',
|
|
# Restore cursor
|
|
'rc': r'\E8',
|
|
# Reverse video
|
|
'rev': r'\E[7m',
|
|
# Scroll backwards the specified number of lines (reverse index)
|
|
'ri': r'\EM',
|
|
'rin': r'\E[%p1%dT',
|
|
# Turn off automatic margins
|
|
'rmam': r'\E[?7l',
|
|
# Exit alternate screen
|
|
'rmcup': r'\E[?1049l',
|
|
# Exit insert mode
|
|
'rmir': r'\E[4l',
|
|
# Exit application keypad mode
|
|
'rmkx': r'\E[?1l',
|
|
# Exit standout mode
|
|
'rmso': r'\E[27m',
|
|
# Exit underline mode
|
|
'rmul': r'\E[24m',
|
|
# Reset string1
|
|
'rs1': r'\Ec',
|
|
# Save cursor
|
|
'sc': r'\E7',
|
|
# Set background color
|
|
'setab': r'\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m',
|
|
# Set foreground color
|
|
'setaf': r'\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m',
|
|
# Set attributes
|
|
'sgr': r'%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m',
|
|
# Clear all attributes
|
|
'sgr0': r'\E(B\E[m',
|
|
# Reset color pair to its original value
|
|
'op': r'\E[39;49m',
|
|
# Turn on automatic margins
|
|
'smam': r'\E[?7h',
|
|
# Start alternate screen
|
|
'smcup': r'\E[?1049h',
|
|
# Enster insert mode
|
|
'smir': r'\E[4h',
|
|
# Enter application keymap mode
|
|
'smkx': r'\E[?1h',
|
|
# Enter standout mode
|
|
'smso': r'\E[7m',
|
|
# Enter underline mode
|
|
'smul': r'\E[4m',
|
|
# Clear all tab stops
|
|
'tbc': r'\E[3g',
|
|
# To status line (used to set window titles)
|
|
'tsl': r'\E]2;',
|
|
# From status line (end window title string)
|
|
'fsl': r'^G',
|
|
# Disable status line (clear window title)
|
|
'dsl': r'\E]2;\007',
|
|
# Move to specified line
|
|
'vpa': r'\E[%i%p1%dd',
|
|
# Enter italics mode
|
|
'sitm': r'\E[3m',
|
|
# Leave italics mode
|
|
'ritm': r'\E[23m',
|
|
# Select alternate charset
|
|
'smacs': r'\E(0',
|
|
'rmacs': r'\E(B',
|
|
# Shifted keys
|
|
'kRIT': r'\E[1;2C',
|
|
'kLFT': r'\E[1;2D',
|
|
'kEND': r'\E[1;2F',
|
|
'kHOM': r'\E[1;2H',
|
|
# Special keys
|
|
'khlp': r'',
|
|
'kund': r'',
|
|
'ka1': r'',
|
|
'ka3': r'',
|
|
'kc1': r'',
|
|
'kc3': r'',
|
|
# Set RGB foreground color (non-standard used by neovim)
|
|
'setrgbf': r'\E[38:2:%p1%d:%p2%d:%p3%dm',
|
|
# Set RGB background color (non-standard used by neovim)
|
|
'setrgbb': r'\E[48:2:%p1%d:%p2%d:%p3%dm',
|
|
|
|
# The following are entries that we dont use
|
|
# # display status line
|
|
# 'dsl': r'\E]2;\007',
|
|
# # return from status line
|
|
# 'fsl': r'^G',
|
|
# # turn on blank mode, (characters invisible)
|
|
# 'invis': r'\E[8m',
|
|
# # init2 string
|
|
# 'is2': r'\E[!p\E[?3;4l\E[4l\E>',
|
|
# # Enter/send key
|
|
# 'kent': r'\EOM',
|
|
# # reset2
|
|
# 'rs2': r'\E[!p\E[?3;4l\E[4l\E>',
|
|
}
|
|
|
|
termcap_aliases.update({
|
|
'ac': 'acsc',
|
|
'bl': 'bel',
|
|
'md': 'bold',
|
|
'bt': 'cbt',
|
|
'kB': 'kcbt',
|
|
'cl': 'clear',
|
|
'vi': 'civis',
|
|
'vs': 'cvvis',
|
|
've': 'cnorm',
|
|
'cr': 'cr',
|
|
'cs': 'csr',
|
|
'LE': 'cub',
|
|
'le': 'cub1',
|
|
'DO': 'cud',
|
|
'do': 'cud1',
|
|
'UP': 'cuu',
|
|
'up': 'cuu1',
|
|
'nd': 'cuf1',
|
|
'RI': 'cuf',
|
|
'cm': 'cup',
|
|
'DC': 'dch',
|
|
'dc': 'dch1',
|
|
'mh': 'dim',
|
|
'DL': 'dl',
|
|
'dl': 'dl1',
|
|
'ec': 'ech',
|
|
'cd': 'ed',
|
|
'ce': 'el',
|
|
'cb': 'el1',
|
|
'vb': 'flash',
|
|
'ho': 'home',
|
|
'ch': 'hpa',
|
|
'ta': 'ht',
|
|
'st': 'hts',
|
|
'IC': 'ich',
|
|
'AL': 'il',
|
|
'al': 'il1',
|
|
'sf': 'ind',
|
|
'SF': 'indn',
|
|
'Ic': 'initc',
|
|
'oc': 'oc',
|
|
# 'mk': 'invis',
|
|
'kb': 'kbs',
|
|
'kl': 'kcub1',
|
|
'kd': 'kcud1',
|
|
'kr': 'kcuf1',
|
|
'ku': 'kcuu1',
|
|
'k1': 'kf1',
|
|
'k2': 'kf2',
|
|
'k3': 'kf3',
|
|
'k4': 'kf4',
|
|
'k5': 'kf5',
|
|
'k6': 'kf6',
|
|
'k7': 'kf7',
|
|
'k8': 'kf8',
|
|
'k9': 'kf9',
|
|
'k;': 'kf10',
|
|
'F1': 'kf11',
|
|
'F2': 'kf12',
|
|
'kh': 'khome',
|
|
'@7': 'kend',
|
|
'kI': 'kich1',
|
|
'kD': 'kdch1',
|
|
'Km': 'kmous',
|
|
'kN': 'knp',
|
|
'kP': 'kpp',
|
|
'kR': 'kri',
|
|
'kF': 'kind',
|
|
'rc': 'rc',
|
|
'mr': 'rev',
|
|
'sr': 'ri',
|
|
'SR': 'rin',
|
|
'RA': 'rmam',
|
|
'te': 'rmcup',
|
|
'ei': 'rmir',
|
|
'se': 'rmso',
|
|
'ue': 'rmul',
|
|
'r1': 'rs1',
|
|
'sc': 'sc',
|
|
'AB': 'setab',
|
|
'AF': 'setaf',
|
|
'sa': 'sgr',
|
|
'me': 'sgr0',
|
|
'op': 'op',
|
|
'SA': 'smam',
|
|
'ti': 'smcup',
|
|
'im': 'smir',
|
|
'so': 'smso',
|
|
'us': 'smul',
|
|
'ct': 'tbc',
|
|
'cv': 'vpa',
|
|
'ZH': 'sitm',
|
|
'ZR': 'ritm',
|
|
'as': 'smacs',
|
|
'ae': 'rmacs',
|
|
'ks': 'smkx',
|
|
'ke': 'rmkx',
|
|
'#2': 'kHOM',
|
|
'#4': 'kLFT',
|
|
'*7': 'kEND',
|
|
'%i': 'kRIT',
|
|
'%1': 'khlp',
|
|
'&8': 'kund',
|
|
'K1': 'ka1',
|
|
'K3': 'ka3',
|
|
'K4': 'kc1',
|
|
'K5': 'kc3',
|
|
'ts': 'tsl',
|
|
'fs': 'fsl',
|
|
'ds': 'dsl',
|
|
|
|
|
|
# 'ut': 'bce',
|
|
# 'ds': 'dsl',
|
|
# 'fs': 'fsl',
|
|
# 'mk': 'invis',
|
|
# 'is': 'is2',
|
|
# '@8': 'kent',
|
|
# 'r2': 'rs2',
|
|
})
|
|
|
|
queryable_capabilities = numeric_capabilities.copy()
|
|
queryable_capabilities.update(string_capabilities)
|
|
extra = (bool_capabilities | numeric_capabilities.keys() | string_capabilities.keys()) - set(termcap_aliases.values())
|
|
no_termcap_for = frozenset('Su Tc setrgbf setrgbb'.split())
|
|
if extra - no_termcap_for:
|
|
raise Exception('Termcap aliases not complete, missing: {}'.format(extra))
|
|
del extra
|
|
|
|
|
|
def generate_terminfo():
|
|
# Use ./build-terminfo to update definition files
|
|
ans = ['|'.join(names)]
|
|
ans.extend(sorted(bool_capabilities))
|
|
ans.extend('{}#{}'.format(k, numeric_capabilities[k]) for k in sorted(numeric_capabilities))
|
|
ans.extend('{}={}'.format(k, string_capabilities[k]) for k in sorted(string_capabilities))
|
|
return ',\n\t'.join(ans) + ',\n'
|
|
|
|
|
|
octal_escape = re.compile(r'\\([0-7]{3})')
|
|
escape_escape = re.compile(r'\\[eE]')
|
|
|
|
|
|
def key_as_bytes(name):
|
|
ans = string_capabilities[name]
|
|
ans = octal_escape.sub(lambda m: chr(int(m.group(1), 8)), ans)
|
|
ans = escape_escape.sub('\033', ans)
|
|
return ans.encode('ascii')
|
|
|
|
|
|
def get_capabilities(query_string):
|
|
from .fast_data_types import ERROR_PREFIX
|
|
ans = []
|
|
try:
|
|
for q in query_string.split(';'):
|
|
name = qname = unhexlify(q).decode('utf-8')
|
|
if name == 'TN':
|
|
val = names[0]
|
|
else:
|
|
try:
|
|
val = queryable_capabilities[name]
|
|
except KeyError:
|
|
try:
|
|
qname = termcap_aliases[name]
|
|
val = queryable_capabilities[qname]
|
|
except Exception as e:
|
|
safe_print(ERROR_PREFIX, 'Unknown terminfo property:', name)
|
|
raise
|
|
if qname in string_capabilities and '%' not in val:
|
|
val = key_as_bytes(qname).decode('ascii')
|
|
ans.append(q + '=' + hexlify(str(val).encode('utf-8')).decode('ascii'))
|
|
return '1+r' + ';'.join(ans)
|
|
except Exception:
|
|
return '0+r' + query_string
|