1

Update generated nvim config

This commit is contained in:
2024-06-02 22:16:32 +02:00
parent afa21f2fae
commit 847a34c6b9
26 changed files with 4399 additions and 1178 deletions

View File

@ -0,0 +1,193 @@
local Util = require("todo-comments.util")
--- @class TodoConfig
local M = {}
M.keywords = {}
--- @type TodoOptions
M.options = {}
M.loaded = false
M.ns = vim.api.nvim_create_namespace("todo-comments")
--- @class TodoOptions
-- TODO: add support for markdown todos
local defaults = {
signs = true, -- show icons in the signs column
sign_priority = 8, -- sign priority
-- keywords recognized as todo comments
keywords = {
FIX = {
icon = "", -- icon used for the sign, and in search results
color = "error", -- can be a hex color, or a named color (see below)
alt = { "FIXME", "BUG", "FIXIT", "ISSUE" }, -- a set of other keywords that all map to this FIX keywords
-- signs = false, -- configure signs for some keywords individually
},
TODO = { icon = "", color = "info" },
HACK = { icon = "", color = "warning" },
WARN = { icon = "", color = "warning", alt = { "WARNING", "XXX" } },
PERF = { icon = "", alt = { "OPTIM", "PERFORMANCE", "OPTIMIZE" } },
NOTE = { icon = "", color = "hint", alt = { "INFO" } },
TEST = { icon = "", color = "test", alt = { "TESTING", "PASSED", "FAILED" } },
},
gui_style = {
fg = "NONE", -- The gui style to use for the fg highlight group.
bg = "BOLD", -- The gui style to use for the bg highlight group.
},
merge_keywords = true, -- when true, custom keywords will be merged with the defaults
-- highlighting of the line containing the todo comment
-- * before: highlights before the keyword (typically comment characters)
-- * keyword: highlights of the keyword
-- * after: highlights after the keyword (todo text)
highlight = {
multiline = true, -- enable multine todo comments
multiline_pattern = "^.", -- lua pattern to match the next multiline from the start of the matched keyword
multiline_context = 10, -- extra lines that will be re-evaluated when changing a line
before = "", -- "fg" or "bg" or empty
keyword = "wide", -- "fg", "bg", "wide" or empty. (wide is the same as bg, but will also highlight surrounding characters)
after = "fg", -- "fg" or "bg" or empty
-- pattern can be a string, or a table of regexes that will be checked
pattern = [[.*<(KEYWORDS)\s*:]], -- pattern or table of patterns, used for highlightng (vim regex)
-- pattern = { [[.*<(KEYWORDS)\s*:]], [[.*\@(KEYWORDS)\s*]] }, -- pattern used for highlightng (vim regex)
comments_only = true, -- uses treesitter to match keywords in comments only
max_line_len = 400, -- ignore lines longer than this
exclude = {}, -- list of file types to exclude highlighting
throttle = 200,
},
-- list of named colors where we try to extract the guifg from the
-- list of hilight groups or use the hex color if hl not found as a fallback
colors = {
error = { "DiagnosticError", "ErrorMsg", "#DC2626" },
warning = { "DiagnosticWarn", "WarningMsg", "#FBBF24" },
info = { "DiagnosticInfo", "#2563EB" },
hint = { "DiagnosticHint", "#10B981" },
default = { "Identifier", "#7C3AED" },
test = { "Identifier", "#FF00FF" },
},
search = {
command = "rg",
args = {
"--color=never",
"--no-heading",
"--with-filename",
"--line-number",
"--column",
},
-- regex that will be used to match keywords.
-- don't replace the (KEYWORDS) placeholder
pattern = [[\b(KEYWORDS):]], -- ripgrep regex
-- pattern = [[\b(KEYWORDS)\b]], -- match without the extra colon. You'll likely get false positives
},
}
M._options = nil
function M.setup(options)
if vim.fn.has("nvim-0.8.0") == 0 then
error("todo-comments needs Neovim >= 0.8.0. Use the 'neovim-pre-0.8.0' branch for older versions")
end
M._options = options
if vim.api.nvim_get_vvar("vim_did_enter") == 0 then
vim.defer_fn(function()
M._setup()
end, 0)
else
M._setup()
end
end
function M._setup()
M.options = vim.tbl_deep_extend("force", {}, defaults, M.options or {}, M._options or {})
-- -- keywords should always be fully overriden
if M._options and M._options.keywords and M._options.merge_keywords == false then
M.options.keywords = M._options.keywords
end
for kw, opts in pairs(M.options.keywords) do
M.keywords[kw] = kw
for _, alt in pairs(opts.alt or {}) do
M.keywords[alt] = kw
end
end
local function tags(keywords)
local kws = keywords or vim.tbl_keys(M.keywords)
table.sort(kws, function(a, b)
return #b < #a
end)
return table.concat(kws, "|")
end
function M.search_regex(keywords)
return M.options.search.pattern:gsub("KEYWORDS", tags(keywords))
end
M.hl_regex = {}
local patterns = M.options.highlight.pattern
patterns = type(patterns) == "table" and patterns or { patterns }
for _, p in pairs(patterns) do
p = p:gsub("KEYWORDS", tags())
table.insert(M.hl_regex, p)
end
M.colors()
M.signs()
require("todo-comments.highlight").start()
M.loaded = true
end
function M.signs()
for kw, opts in pairs(M.options.keywords) do
vim.fn.sign_define("todo-sign-" .. kw, {
text = opts.icon,
texthl = "TodoSign" .. kw,
})
end
end
function M.colors()
local normal = Util.get_hl("Normal")
local fg_dark = Util.is_dark(normal.foreground or "#ffffff") and normal.foreground or normal.background
local fg_light = Util.is_dark(normal.foreground or "#ffffff") and normal.background or normal.foreground
fg_dark = fg_dark or "#000000"
fg_light = fg_light or "#ffffff"
local fg_gui = M.options.gui_style.fg
local bg_gui = M.options.gui_style.bg
local sign_hl = Util.get_hl("SignColumn")
local sign_bg = (sign_hl and sign_hl.background) and sign_hl.background or "NONE"
for kw, opts in pairs(M.options.keywords) do
local kw_color = opts.color or "default"
local hex
if kw_color:sub(1, 1) == "#" then
hex = kw_color
else
local colors = M.options.colors[kw_color]
colors = type(colors) == "string" and { colors } or colors
for _, color in pairs(colors) do
if color:sub(1, 1) == "#" then
hex = color
break
end
local c = Util.get_hl(color)
if c and c.foreground then
hex = c.foreground
break
end
end
end
if not hex then
error("Todo: no color for " .. kw)
end
local fg = Util.is_dark(hex) and fg_light or fg_dark
vim.cmd("hi def TodoBg" .. kw .. " guibg=" .. hex .. " guifg=" .. fg .. " gui=" .. bg_gui)
vim.cmd("hi def TodoFg" .. kw .. " guibg=NONE guifg=" .. hex .. " gui=" .. fg_gui)
vim.cmd("hi def TodoSign" .. kw .. " guibg=" .. sign_bg .. " guifg=" .. hex .. " gui=NONE")
end
end
return M

View File

@ -0,0 +1,403 @@
-- FIX: dddddd
-- TODO: foobar
-- dddddd
local Config = require("todo-comments.config")
local M = {}
M.enabled = false
M.bufs = {}
M.wins = {}
-- PERF: fully optimised
-- FIX: ddddddasdasdasdasdasda
-- PERF: dddd
-- ddddd
-- dddddd
-- ddddddd
-- FIXME: dddddd
-- FIX: ddd
-- HACK: hmmm, this looks a bit funky
-- TODO: What else?
-- NOTE: adding a note
--
-- FIX: this needs fixing
-- WARNING: ???
-- FIX: ddddd
-- continuation
-- @TODO foobar
-- @hack foobar
---@alias TodoDirty table<number, boolean>
---@type table<buffer, {dirty:TodoDirty}>
M.state = {}
function M.match(str, patterns)
local max_line_len = Config.options.highlight.max_line_len
if max_line_len and #str > max_line_len then
return
end
patterns = patterns or Config.hl_regex
if not type(patterns) == "table" then
patterns = { patterns }
end
for _, pattern in pairs(patterns) do
local m = vim.fn.matchlist(str, [[\v\C]] .. pattern)
if #m > 1 and m[2] then
local kw = m[2]
local start = str:find(kw)
return start, start + #kw, kw
end
end
end
-- This method returns nil if this buf doesn't have a treesitter parser
--- @return boolean? true or false otherwise
function M.is_comment(buf, row, col)
if vim.treesitter.highlighter.active[buf] then
local captures = vim.treesitter.get_captures_at_pos(buf, row, col)
for _, c in ipairs(captures) do
if c.capture == "comment" then
return true
end
end
else
local win = vim.fn.bufwinid(buf)
return win ~= -1
and vim.api.nvim_win_call(win, function()
for _, i1 in ipairs(vim.fn.synstack(row + 1, col)) do
local i2 = vim.fn.synIDtrans(i1)
local n1 = vim.fn.synIDattr(i1, "name")
local n2 = vim.fn.synIDattr(i2, "name")
if n1 == "Comment" or n2 == "Comment" then
return true
end
end
end)
end
end
local function add_highlight(buffer, ns, hl, line, from, to)
-- vim.api.nvim_buf_set_extmark(buffer, ns, line, from, {
-- end_line = line,
-- end_col = to,
-- hl_group = hl,
-- priority = 500,
-- })
vim.api.nvim_buf_add_highlight(buffer, ns, hl, line, from, to)
end
function M.get_state(buf)
if not M.state[buf] then
M.state[buf] = { dirty = {}, comments = {} }
end
return M.state[buf]
end
function M.redraw(buf, first, last)
first = math.max(first - Config.options.highlight.multiline_context, 0)
last = math.min(last + Config.options.highlight.multiline_context, vim.api.nvim_buf_line_count(buf))
local state = M.get_state(buf)
for i = first, last do
state.dirty[i] = true
end
if not M.timer then
M.timer = vim.defer_fn(M.update, Config.options.highlight.throttle)
end
end
---@type vim.loop.Timer
M.timer = nil
function M.update()
M.timer = nil
for buf, state in pairs(M.state) do
if vim.api.nvim_buf_is_valid(buf) then
if not vim.tbl_isempty(state.dirty) then
local dirty = vim.tbl_keys(state.dirty)
table.sort(dirty)
local i = 1
while i <= #dirty do
local first = dirty[i]
local last = dirty[i]
while dirty[i + 1] == dirty[i] + 1 do
i = i + 1
last = dirty[i]
end
M.highlight(buf, first, last)
i = i + 1
end
state.dirty = {}
end
else
M.state[buf] = nil
end
end
end
-- highlights the range for the given buf
function M.highlight(buf, first, last, _event)
if not vim.api.nvim_buf_is_valid(buf) then
return
end
vim.api.nvim_buf_clear_namespace(buf, Config.ns, first, last + 1)
-- clear signs
for _, sign in pairs(vim.fn.sign_getplaced(buf, { group = "todo-signs" })[1].signs) do
if sign.lnum - 1 >= first and sign.lnum - 1 <= last then
vim.fn.sign_unplace("todo-signs", { buffer = buf, id = sign.id })
end
end
local lines = vim.api.nvim_buf_get_lines(buf, first, last + 1, false)
---@type {kw: string, start:integer}?
local last_match
for l, line in ipairs(lines) do
local ok, start, finish, kw = pcall(M.match, line)
local lnum = first + l - 1
if ok and start then
---@cast kw string
if
Config.options.highlight.comments_only
and not M.is_quickfix(buf)
and not M.is_comment(buf, lnum, start - 1)
then
kw = nil
else
last_match = { kw = kw, start = start }
end
end
local is_multiline = false
if not kw and last_match and Config.options.highlight.multiline then
if
M.is_comment(buf, lnum, last_match.start)
and line:find(Config.options.highlight.multiline_pattern, last_match.start)
then
kw = last_match.kw
start = last_match.start
finish = start
is_multiline = true
else
last_match = nil
end
end
if kw then
kw = Config.keywords[kw] or kw
end
local opts = Config.options.keywords[kw]
if opts then
start = start - 1
finish = finish - 1
local hl_fg = "TodoFg" .. kw
local hl_bg = "TodoBg" .. kw
local hl = Config.options.highlight
if not is_multiline then
-- before highlights
if hl.before == "fg" then
add_highlight(buf, Config.ns, hl_fg, lnum, 0, start)
elseif hl.before == "bg" then
add_highlight(buf, Config.ns, hl_bg, lnum, 0, start)
end
-- tag highlights
if hl.keyword == "wide" or hl.keyword == "wide_bg" then
add_highlight(buf, Config.ns, hl_bg, lnum, math.max(start - 1, 0), finish + 1)
elseif hl.keyword == "wide_fg" then
add_highlight(buf, Config.ns, hl_fg, lnum, math.max(start - 1, 0), finish + 1)
elseif hl.keyword == "bg" then
add_highlight(buf, Config.ns, hl_bg, lnum, start, finish)
elseif hl.keyword == "fg" then
add_highlight(buf, Config.ns, hl_fg, lnum, start, finish)
end
end
-- after highlights
if hl.after == "fg" then
add_highlight(buf, Config.ns, hl_fg, lnum, finish, #line)
elseif hl.after == "bg" then
add_highlight(buf, Config.ns, hl_bg, lnum, finish, #line)
end
if not is_multiline then
-- signs
local show_sign = Config.options.signs
if opts.signs ~= nil then
show_sign = opts.signs
end
if show_sign then
vim.fn.sign_place(
0,
"todo-signs",
"todo-sign-" .. kw,
buf,
{ lnum = lnum + 1, priority = Config.options.sign_priority }
)
end
end
end
end
end
-- highlights the visible range of the window
function M.highlight_win(win, force)
win = win or vim.api.nvim_get_current_win()
if force ~= true and not M.is_valid_win(win) then
return
end
vim.api.nvim_win_call(win, function()
local buf = vim.api.nvim_win_get_buf(win)
local first = vim.fn.line("w0") - 1
local last = vim.fn.line("w$")
M.redraw(buf, first, last)
end)
end
function M.is_float(win)
local opts = vim.api.nvim_win_get_config(win)
return opts and opts.relative and opts.relative ~= ""
end
function M.is_valid_win(win)
if not vim.api.nvim_win_is_valid(win) then
return false
end
-- avoid E5108 after pressing q:
if vim.fn.getcmdwintype() ~= "" then
return false
end
-- dont do anything for floating windows
if M.is_float(win) then
return false
end
local buf = vim.api.nvim_win_get_buf(win)
return M.is_valid_buf(buf)
end
function M.is_quickfix(buf)
return vim.api.nvim_buf_get_option(buf, "buftype") == "quickfix"
end
function M.is_valid_buf(buf)
-- Skip special buffers
local buftype = vim.api.nvim_buf_get_option(buf, "buftype")
if buftype ~= "" and buftype ~= "quickfix" then
return false
end
local filetype = vim.api.nvim_buf_get_option(buf, "filetype")
if vim.tbl_contains(Config.options.highlight.exclude, filetype) then
return false
end
return true
end
-- will attach to the buf in the window and highlight the active buf if needed
function M.attach(win)
win = win or vim.api.nvim_get_current_win()
if not M.is_valid_win(win) then
return
end
local buf = vim.api.nvim_win_get_buf(win)
if not M.bufs[buf] then
vim.api.nvim_buf_attach(buf, false, {
on_lines = function(_event, _buf, _tick, first, _last, last_new)
if not M.enabled then
return true
end
-- detach from this buffer in case we no longer want it
if not M.is_valid_buf(buf) then
return true
end
M.redraw(buf, first, last_new)
end,
on_detach = function()
M.state[buf] = nil
M.bufs[buf] = nil
end,
})
local highlighter = require("vim.treesitter.highlighter")
local hl = highlighter.active[buf]
if hl then
-- also listen to TS changes so we can properly update the buffer based on is_comment
hl.tree:register_cbs({
on_bytes = function(_, _, row)
M.redraw(buf, row, row + 1)
end,
on_changedtree = function(changes)
for _, ch in ipairs(changes or {}) do
M.redraw(buf, ch[1], ch[3] + 1)
end
end,
})
end
M.bufs[buf] = true
M.highlight_win(win)
M.wins[win] = true
elseif not M.wins[win] then
M.highlight_win(win)
M.wins[win] = true
end
end
function M.stop()
M.enabled = false
pcall(vim.cmd, "autocmd! Todo")
pcall(vim.cmd, "augroup! Todo")
M.wins = {}
---@diagnostic disable-next-line: missing-parameter
vim.fn.sign_unplace("todo-signs")
for buf, _ in pairs(M.bufs) do
if vim.api.nvim_buf_is_valid(buf) then
pcall(vim.api.nvim_buf_clear_namespace, buf, Config.ns, 0, -1)
end
end
M.bufs = {}
end
function M.start()
if M.enabled then
M.stop()
end
M.enabled = true
-- setup autocmds
-- TODO: make some of the below configurable
vim.api.nvim_exec(
[[augroup Todo
autocmd!
autocmd BufWinEnter,WinNew * lua require("todo-comments.highlight").attach()
autocmd BufWritePost * silent! lua require'trouble'.refresh({auto = true, provider = "todo"})
autocmd WinScrolled * lua require("todo-comments.highlight").highlight_win()
autocmd ColorScheme * lua vim.defer_fn(require("todo-comments.config").colors, 10)
augroup end]],
false
)
-- attach to all bufs in visible windows
for _, win in pairs(vim.api.nvim_list_wins()) do
M.attach(win)
end
end
return M

View File

@ -0,0 +1,24 @@
local config = require("todo-comments.config")
local jump = require("todo-comments.jump")
local M = {}
M.setup = config.setup
function M.reset()
require("plenary.reload").reload_module("todo")
require("todo-comments").setup()
end
function M.disable()
require("todo-comments.highlight").stop()
end
function M.enable()
require("todo-comments.highlight").start()
end
M.jump_prev = jump.prev
M.jump_next = jump.next
return M

View File

@ -0,0 +1,54 @@
local highlight = require("todo-comments.highlight")
local config = require("todo-comments.config")
local util = require("todo-comments.util")
local M = {}
---@param up boolean
local function jump(up, opts)
opts = opts or {}
opts.keywords = opts.keywords or {}
local win = vim.api.nvim_get_current_win()
local buf = vim.api.nvim_get_current_buf()
local pos = vim.api.nvim_win_get_cursor(win)
local from = pos[1] + 1
local to = vim.api.nvim_buf_line_count(buf)
if up then
from = pos[1] - 1
to = 1
end
for l = from, to, up and -1 or 1 do
local line = vim.api.nvim_buf_get_lines(buf, l - 1, l, false)[1] or ""
local ok, start, _, kw = pcall(highlight.match, line)
if ok and start then
if config.options.highlight.comments_only and highlight.is_comment(buf, l - 1, start) == false then
kw = nil
end
end
if kw and #opts.keywords > 0 and not vim.tbl_contains(opts.keywords, kw) then
kw = nil
end
if kw then
vim.api.nvim_win_set_cursor(win, { l, start - 1 })
return
end
end
util.warn("No more todo comments to jump to")
end
function M.next(opts)
jump(false, opts)
end
function M.prev(opts)
jump(true, opts)
end
return M

View File

@ -0,0 +1,130 @@
local Config = require("todo-comments.config")
local Highlight = require("todo-comments.highlight")
local Util = require("todo-comments.util")
local M = {}
local function keywords_filter(opts_keywords)
assert(not opts_keywords or type(opts_keywords) == "string", "'keywords' must be a comma separated string or nil")
local all_keywords = vim.tbl_keys(Config.keywords)
if not opts_keywords then
return all_keywords
end
local filters = vim.split(opts_keywords, ",")
return vim.tbl_filter(function(kw)
return vim.tbl_contains(filters, kw)
end, all_keywords)
end
function M.process(lines)
local results = {}
for _, line in pairs(lines) do
local file, row, col, text = line:match("^(.+):(%d+):(%d+):(.*)$")
if file then
local item = {
filename = file,
lnum = tonumber(row),
col = tonumber(col),
line = text,
}
local start, finish, kw = Highlight.match(text)
if start then
kw = Config.keywords[kw] or kw
item.tag = kw
item.text = vim.trim(text:sub(start))
item.message = vim.trim(text:sub(finish + 1))
table.insert(results, item)
end
end
end
return results
end
function M.search(cb, opts)
opts = opts or {}
opts.cwd = opts.cwd or "."
opts.cwd = vim.fn.fnamemodify(opts.cwd, ":p")
opts.disable_not_found_warnings = opts.disable_not_found_warnings or false
if not Config.loaded then
Util.error("todo-comments isn't loaded. Did you run setup()?")
return
end
local command = Config.options.search.command
if vim.fn.executable(command) ~= 1 then
Util.error(command .. " was not found on your path")
return
end
local ok, Job = pcall(require, "plenary.job")
if not ok then
Util.error("search requires https://github.com/nvim-lua/plenary.nvim")
return
end
local args =
vim.tbl_flatten({ Config.options.search.args, Config.search_regex(keywords_filter(opts.keywords)), opts.cwd })
Job
:new({
command = command,
args = args,
on_exit = vim.schedule_wrap(function(j, code)
if code == 2 then
local error = table.concat(j:stderr_result(), "\n")
Util.error(command .. " failed with code " .. code .. "\n" .. error)
end
if code == 1 and opts.disable_not_found_warnings ~= true then
Util.warn("no todos found")
end
local lines = j:result()
cb(M.process(lines))
end),
})
:start()
end
local function parse_opts(opts)
if not opts or type(opts) ~= "string" then
return opts
end
return {
keywords = opts:match("keywords=(%S*)"),
cwd = opts:match("cwd=(%S*)"),
}
end
function M.setqflist(opts)
M.setlist(opts)
end
function M.setloclist(opts)
M.setlist(opts, true)
end
function M.setlist(opts, use_loclist)
opts = parse_opts(opts) or {}
opts.open = (opts.open ~= nil and {opts.open} or {true})[1]
M.search(function(results)
if use_loclist then
vim.fn.setloclist(0, {}, " ", { title = "Todo", id = "$", items = results })
else
vim.fn.setqflist({}, " ", { title = "Todo", id = "$", items = results })
end
if opts.open then
if use_loclist then
vim.cmd([[lopen]])
else
vim.cmd([[copen]])
end
end
local win = vim.fn.getqflist({ winid = true })
if win.winid ~= 0 then
Highlight.highlight_win(win.winid, true)
end
end, opts)
end
return M

View File

@ -0,0 +1,40 @@
--- @class Util
local M = {}
function M.get_hl(name)
local ok, hl = pcall(vim.api.nvim_get_hl_by_name, name, true)
if not ok then
return
end
for _, key in pairs({ "foreground", "background", "special" }) do
if hl[key] then
hl[key] = string.format("#%06x", hl[key])
end
end
return hl
end
function M.hex2rgb(hex)
hex = hex:gsub("#", "")
return tonumber("0x" .. hex:sub(1, 2)), tonumber("0x" .. hex:sub(3, 4)), tonumber("0x" .. hex:sub(5, 6))
end
function M.rgb2hex(r, g, b)
return string.format("#%02x%02x%02x", r, g, b)
end
function M.is_dark(hex)
local r, g, b = M.hex2rgb(hex)
local lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255
return lum <= 0.5
end
function M.warn(msg)
vim.notify(msg, vim.log.levels.WARN, { title = "TodoComments" })
end
function M.error(msg)
vim.notify(msg, vim.log.levels.ERROR, { title = "TodoComments" })
end
return M