218 lines
5.4 KiB
Lua
218 lines
5.4 KiB
Lua
---@class data.buf.node
|
|
---@field size number size of the node
|
|
---@field extmark_bg number? extmark id for the bg
|
|
---@field extmark_fg number? extmark id for the fg
|
|
---@field extmark_sn number? extmark id for the sign
|
|
|
|
---@class data.buf
|
|
---@field [integer] data.buf.node a list of nodes where the index is the row in file where the node resides
|
|
|
|
---@class data
|
|
---@field [integer] data.buf buffer number
|
|
local data = {}
|
|
|
|
local M = {}
|
|
|
|
local todo_comments_conf = {
|
|
TODO = {
|
|
-- TODO:
|
|
-- NOTE:
|
|
-- INFO:
|
|
"TODO", "NOTE", "INFO",
|
|
hlgroup = "TodoTODO"
|
|
},
|
|
BUG = {
|
|
-- BUG:
|
|
-- FIXME:
|
|
"BUG", "FIXME",
|
|
hlgroup = "TodoBUG"
|
|
},
|
|
TEST = {
|
|
-- TEST:
|
|
-- PERF:
|
|
"TEST", "PERF",
|
|
hlgroup = "TodoTEST"
|
|
},
|
|
WARN = {
|
|
-- WARN:
|
|
-- HACK:
|
|
"WARN", "HACK",
|
|
hlgroup = "TodoWARN"
|
|
}
|
|
}
|
|
|
|
local todo_hl_ns = vim.api.nvim_create_namespace("todo_highlights")
|
|
|
|
--- create a new highlight or override an existing one
|
|
---@param bufnr number
|
|
---@param group string
|
|
---@param s_col number
|
|
---@param e_col number
|
|
---@param s_row number
|
|
---@param e_row number
|
|
---@param id number?
|
|
---@return number?
|
|
local function set_hl_at(bufnr, group, s_col, e_col, s_row, e_row, id)
|
|
local ok, res = pcall(
|
|
vim.api.nvim_buf_set_extmark,
|
|
bufnr,
|
|
todo_hl_ns,
|
|
s_row,
|
|
s_col,
|
|
{
|
|
hl_mode = "replace",
|
|
hl_group = group,
|
|
end_col = e_col,
|
|
end_row = e_row,
|
|
id = id,
|
|
})
|
|
|
|
if ok then
|
|
return res
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
--- I'm wayyyyyy too lazy to make this function take a real list of arguments
|
|
---@param tab table everything in the whole universe in the most backwards incompatable way
|
|
local function create_extmarks(tab)
|
|
local bufnr, s, e, node, only_continuation, t = vim.F.unpack_len(tab)
|
|
|
|
local s_row, s_col = node:start()
|
|
local e_row, e_col = node:end_()
|
|
|
|
-- ensure that our string indicies are relative to the line
|
|
s = s + s_col
|
|
e = e + s_col
|
|
|
|
if not data[bufnr][s_row] then
|
|
local _, _, bytes = node:start()
|
|
table.insert(data[bufnr], s_row, { size = bytes })
|
|
end
|
|
|
|
do
|
|
local id = data[bufnr][s_row].extmark_fg
|
|
local ext_id = set_hl_at(bufnr, t.hlgroup, only_continuation and s - 2 or e, e_col, s_row, e_row, id)
|
|
if not id then
|
|
data[bufnr][s_row].extmark_fg = ext_id
|
|
end
|
|
end
|
|
|
|
if not only_continuation then
|
|
do
|
|
local id = data[bufnr][s_row].extmark_bg
|
|
local ext_id = set_hl_at(bufnr, t.hlgroup.."BG", s - 2, e - 1, s_row, e_row, id)
|
|
if not id then
|
|
data[bufnr][s_row].extmark_bg = ext_id
|
|
end
|
|
end
|
|
|
|
do
|
|
local id = data[bufnr][s_row].extmark_sn
|
|
local ext_id = set_hl_at(bufnr, t.hlgroup.."SIGN", e - 1, e, s_row, e_row, id)
|
|
if not id then
|
|
data[bufnr][s_row].extmark_sn = ext_id
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- highlight todo comments in the current buffer.
|
|
--- No I won't use folke's super bloated plugin.
|
|
--- TODO: check if there's already an extmark at the location we're trying to
|
|
--- highlight, and if so don't add it again unless the node has changed.
|
|
---
|
|
--- ensure that we only change the extmark when the node has changed
|
|
function M.todo_comments()
|
|
local bufnr = vim.api.nvim_win_get_buf(0)
|
|
local ok, parser = pcall(vim.treesitter.get_parser, bufnr)
|
|
if not ok or not parser then
|
|
return
|
|
end
|
|
|
|
-- create an entry for the buffer in the data
|
|
if not data[bufnr] then
|
|
data[bufnr] = {}
|
|
end
|
|
|
|
-- Construct the query for comments.
|
|
-- We're using treesitter so that I don't have to use external tooling.
|
|
local ok, comment_query = pcall(vim.treesitter.query.parse,
|
|
parser:lang(),
|
|
"(comment) @comment"
|
|
)
|
|
if not ok then
|
|
return
|
|
end
|
|
|
|
parser:parse(false, function(err, trees)
|
|
if err then
|
|
return
|
|
end
|
|
|
|
-- store the previous todo comment's location information
|
|
local start_loc = nil
|
|
local ls, le, lt = nil, nil, nil
|
|
|
|
local root = trees[1]:root()
|
|
for _, match in comment_query:iter_matches(root, bufnr, 0, -1) do
|
|
for _, nodes in pairs(match) do
|
|
for _, node in ipairs(nodes) do
|
|
if not node or node:type() ~= "comment" then
|
|
goto continue
|
|
end
|
|
local text = vim.treesitter.get_node_text(node, bufnr)
|
|
|
|
-- is the node previous to this node a todo comment?
|
|
local continuation = (start_loc and start_loc + 1 == node:start())
|
|
|
|
-- the current todo comment's information
|
|
local s, e = nil, nil
|
|
local t = nil
|
|
for _, type in pairs(todo_comments_conf) do
|
|
for _, v in ipairs(type) do
|
|
s, e = string.find(text, v..":")
|
|
if not s or not e then
|
|
s, e = string.find(text, v.."%b():")
|
|
end
|
|
if s and e then
|
|
t = type
|
|
goto work;
|
|
end
|
|
end
|
|
end
|
|
::work::
|
|
|
|
-- is the node previous to this node a todo comment and is this node
|
|
-- not a todo comment?
|
|
local only_continuation = (not s or not e) and continuation
|
|
|
|
-- let's render this bad boy
|
|
if (s and e) or continuation then
|
|
if only_continuation then
|
|
s, e, t = ls, le, lt
|
|
end
|
|
start_loc = node:start()
|
|
|
|
if s and e then
|
|
ls, le, lt = s, e, t
|
|
end
|
|
|
|
create_extmarks(vim.F.pack_len(
|
|
bufnr,
|
|
s,
|
|
e,
|
|
node,
|
|
only_continuation,
|
|
t
|
|
))
|
|
end
|
|
::continue::
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
return M
|