From 30924f12cf8cc88deb657983ddcbc6161909c5c6 Mon Sep 17 00:00:00 2001 From: Squibid Date: Sat, 25 Oct 2025 00:57:59 -0400 Subject: [PATCH] update and enable my todo comment highlighter --- lua/conf/autos.lua | 13 ++- lua/core/color.lua | 130 --------------------------- lua/core/init.lua | 1 + lua/core/todo.lua | 218 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 225 insertions(+), 137 deletions(-) create mode 100644 lua/core/todo.lua diff --git a/lua/conf/autos.lua b/lua/conf/autos.lua index db20920..c501dca 100644 --- a/lua/conf/autos.lua +++ b/lua/conf/autos.lua @@ -30,12 +30,11 @@ auto("BufWritePre", { end }) --- FIXME: disable for now until I can do more work and make it work better --- auto({ "BufEnter", "CursorMoved", "CursorMovedI" }, { --- group = bufcheck, --- callback = function() --- core.color.todo_comments() --- end --- }) +auto({ "BufEnter", "CursorMoved", "CursorMovedI" }, { + group = bufcheck, + callback = function() + core.todo.todo_comments() + end +}) core.color.setup_termbg_sync() diff --git a/lua/core/color.lua b/lua/core/color.lua index e481d2e..08fe763 100644 --- a/lua/core/color.lua +++ b/lua/core/color.lua @@ -29,136 +29,6 @@ function M.copyhl(hlgroup, namespace) return res end -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") - ---- highlight todo comments in the current buffer. ---- No I won't use folke's super bloated plugin. ---- ---- TODO: make this work with coniguious comment blocks like this one. ---- currently this line won't be highlighted, but I'd like it to be ---- ---- TEST: We could make this a plugin called ts-todo-hl or smthn like that, but ---- I'd be willing to bet no one would use it cause everyone loves folke too ---- much -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 - - -- 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 - - 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) - -- TODO: instead of doing everything relative to the node we at this - -- point should obtain the node start and use a for loop to iterate - -- over the lines until we're no longer in a comment. This should - -- make dealing with comment blocks easier, and working with these - -- multiline comments easier. - for _, type in pairs(todo_comments_conf) do - for _, v in ipairs(type) do - local 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 - 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 - - ok, err = pcall(vim.api.nvim_buf_set_extmark, bufnr, todo_hl_ns, s_row, e, { - hl_mode = "replace", - hl_group = type.hlgroup, - end_col = e_col, - end_row = e_row - }) - if not ok then - print("fg", s_row, e, e_col, e_row) - print("fg", err) - end - - ok, err = pcall(vim.api.nvim_buf_set_extmark, bufnr, todo_hl_ns, s_row, s - 2, { - hl_mode = "replace", - hl_group = type.hlgroup.."BG", - end_col = e - 1, - end_row = s_row - }) - if not ok then - print("bg", s_row, s - 2, e - 1, s_row) - print("bg", err) - end - - ok, err = pcall(vim.api.nvim_buf_set_extmark, bufnr, todo_hl_ns, s_row, e - 1, { - hl_mode = "replace", - hl_group = type.hlgroup.."SIGN", - end_col = e, - end_row = s_row - }) - if not ok then - print("sn", s_row, e - 1, e, s_row) - print("sn", err) - end - end - end - end - ::continue:: - end - end - end - end) -end - -- Source: 'runtime/lua/vim/_defaults.lua' in Neovim source local function parse_osc11(x) local r, g, b = x:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$') diff --git a/lua/core/init.lua b/lua/core/init.lua index 2f47744..9907285 100644 --- a/lua/core/init.lua +++ b/lua/core/init.lua @@ -31,6 +31,7 @@ local M = { misc = require("core.misc"), lsp = require("core.lsp"), color = require("core.color"), + todo = require("core.todo"), snippets = vim.fs.joinpath(vim.fn.stdpath("config"), "lua/core/snippets.lua"), } diff --git a/lua/core/todo.lua b/lua/core/todo.lua new file mode 100644 index 0000000..a5a44f4 --- /dev/null +++ b/lua/core/todo.lua @@ -0,0 +1,218 @@ +---@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