neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

gen_terminfo.lua (9028B)


      1 -- USAGE:
      2 --
      3 --    # Optional: Delete cache to get latest terminfo from internet.
      4 --    rm -rf /tmp/nvim_terminfo/
      5 --
      6 --    # Optional: Ensure the latest ncurses+tic is in your PATH.
      7 --    export PATH="/opt/homebrew/Cellar/ncurses/6.5/bin/":"$PATH"
      8 --
      9 --    nvim -ll src/gen/gen_terminfo.lua
     10 --
     11 -- This script does:
     12 --
     13 --   1. Download Dickey's terminfo.src
     14 --   2. Compile temporary terminfo database from terminfo.src
     15 --   3. Use database to generate src/nvim/tui/terminfo_defs.h
     16 
     17 local url = 'https://invisible-island.net/datafiles/current/terminfo.src.gz'
     18 local target_gen = 'src/nvim/tui/terminfo_builtin.h'
     19 local target_enum = 'src/nvim/tui/terminfo_enum_defs.h'
     20 
     21 local entries = {
     22  { 'ansi', 'ansi_terminfo' },
     23  { 'ghostty', 'ghostty_terminfo' }, -- Note: ncurses defs do not exactly match what ghostty ships.
     24  { 'interix', 'interix_8colour_terminfo' },
     25  { 'iterm2', 'iterm_256colour_terminfo' },
     26  { 'linux', 'linux_16colour_terminfo' },
     27  { 'putty-256color', 'putty_256colour_terminfo' },
     28  { 'rxvt-256color', 'rxvt_256colour_terminfo' },
     29  { 'screen-256color', 'screen_256colour_terminfo' },
     30  { 'st-256color', 'st_256colour_terminfo' },
     31  { 'tmux-256color', 'tmux_256colour_terminfo' },
     32  { 'vte-256color', 'vte_256colour_terminfo' },
     33  { 'xterm-256color', 'xterm_256colour_terminfo' },
     34  { 'cygwin', 'cygwin_terminfo' },
     35  { 'win32con', 'win32con_terminfo' },
     36  { 'conemu', 'conemu_terminfo' },
     37  { 'vtpcon', 'vtpcon_terminfo' },
     38 }
     39 
     40 local wanted_numbers = { 'max_colors', 'lines', 'columns' }
     41 local wanted_strings = {
     42  'carriage_return',
     43  'change_scroll_region',
     44  'clear_screen',
     45  'clr_eol',
     46  'clr_eos',
     47  'cursor_address',
     48  'cursor_down',
     49  'cursor_invisible',
     50  'cursor_left',
     51  'cursor_home',
     52  'cursor_normal',
     53  'cursor_up',
     54  'cursor_right',
     55  'delete_line',
     56  'enter_blink_mode',
     57  'enter_bold_mode',
     58  'enter_ca_mode',
     59  'enter_dim_mode',
     60  'enter_italics_mode',
     61  'enter_reverse_mode',
     62  'enter_secure_mode',
     63  'enter_standout_mode',
     64  'enter_underline_mode',
     65  'erase_chars',
     66  'exit_attribute_mode',
     67  'exit_ca_mode',
     68  'from_status_line',
     69  'insert_line',
     70  'keypad_local',
     71  'keypad_xmit',
     72  'parm_delete_line',
     73  'parm_down_cursor',
     74  'parm_insert_line',
     75  'parm_left_cursor',
     76  'parm_right_cursor',
     77  'parm_up_cursor',
     78  'set_a_background',
     79  'set_a_foreground',
     80  'set_attributes',
     81  'set_lr_margin',
     82  'to_status_line',
     83 }
     84 
     85 local wanted_strings_ext = {
     86  -- the following are our custom name for extensions, see "extmap"
     87  { 'reset_cursor_style', 'Se' },
     88  { 'set_cursor_style', 'Ss' },
     89  -- terminfo describes strikethrough modes as rmxx/smxx with respect
     90  -- to the ECMA-48 strikeout/crossed-out attributes.
     91  { 'enter_strikethrough_mode', 'smxx' },
     92  { 'set_rgb_foreground', 'setrgbf' },
     93  { 'set_rgb_background', 'setrgbb' },
     94  { 'set_cursor_color', 'Cs' },
     95  { 'reset_cursor_color', 'Cr' },
     96  { 'set_underline_style', 'Smulx' },
     97 }
     98 
     99 -- Note: these are only consumed by driver-ti via it's table of "funcs" keys.
    100 -- Second value is whether there is a "shift" variant in terminfo.
    101 local wanted_termkeys = {
    102  { 'backspace', false },
    103  { 'beg', true }, -- sometimes known as: "begin"
    104  { 'btab', false },
    105  { 'clear', false },
    106  { 'dc', true },
    107  { 'end', true },
    108  { 'find', true },
    109  { 'home', true },
    110  { 'ic', true },
    111  { 'npage', false },
    112  { 'ppage', false },
    113  { 'select', false },
    114  { 'suspend', true },
    115  { 'undo', true },
    116 }
    117 
    118 local db = '/tmp/nvim_terminfo'
    119 if vim.uv.fs_stat(db) == nil then
    120  local function sys(cmd)
    121    print(cmd)
    122    os.execute(cmd)
    123  end
    124  sys('curl -O ' .. url)
    125  sys('gunzip -f terminfo.src.gz')
    126  sys(('cat terminfo.src | tic -x -o "%s" -'):format(db))
    127  sys(('cat scripts/windows.ti | tic -x -o "%s" -'):format(db))
    128  sys('rm -f terminfo.src')
    129 else
    130  print('using cached terminfo in ' .. db)
    131 end
    132 
    133 local function enumify(str)
    134  return 'kTerm_' .. str
    135 end
    136 local function quote(str)
    137  if str == nil then
    138    return 'NULL'
    139  end
    140  -- remungle the strings to look like C strings
    141  str = string.gsub(str, '\\E', '\\033')
    142  str = string.gsub(str, '%^G', '\\a')
    143  str = string.gsub(str, '%^H', '\\b')
    144  str = string.gsub(str, '%^O', '\\017') -- o dod
    145  -- str = string.gsub(str, "\\", "\\\\")
    146  str = string.gsub(str, '"', '\\"')
    147  return '"' .. str .. '"'
    148 end
    149 
    150 local dbg = function() end
    151 -- dbg = print
    152 
    153 local f_enum = assert(io.open(target_enum, 'wb'))
    154 f_enum:write('// generated by src/gen/gen_terminfo.lua\n\n')
    155 f_enum:write('#pragma once\n\n')
    156 f_enum:write('typedef enum {\n')
    157 for _, name in ipairs(wanted_strings) do
    158  f_enum:write('  ' .. enumify(name) .. ',\n')
    159 end
    160 f_enum:write('#define kTermExtOffset ' .. enumify(wanted_strings_ext[1][1]) .. '\n')
    161 for _, item in ipairs(wanted_strings_ext) do
    162  f_enum:write('  ' .. enumify(item[1]) .. ',\n')
    163 end
    164 f_enum:write('  kTermCount,  // sentinel\n')
    165 f_enum:write('} TerminfoDef;\n\n')
    166 
    167 f_enum:write([[
    168 // TODO(bfredl): physical F-keys beyond F12 are uncommon. But terminfo
    169 // likes to represent chords with shift and/or ctrl and F keys as high
    170 // F-key numbers. The same chords can also be recognized by driver-csi.c
    171 // but will then be encoded as chords. We might actually prefer that but it is
    172 // potentially breaking change.
    173 ]])
    174 local func_key_max = 63
    175 f_enum:write('#define kTerminfoFuncKeyMax ' .. func_key_max .. '\n')
    176 f_enum:write('typedef enum {\n')
    177 for _, item in ipairs(wanted_termkeys) do
    178  f_enum:write('  kTermKey_' .. item[1] .. ',\n')
    179 end
    180 f_enum:write('  kTermKeyCount,\n')
    181 f_enum:write('} TerminfoKey;\n')
    182 f_enum:close()
    183 
    184 local f_defs = assert(io.open(target_gen, 'wb'))
    185 
    186 f_defs:write('// uncrustify:off\n\n')
    187 
    188 local version = io.popen('infocmp -V'):read '*a'
    189 f_defs:write('// Generated by src/gen/gen_terminfo.lua and ' .. version .. '\n')
    190 
    191 f_defs:write('#pragma once\n\n')
    192 f_defs:write('#include "nvim/tui/terminfo_defs.h"\n')
    193 
    194 for _, entry in ipairs(entries) do
    195  local term, target = unpack(entry)
    196  local fil = io.popen('infocmp -L -x -1 -A ' .. db .. ' ' .. term):read '*a'
    197  local lines = vim.split(fil, '\n')
    198  local prepat = '^%s*([%w_]+)'
    199  local boolpat = prepat .. ','
    200  local numpat = prepat .. '#([^,]+),'
    201  local strpat = prepat .. '=([^,]+),'
    202  local bools, nums, strs = {}, {}, {}
    203  for i, line in ipairs(lines) do
    204    local boolmatch = string.match(line, boolpat)
    205    local nummatch, numval = string.match(line, numpat)
    206    local strmatch, strval = string.match(line, strpat)
    207    if boolmatch then
    208      dbg('boolean: ' .. boolmatch)
    209      bools[boolmatch] = true
    210    elseif nummatch then
    211      dbg('number: ' .. nummatch .. ' is ' .. numval)
    212      nums[nummatch] = numval
    213    elseif strmatch then
    214      dbg('string: ' .. strmatch .. ' is ' .. strval)
    215      strs[strmatch] = strval
    216    else
    217      dbg('UNKNOWN:', i, line)
    218    end
    219  end
    220 
    221  f_defs:write('\nstatic const TerminfoEntry ' .. target .. ' = {\n')
    222  f_defs:write('  .bce = ' .. tostring(bools.back_color_erase or false) .. ',\n')
    223  local has_Tc_or_RGB = (bools.Tc or bools.RGB) or false
    224  f_defs:write('  .has_Tc_or_RGB = ' .. tostring(has_Tc_or_RGB or false) .. ',\n')
    225  f_defs:write('  .Su = ' .. tostring(bools.Su or false) .. ',\n')
    226 
    227  for _, name in ipairs(wanted_numbers) do
    228    f_defs:write('  .' .. name .. ' = ' .. (nums[name] or '-1') .. ',\n')
    229  end
    230  f_defs:write('  .defs = {\n')
    231  for _, name in ipairs(wanted_strings) do
    232    f_defs:write('    [' .. enumify(name) .. '] = ' .. quote(strs[name]) .. ',\n')
    233  end
    234  for _, item in ipairs(wanted_strings_ext) do
    235    f_defs:write('    [' .. enumify(item[1]) .. '] = ' .. quote(strs[item[2]]) .. ',\n')
    236  end
    237  f_defs:write('  },\n')
    238  f_defs:write('  .keys = {\n')
    239  for _, item in ipairs(wanted_termkeys) do
    240    local name = item[1]
    241    f_defs:write(
    242      '    [kTermKey_'
    243        .. name
    244        .. '] = {'
    245        .. quote(strs['key_' .. name])
    246        .. ', '
    247        .. quote(strs['key_s' .. name])
    248        .. '},\n'
    249    )
    250  end
    251  f_defs:write('  },\n')
    252  f_defs:write('  .f_keys = {\n')
    253  if strs['key_f1'] == nil then
    254    f_defs:write('    NULL,\n') -- compiler get sad if list is empty
    255  else
    256    f_defs:write('    // note: offset by one, f_keys[0] is F1 and so on\n')
    257  end
    258  for i = 1, func_key_max do
    259    if strs['key_f' .. i] ~= nil then
    260      f_defs:write('    [' .. i - 1 .. '] = ' .. quote(strs['key_f' .. i]) .. ',\n')
    261    end
    262  end
    263  f_defs:write('  },\n')
    264 
    265  f_defs:write('};\n')
    266 end
    267 
    268 f_defs:write('\n#define XLIST_TERMINFO_BUILTIN \\\n')
    269 for _, name in ipairs(wanted_strings) do
    270  f_defs:write('  X(' .. name .. ') \\\n')
    271 end
    272 f_defs:write('// end of list\n\n')
    273 f_defs:write('#define XLIST_TERMINFO_EXT \\\n')
    274 for _, item in ipairs(wanted_strings_ext) do
    275  f_defs:write('  X(' .. item[1] .. ', ' .. item[2] .. ') \\\n')
    276 end
    277 f_defs:write('// end of list\n\n')
    278 f_defs:write('#define XYLIST_TERMINFO_KEYS \\\n')
    279 for _, item in ipairs(wanted_termkeys) do
    280  f_defs:write('  ' .. (item[2] and 'Y' or 'X') .. '(' .. item[1] .. ') \\\n')
    281 end
    282 f_defs:write('// end of list\n\n')
    283 f_defs:write('#define XLIST_TERMINFO_FKEYS \\\n')
    284 for i = 1, func_key_max do
    285  f_defs:write('  X(f' .. i .. ') \\\n')
    286 end
    287 f_defs:write('// end of list\n')
    288 f_defs:close()