neovim

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

_inspector.lua (8081B)


      1 --- @diagnostic disable:no-unknown
      2 
      3 --- @class vim._inspector.Filter
      4 --- @inlinedoc
      5 ---
      6 --- Include syntax based highlight groups.
      7 --- (default: `true`)
      8 --- @field syntax boolean
      9 ---
     10 --- Include treesitter based highlight groups.
     11 --- (default: `true`)
     12 --- @field treesitter boolean
     13 ---
     14 --- Include extmarks. When `all`, then extmarks without a `hl_group` will also be included.
     15 --- (default: true)
     16 --- @field extmarks boolean|"all"
     17 ---
     18 --- Include semantic token highlights.
     19 --- (default: true)
     20 --- @field semantic_tokens boolean
     21 local defaults = {
     22  syntax = true,
     23  treesitter = true,
     24  extmarks = true,
     25  semantic_tokens = true,
     26 }
     27 
     28 ---Get all the items at a given buffer position.
     29 ---
     30 ---Can also be pretty-printed with `:Inspect!`. [:Inspect!]()
     31 ---
     32 ---@since 11
     33 ---@param bufnr? integer defaults to the current buffer
     34 ---@param row? integer row to inspect, 0-based. Defaults to the row of the current cursor
     35 ---@param col? integer col to inspect, 0-based. Defaults to the col of the current cursor
     36 ---@param filter? vim._inspector.Filter Table with key-value pairs to filter the items
     37 ---@return {treesitter:table,syntax:table,extmarks:table,semantic_tokens:table,buffer:integer,col:integer,row:integer} (table) a table with the following key-value pairs. Items are in "traversal order":
     38 ---               - treesitter: a list of treesitter captures
     39 ---               - syntax: a list of syntax groups
     40 ---               - semantic_tokens: a list of semantic tokens
     41 ---               - extmarks: a list of extmarks
     42 ---               - buffer: the buffer used to get the items
     43 ---               - row: the row used to get the items
     44 ---               - col: the col used to get the items
     45 function vim.inspect_pos(bufnr, row, col, filter)
     46  filter = vim.tbl_deep_extend('force', defaults, filter or {})
     47 
     48  bufnr = bufnr or 0
     49  if row == nil or col == nil then
     50    -- get the row/col from the first window displaying the buffer
     51    local win = bufnr == 0 and vim.api.nvim_get_current_win() or vim.fn.bufwinid(bufnr)
     52    if win == -1 then
     53      error('row/col is required for buffers not visible in a window')
     54    end
     55    local cursor = vim.api.nvim_win_get_cursor(win)
     56    row, col = cursor[1] - 1, cursor[2]
     57  end
     58  bufnr = vim._resolve_bufnr(bufnr)
     59 
     60  local results = {
     61    treesitter = {}, --- @type table[]
     62    syntax = {}, --- @type table[]
     63    extmarks = {},
     64    semantic_tokens = {},
     65    buffer = bufnr,
     66    row = row,
     67    col = col,
     68  }
     69 
     70  -- resolve hl links
     71  local function resolve_hl(data)
     72    if data.hl_group then
     73      local hlid = vim.api.nvim_get_hl_id_by_name(data.hl_group)
     74      local name = vim.fn.synIDattr(vim.fn.synIDtrans(hlid), 'name')
     75      data.hl_group_link = name
     76    end
     77    return data
     78  end
     79 
     80  -- treesitter
     81  if filter.treesitter then
     82    for _, capture in pairs(vim.treesitter.get_captures_at_pos(bufnr, row, col)) do
     83      --- @diagnostic disable-next-line: inject-field
     84      capture.hl_group = '@' .. capture.capture .. '.' .. capture.lang
     85      results.treesitter[#results.treesitter + 1] = resolve_hl(capture)
     86    end
     87  end
     88 
     89  -- syntax
     90  if filter.syntax and vim.api.nvim_buf_is_valid(bufnr) then
     91    vim._with({ buf = bufnr }, function()
     92      for _, i1 in ipairs(vim.fn.synstack(row + 1, col + 1)) do
     93        results.syntax[#results.syntax + 1] =
     94          resolve_hl({ hl_group = vim.fn.synIDattr(i1, 'name') })
     95      end
     96    end)
     97  end
     98 
     99  -- namespace id -> name map
    100  local nsmap = {} --- @type table<integer,string>
    101  for name, id in pairs(vim.api.nvim_get_namespaces()) do
    102    nsmap[id] = name
    103  end
    104 
    105  --- Convert an extmark tuple into a table
    106  local function to_map(extmark)
    107    local opts = resolve_hl(extmark[4])
    108    return {
    109      id = extmark[1],
    110      row = extmark[2],
    111      col = extmark[3],
    112      end_row = opts.end_row or extmark[2],
    113      end_col = opts.end_col or extmark[3],
    114      opts = opts,
    115      ns_id = opts.ns_id,
    116      ns = nsmap[opts.ns_id] or '',
    117    }
    118  end
    119 
    120  --- Exclude end_col and unpaired marks from the overlapping marks, unless
    121  --- filter.extmarks == 'all' (a highlight is drawn until end_col - 1).
    122  local function exclude_end_col(extmark)
    123    return filter.extmarks == 'all' or row < extmark.end_row or col < extmark.end_col
    124  end
    125 
    126  -- All overlapping extmarks at this position:
    127  local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, -1, { row, col }, { row, col }, {
    128    details = true,
    129    overlap = true,
    130  })
    131  extmarks = vim.tbl_map(to_map, extmarks)
    132  extmarks = vim.tbl_filter(exclude_end_col, extmarks)
    133 
    134  if filter.semantic_tokens then
    135    results.semantic_tokens = vim.tbl_filter(function(extmark)
    136      return extmark.ns:find('nvim.lsp.semantic_tokens') == 1
    137    end, extmarks)
    138  end
    139 
    140  if filter.extmarks then
    141    results.extmarks = vim.tbl_filter(function(extmark)
    142      return extmark.ns:find('nvim.lsp.semantic_tokens') ~= 1
    143        and (filter.extmarks == 'all' or extmark.opts.hl_group)
    144    end, extmarks)
    145  end
    146 
    147  return results
    148 end
    149 
    150 ---Show all the items at a given buffer position.
    151 ---
    152 ---Can also be shown with `:Inspect`. [:Inspect]()
    153 ---
    154 ---Example: To bind this function to the vim-scriptease
    155 ---inspired `zS` in Normal mode:
    156 ---
    157 ---```lua
    158 ---vim.keymap.set('n', 'zS', vim.show_pos)
    159 ---```
    160 ---
    161 ---@since 11
    162 ---@param bufnr? integer defaults to the current buffer
    163 ---@param row? integer row to inspect, 0-based. Defaults to the row of the current cursor
    164 ---@param col? integer col to inspect, 0-based. Defaults to the col of the current cursor
    165 ---@param filter? vim._inspector.Filter
    166 function vim.show_pos(bufnr, row, col, filter)
    167  local items = vim.inspect_pos(bufnr, row, col, filter)
    168 
    169  local lines = { {} }
    170 
    171  local function append(str, hl)
    172    table.insert(lines[#lines], { str, hl })
    173  end
    174 
    175  local function nl()
    176    table.insert(lines, {})
    177  end
    178 
    179  local function item(data, comment)
    180    append('  - ')
    181    append(data.hl_group, data.hl_group)
    182    append(' ')
    183    if data.hl_group ~= data.hl_group_link then
    184      append('links to ', 'MoreMsg')
    185      append(data.hl_group_link, data.hl_group_link)
    186      append('   ')
    187    end
    188    if comment then
    189      append(comment, 'Comment')
    190    end
    191    nl()
    192  end
    193 
    194  -- treesitter
    195  if #items.treesitter > 0 then
    196    append('Treesitter', 'Title')
    197    nl()
    198    for _, capture in ipairs(items.treesitter) do
    199      item(
    200        capture,
    201        string.format(
    202          'priority: %d   language: %s',
    203          capture.metadata.priority
    204            or (capture.metadata[capture.id] and capture.metadata[capture.id].priority)
    205            or vim.hl.priorities.treesitter,
    206          capture.lang
    207        )
    208      )
    209    end
    210    nl()
    211  end
    212 
    213  -- semantic tokens
    214  if #items.semantic_tokens > 0 then
    215    append('Semantic Tokens', 'Title')
    216    nl()
    217    local sorted_marks = vim.fn.sort(items.semantic_tokens, function(left, right)
    218      local left_first = left.opts.priority < right.opts.priority
    219        or left.opts.priority == right.opts.priority and left.opts.hl_group < right.opts.hl_group
    220      return left_first and -1 or 1
    221    end)
    222    for _, extmark in ipairs(sorted_marks) do
    223      item(extmark.opts, 'priority: ' .. extmark.opts.priority)
    224    end
    225    nl()
    226  end
    227 
    228  -- syntax
    229  if #items.syntax > 0 then
    230    append('Syntax', 'Title')
    231    nl()
    232    for _, syn in ipairs(items.syntax) do
    233      item(syn)
    234    end
    235    nl()
    236  end
    237 
    238  -- extmarks
    239  if #items.extmarks > 0 then
    240    append('Extmarks', 'Title')
    241    nl()
    242    for _, extmark in ipairs(items.extmarks) do
    243      if extmark.opts.hl_group then
    244        item(extmark.opts, extmark.ns)
    245      else
    246        append('  - ')
    247        append(extmark.ns, 'Comment')
    248        nl()
    249      end
    250    end
    251    nl()
    252  end
    253 
    254  if #lines[#lines] == 0 then
    255    table.remove(lines)
    256  end
    257 
    258  local chunks = {}
    259  for _, line in ipairs(lines) do
    260    vim.list_extend(chunks, line)
    261    table.insert(chunks, { '\n' })
    262  end
    263  if #chunks == 0 then
    264    chunks = {
    265      {
    266        'No items found at position '
    267          .. items.row
    268          .. ','
    269          .. items.col
    270          .. ' in buffer '
    271          .. items.buffer,
    272      },
    273    }
    274  end
    275  vim.api.nvim_echo(chunks, false, { kind = 'list_cmd' })
    276 end