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()