Skip to content

Instantly share code, notes, and snippets.

@aaronedev
Forked from roycrippen4/inspector.lua
Last active November 5, 2025 05:51
Show Gist options
  • Select an option

  • Save aaronedev/ef03f3460052ebd885bc08d5cc6bf190 to your computer and use it in GitHub Desktop.

Select an option

Save aaronedev/ef03f3460052ebd885bc08d5cc6bf190 to your computer and use it in GitHub Desktop.
Inspect nvim highlight groups under mouse cursor in a floating window
local M = {}
-- Cache for module state
local state = {
buf = nil,
win = nil,
ns = nil,
width = 34,
uses_mousemoveevent = vim.o.mousemoveevent,
}
-- Initialize the module
local function init()
if not state.buf then
state.buf = vim.api.nvim_create_buf(false, true)
vim.bo[state.buf].filetype = "inspector"
vim.bo[state.buf].bufhidden = "hide"
end
if not state.ns then
state.ns = vim.api.nvim_create_namespace("inspect_word")
end
end
-- Format the inspection info into displayable lines
local function format_inspect_info(inspect_info)
local lines = {}
local highlights = {}
-- Helper to add a line with optional highlighting
local function add_line(text, hl_group)
table.insert(lines, text)
if hl_group then
table.insert(highlights, { line = #lines - 1, hl_group = hl_group })
end
end
-- Treesitter highlights
if inspect_info.treesitter and #inspect_info.treesitter > 0 then
add_line("Treesitter", "@function.call.lua")
for _, entry in ipairs(inspect_info.treesitter) do
local text = string.format(
" • %s → %s [%s]",
entry.hl_group or "none",
entry.hl_group_link or "none",
entry.lang or "?"
)
add_line(text)
end
add_line("")
end
-- Semantic tokens
if inspect_info.semantic_tokens and #inspect_info.semantic_tokens > 0 then
add_line("Semantic Tokens", "@function.lua")
for _, entry in ipairs(inspect_info.semantic_tokens) do
local text = string.format(
" • %s → %s (p:%d)",
entry.opts.hl_group or "none",
entry.opts.hl_group_link or "none",
entry.opts.priority or 0
)
add_line(text)
end
add_line("")
end
-- Extmarks
if inspect_info.extmarks and #inspect_info.extmarks > 0 then
add_line("Extmarks", "@function.lua")
for _, entry in ipairs(inspect_info.extmarks) do
local text = " • " .. (entry.opts.hl_group or "none")
if entry.ns and #entry.ns > 0 then
text = text .. " [" .. entry.ns .. "]"
end
add_line(text)
end
add_line("")
end
-- If no info found
if #lines == 0 then
add_line("No highlight information found")
end
return lines, highlights
end
-- Apply highlights to the buffer
local function apply_highlights(highlights)
vim.api.nvim_buf_clear_namespace(state.buf, state.ns, 0, -1)
for _, hl in ipairs(highlights) do
vim.api.nvim_buf_add_highlight(state.buf, state.ns, hl.hl_group, hl.line, 0, -1)
end
end
-- Inspect at current position in a split
function M.inspect_in_split()
init()
local pos = vim.api.nvim_win_get_cursor(0)
local inspect_info = vim.inspect_pos(0, pos[1] - 1, pos[2])
local lines, highlights = format_inspect_info(inspect_info)
-- Set buffer content
vim.api.nvim_buf_set_lines(state.buf, 0, -1, false, lines)
apply_highlights(highlights)
-- Open in split
vim.cmd("botright split")
vim.cmd("resize " .. math.min(#lines + 2, 15))
vim.api.nvim_win_set_buf(0, state.buf)
-- Set window options
vim.wo.number = false
vim.wo.relativenumber = false
vim.wo.signcolumn = "no"
vim.wo.foldcolumn = "0"
vim.wo.statusline = " Inspector (q to close)"
-- Add quit mapping for this buffer
vim.api.nvim_buf_set_keymap(state.buf, "n", "q", "<cmd>close<cr>", {
noremap = true,
silent = true,
nowait = true,
})
end
-- Toggle float inspector with mouse tracking
function M.toggle_float_inspector()
init()
-- If window exists and is valid, close it
if state.win and vim.api.nvim_win_is_valid(state.win) then
vim.api.nvim_win_close(state.win, true)
state.win = nil
vim.o.mousemoveevent = state.uses_mousemoveevent
return
end
-- Enable mouse move events
vim.o.mousemoveevent = true
-- Create floating window
state.win = vim.api.nvim_open_win(state.buf, false, {
relative = "cursor",
width = 40,
height = 1,
row = 1,
col = 1,
style = "minimal",
focusable = false,
border = "rounded",
zindex = 1000,
})
-- Initial content
vim.api.nvim_buf_set_lines(state.buf, 0, -1, false, { " Move mouse to inspect... " })
-- Update content on first call
vim.schedule(function()
M.update_float()
end)
end
-- Update float window content based on mouse position
function M.update_float()
if not state.win or not vim.api.nvim_win_is_valid(state.win) then
return "<MouseMove>"
end
vim.schedule(function()
local pos = vim.fn.getmousepos()
-- Skip if position is invalid
if not pos.winid or pos.winid == 0 then
return
end
-- Get buffer from mouse position window
local buf = vim.api.nvim_win_get_buf(pos.winid)
local inspect_info = vim.inspect_pos(buf, pos.line - 1, pos.column - 1)
local lines, highlights = format_inspect_info(inspect_info)
-- Update buffer content
vim.api.nvim_buf_set_lines(state.buf, 0, -1, false, lines)
apply_highlights(highlights)
-- Calculate window size
local width = 0
for _, line in ipairs(lines) do
width = math.max(width, #line)
end
width = math.min(width + 2, 60)
local height = math.min(#lines, 20)
-- Update window configuration
local screen_height = vim.o.lines
local screen_width = vim.o.columns
local row = pos.screenrow
local col = pos.screencol
-- Adjust position to keep window on screen
if row + height + 2 > screen_height then
row = math.max(1, row - height - 2)
end
if col + width > screen_width then
col = math.max(1, screen_width - width)
end
vim.api.nvim_win_set_config(state.win, {
relative = "editor",
row = row,
col = col,
width = width,
height = height,
})
end)
return "<MouseMove>"
end
-- Set up mouse move autocmd
function M.setup_mouse_tracking()
vim.keymap.set({ "", "i" }, "<MouseMove>", M.update_float, { expr = true, silent = true })
-- Clean up when window is closed
vim.api.nvim_create_autocmd("WinClosed", {
pattern = "*",
callback = function(event)
if state.win and tonumber(event.match) == state.win then
state.win = nil
vim.o.mousemoveevent = state.uses_mousemoveevent
end
end,
})
end
-- Inspect under cursor (simple version)
function M.inspect_under_cursor()
local pos = vim.api.nvim_win_get_cursor(0)
local inspect_info = vim.inspect_pos(0, pos[1] - 1, pos[2])
-- Quick display in echo area
local items = {}
if inspect_info.treesitter and #inspect_info.treesitter > 0 then
for _, entry in ipairs(inspect_info.treesitter) do
table.insert(items, string.format("TS: %s", entry.hl_group or "none"))
end
end
if inspect_info.semantic_tokens and #inspect_info.semantic_tokens > 0 then
for _, entry in ipairs(inspect_info.semantic_tokens) do
table.insert(items, string.format("LSP: %s", entry.opts.hl_group or "none"))
end
end
if #items > 0 then
vim.notify(table.concat(items, " | "), vim.log.levels.INFO)
else
vim.notify("No highlight groups found", vim.log.levels.WARN)
end
end
return M
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment