neovim

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

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