termcap.lua (3028B)
1 local M = {} 2 3 --- Query the host terminal emulator for terminfo capabilities. 4 --- 5 --- This function sends the XTGETTCAP DCS sequence to the host terminal emulator asking the terminal 6 --- to send us its terminal capabilities. These are strings that are normally taken from a terminfo 7 --- file, however an up to date terminfo database is not always available (particularly on remote 8 --- machines), and many terminals continue to misidentify themselves or do not provide their own 9 --- terminfo file, making the terminfo database unreliable. 10 --- 11 --- Querying the terminal guarantees that we get a truthful answer, but only if the host terminal 12 --- emulator supports the XTGETTCAP sequence. 13 --- 14 --- @param caps string|table A terminal capability or list of capabilities to query 15 --- @param cb fun(cap:string, found:boolean, seq:string?) Callback function which is called for 16 --- each capability in {caps}. {found} is set to true if the capability was found or false 17 --- otherwise. {seq} is the control sequence for the capability if found, or nil for 18 --- boolean capabilities. 19 function M.query(caps, cb) 20 vim.validate('caps', caps, { 'string', 'table' }) 21 vim.validate('cb', cb, 'function') 22 23 if type(caps) ~= 'table' then 24 caps = { caps } 25 end 26 27 local pending = {} ---@type table<string, boolean> 28 for _, v in ipairs(caps) do 29 pending[v] = true 30 end 31 32 local timer = assert(vim.uv.new_timer()) 33 34 local id = vim.api.nvim_create_autocmd('TermResponse', { 35 nested = true, 36 callback = function(args) 37 local resp = args.data.sequence ---@type string 38 local k, rest = resp:match('^\027P1%+r(%x+)(.*)$') 39 if k and rest then 40 local cap = vim.text.hexdecode(k) 41 if not cap or not pending[cap] then 42 -- Received a response for a capability we didn't request. This can happen if there are 43 -- multiple concurrent XTGETTCAP requests 44 return 45 end 46 47 local seq ---@type string? 48 if rest:match('^=%x+$') then 49 seq = vim.text 50 .hexdecode(rest:sub(2)) 51 :gsub('\\E', '\027') 52 :gsub('%%p%d', '') 53 :gsub('\\(%d+)', string.char) 54 end 55 56 cb(cap, true, seq) 57 58 pending[cap] = nil 59 60 if next(pending) == nil then 61 return true 62 end 63 end 64 end, 65 }) 66 67 local encoded = {} ---@type string[] 68 for i = 1, #caps do 69 encoded[i] = vim.text.hexencode(caps[i]) 70 end 71 72 local query = string.format('\027P+q%s\027\\', table.concat(encoded, ';')) 73 74 vim.api.nvim_ui_send(query) 75 76 timer:start(1000, 0, function() 77 -- Delete the autocommand if no response was received 78 vim.schedule(function() 79 -- Suppress error if autocommand has already been deleted 80 pcall(vim.api.nvim_del_autocmd, id) 81 82 -- Call the callback for all capabilities that were not found 83 for k in pairs(pending) do 84 cb(k, false, nil) 85 end 86 end) 87 88 if not timer:is_closing() then 89 timer:close() 90 end 91 end) 92 end 93 94 return M