287 lines
8.5 KiB
Lua
287 lines
8.5 KiB
Lua
local M = {}
|
|
|
|
function M.safe_require(name)
|
|
local ok, mod = pcall(require, name)
|
|
if ok then return mod end
|
|
return nil
|
|
end
|
|
|
|
-- Number of parent directories to show in full in the tabline
|
|
-- The rest will be shortened according to tabline_shorten_length
|
|
-- Example: with full_parents = 1, /path/to/my/project/src/file.txt becomes pat/to/my/project/src/file.txt
|
|
-- Example: with full_parents = 2, it becomes pat/to/my/project/src/file.txt
|
|
M.tabline_full_parents = 1
|
|
|
|
-- Number of characters to show for shortened directory names in the tabline
|
|
-- Example: with shorten_length = 3, /path/to/my becomes pat/to/my
|
|
-- Example: with shorten_length = 1, /path/to/my becomes p/t/m
|
|
M.tabline_shorten_length = 3
|
|
|
|
-- Get all modified, deleted, and untracked Git files with status
|
|
-- Returns a list suitable for setqflist() or nil on error
|
|
function M.git_changed_files()
|
|
-- Use git status --porcelain to get all changes with status indicators
|
|
-- Format: "XY filename" where X=index status, Y=worktree status
|
|
-- Status codes: M=modified, D=deleted, A=added, ??=untracked, etc.
|
|
local handle = io.popen('git status --porcelain 2>/dev/null')
|
|
if not handle then
|
|
vim.notify('Failed to run git status', vim.log.levels.ERROR)
|
|
return nil
|
|
end
|
|
|
|
local result = handle:read('*a')
|
|
handle:close()
|
|
|
|
if result == '' then
|
|
vim.notify('No git changes found', vim.log.levels.INFO)
|
|
return nil
|
|
end
|
|
|
|
local qf_list = {}
|
|
local status_map = {
|
|
['M'] = 'Modified',
|
|
['A'] = 'Added',
|
|
['D'] = 'Deleted',
|
|
['R'] = 'Renamed',
|
|
['C'] = 'Copied',
|
|
['U'] = 'Unmerged',
|
|
['?'] = 'Untracked',
|
|
}
|
|
|
|
for line in result:gmatch('[^\n]+') do
|
|
-- Parse porcelain format: "XY filename" or "XY original -> renamed"
|
|
local index_status = line:sub(1, 1)
|
|
local work_status = line:sub(2, 2)
|
|
local filename = line:sub(4) -- Skip "XY " prefix
|
|
|
|
-- Determine status text (worktree takes precedence over index)
|
|
local status_code = work_status ~= ' ' and work_status or index_status
|
|
local status_text = status_map[status_code] or 'Changed'
|
|
|
|
table.insert(qf_list, {
|
|
filename = filename,
|
|
lnum = 1,
|
|
text = status_text,
|
|
})
|
|
end
|
|
|
|
return qf_list
|
|
end
|
|
|
|
-- Show highlight group and color information under cursor
|
|
-- Returns nothing, displays results in a floating window
|
|
function M.show_highlight_info()
|
|
local cursor_pos = vim.api.nvim_win_get_cursor(0)
|
|
local row, col = cursor_pos[1] - 1, cursor_pos[2]
|
|
|
|
-- Get all highlight groups at cursor position
|
|
local ts_hl = vim.treesitter.get_captures_at_pos(0, row, col)
|
|
local synID = vim.fn.synID(row + 1, col + 1, 1)
|
|
local synName = vim.fn.synIDattr(synID, 'name')
|
|
local synTrans = vim.fn.synIDattr(vim.fn.synIDtrans(synID), 'name')
|
|
|
|
-- Helper to resolve highlight links
|
|
local function resolve_hl(name)
|
|
local hl = vim.api.nvim_get_hl(0, { name = name })
|
|
local max_depth = 10
|
|
local depth = 0
|
|
while hl.link and depth < max_depth do
|
|
name = hl.link
|
|
hl = vim.api.nvim_get_hl(0, { name = name })
|
|
depth = depth + 1
|
|
end
|
|
return hl, name
|
|
end
|
|
|
|
local lines = {
|
|
'=== Highlight Info Under Cursor ===',
|
|
'',
|
|
'Position: row=' .. row .. ' col=' .. col,
|
|
'',
|
|
}
|
|
|
|
-- TreeSitter captures
|
|
if #ts_hl > 0 then
|
|
table.insert(lines, 'TreeSitter Captures:')
|
|
for _, capture in ipairs(ts_hl) do
|
|
local cap_name = '@' .. capture.capture
|
|
local hl, resolved_name = resolve_hl(cap_name)
|
|
|
|
table.insert(lines, string.format(' %s', cap_name))
|
|
if resolved_name ~= cap_name then
|
|
table.insert(lines, string.format(' → resolves to: %s', resolved_name))
|
|
end
|
|
if hl.fg then
|
|
table.insert(lines, string.format(' fg: #%06x', hl.fg))
|
|
end
|
|
if hl.bg then
|
|
table.insert(lines, string.format(' bg: #%06x', hl.bg))
|
|
end
|
|
|
|
local styles = {}
|
|
if hl.bold then table.insert(styles, 'bold') end
|
|
if hl.italic then table.insert(styles, 'italic') end
|
|
if hl.underline then table.insert(styles, 'underline') end
|
|
if #styles > 0 then
|
|
table.insert(lines, ' style: ' .. table.concat(styles, ', '))
|
|
end
|
|
end
|
|
table.insert(lines, '')
|
|
end
|
|
|
|
-- Syntax group
|
|
if synName ~= '' then
|
|
table.insert(lines, 'Syntax Group: ' .. synName)
|
|
if synTrans ~= synName and synTrans ~= '' then
|
|
table.insert(lines, 'Translates to: ' .. synTrans)
|
|
end
|
|
|
|
local hl, resolved_name = resolve_hl(synTrans ~= '' and synTrans or synName)
|
|
if hl.fg then
|
|
table.insert(lines, string.format(' fg: #%06x', hl.fg))
|
|
end
|
|
if hl.bg then
|
|
table.insert(lines, string.format(' bg: #%06x', hl.bg))
|
|
end
|
|
table.insert(lines, '')
|
|
end
|
|
|
|
-- Final applied highlight (use TreeSitter if available, otherwise syntax)
|
|
local final_hl_name = nil
|
|
if #ts_hl > 0 then
|
|
final_hl_name = '@' .. ts_hl[1].capture
|
|
elseif synTrans ~= '' then
|
|
final_hl_name = synTrans
|
|
elseif synName ~= '' then
|
|
final_hl_name = synName
|
|
end
|
|
|
|
if final_hl_name then
|
|
local final_hl, final_resolved = resolve_hl(final_hl_name)
|
|
table.insert(lines, 'Applied Highlight: ' .. final_resolved)
|
|
if final_hl.fg then
|
|
table.insert(lines, string.format(' fg: #%06x', final_hl.fg))
|
|
else
|
|
table.insert(lines, ' fg: NONE')
|
|
end
|
|
if final_hl.bg then
|
|
table.insert(lines, string.format(' bg: #%06x', final_hl.bg))
|
|
else
|
|
table.insert(lines, ' bg: NONE')
|
|
end
|
|
|
|
local styles = {}
|
|
if final_hl.bold then table.insert(styles, 'bold') end
|
|
if final_hl.italic then table.insert(styles, 'italic') end
|
|
if final_hl.underline then table.insert(styles, 'underline') end
|
|
if final_hl.undercurl then table.insert(styles, 'undercurl') end
|
|
if #styles > 0 then
|
|
table.insert(lines, ' style: ' .. table.concat(styles, ', '))
|
|
end
|
|
end
|
|
|
|
-- Show in a floating window
|
|
local buf = vim.api.nvim_create_buf(false, true)
|
|
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
|
|
|
local width = 0
|
|
for _, line in ipairs(lines) do
|
|
width = math.max(width, #line)
|
|
end
|
|
width = math.min(width + 2, vim.o.columns - 4)
|
|
|
|
local height = #lines
|
|
local opts = {
|
|
relative = 'cursor',
|
|
width = width,
|
|
height = height,
|
|
row = 1,
|
|
col = 0,
|
|
style = 'minimal',
|
|
border = 'rounded',
|
|
}
|
|
|
|
vim.api.nvim_open_win(buf, false, opts)
|
|
end
|
|
|
|
-- Custom tabline function
|
|
-- Shows configurable number of full parent directories, shortens the rest
|
|
function M.custom_tabline()
|
|
local tabline = ''
|
|
local num_tabs = vim.fn.tabpagenr('$')
|
|
|
|
for i = 1, num_tabs do
|
|
local buflist = vim.fn.tabpagebuflist(i)
|
|
local winnr = vim.fn.tabpagewinnr(i)
|
|
local bufnr = buflist[winnr]
|
|
local bufname = vim.fn.bufname(bufnr)
|
|
local bufmodified = vim.fn.getbufvar(bufnr, "&modified")
|
|
|
|
-- Highlight for the tab
|
|
if i == vim.fn.tabpagenr() then
|
|
tabline = tabline .. '%#TabLineSel#'
|
|
else
|
|
tabline = tabline .. '%#TabLine#'
|
|
end
|
|
|
|
-- Tab number
|
|
tabline = tabline .. ' ' .. i .. ' '
|
|
|
|
-- Format the filename with smart path shortening
|
|
local filename
|
|
if bufname == '' then
|
|
filename = '[No Name]'
|
|
else
|
|
-- Get the full path relative to cwd if possible
|
|
local path = vim.fn.fnamemodify(bufname, ':~:.')
|
|
|
|
-- Split path into components
|
|
local parts = vim.split(path, '/', { plain = true })
|
|
|
|
if #parts > M.tabline_full_parents + 1 then
|
|
-- We have enough parts to do smart shortening
|
|
local result = {}
|
|
|
|
-- Shorten the leading directories (all but the last full_parents + filename)
|
|
local num_to_shorten = #parts - M.tabline_full_parents - 1
|
|
for j = 1, num_to_shorten do
|
|
table.insert(result, parts[j]:sub(1, M.tabline_shorten_length))
|
|
end
|
|
|
|
-- Add the full parent directories
|
|
for j = num_to_shorten + 1, #parts - 1 do
|
|
table.insert(result, parts[j])
|
|
end
|
|
|
|
-- Add the filename
|
|
table.insert(result, parts[#parts])
|
|
|
|
filename = table.concat(result, '/')
|
|
else
|
|
-- Path is short enough, just use it as-is
|
|
filename = path
|
|
end
|
|
end
|
|
|
|
-- Add modified flag
|
|
if bufmodified == 1 then
|
|
filename = filename .. ' [+]'
|
|
end
|
|
|
|
tabline = tabline .. filename .. ' '
|
|
end
|
|
|
|
-- Fill the rest with TabLineFill
|
|
tabline = tabline .. '%#TabLineFill#%T'
|
|
|
|
-- Right-align: show tab page count if more than one tab
|
|
if num_tabs > 1 then
|
|
tabline = tabline .. '%=%#TabLine# ' .. num_tabs .. ' tabs '
|
|
end
|
|
|
|
return tabline
|
|
end
|
|
|
|
return M
|
|
|