Update generated nvim config
This commit is contained in:
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
Reference in New Issue
Block a user