1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-24 02:50:03 +08:00
SpaceVim/lua/spacevim/plugin/todo.lua
2024-03-24 21:46:37 +08:00

302 lines
7.1 KiB
Lua

--=============================================================================
-- todo.lua --- todo manager for SpaceVim
-- Copyright (c) 2016-2023 Wang Shidong & Contributors
-- Author: Wang Shidong < wsdjeg@outlook.com >
-- URL: https://spacevim.org
-- License: GPLv3
--=============================================================================
local M = {}
local sys = require('spacevim.api').import('system')
local reg = require('spacevim.api').import('vim.regex')
local project_manager_registered = false
local bufnr = -1
local todo_jobid = -1
local todos = {}
local todo = {}
local winnr = -1
-- set the default labels for todo manager.
local labels = { 'fixme', 'question', 'todo', 'idea' }
local prefix = '@'
local grep_default_exe, grep_default_opt, grep_default_ropt, grep_default_expr_opt, grep_default_fix_string_opt, grep_default_ignore_case, grep_default_smart_case =
require('spacevim.plugin.search').default_tool()
local logger = require('spacevim.logger').derive('todo')
local jobstart = vim.fn.jobstart
-- the labels_partten is used for viml function: matchstr etc
local labels_partten = ''
-- labels_regex is for command line argv
local labels_regex = ''
local function empty(d) -- {{{
if type(d) == 'string' then
return d == ''
end
end
-- }}}
local function stderr(id, data, event) -- {{{
if id ~= todo_jobid then
return
end
for _, d in ipairs(data) do
logger.debug('stderr: ' .. d)
end
end
-- }}}
local function data_to_todo(d) -- {{{
logger.debug('stdout:' .. d)
local f = vim.split(d, ':%d+:')[1]
logger.debug('file is:' .. f)
local i, j = string.find(d, ':%d+:')
local line = string.sub(d, i + 1, j - 1)
logger.debug('line is:' .. line)
local column = string.sub(vim.fn.matchstr(d, [[\(:\d\+\)\@<=:\d\+:]]), 1, -2)
local label = string.sub(vim.fn.matchstr(d, labels_partten), #prefix + 1, -1)
logger.debug('label is:' .. label)
local title = vim.fn.get(vim.fn.split(d, prefix .. label), 1, '')
logger.debug('title is:' .. title)
return {
file = f,
line = line,
column = column,
title = title,
label = label,
}
end
-- }}}
local function stdout(id, data, event) -- {{{
if id ~= todo_jobid then
return
end
for _, d in ipairs(data) do
if not empty(d) then
table.insert(todos, data_to_todo(d))
end
end
end
-- }}}
local function open_todo() -- {{{
local t = todos[vim.fn.line('.')]
if vim.g.spacevim_todo_close_list == 1 or vim.g.spacevim_todo_close_list == true then
vim.cmd('close')
end
vim.cmd(winnr .. 'wincmd w')
vim.cmd('e ' .. t.file)
vim.fn.cursor(t.line, t.column)
vim.cmd('noautocmd normal! :')
end
-- }}}
--
local function apply_to_quickfix() -- {{{
local qf = {}
for _, v in ipairs(todos) do
table.insert(qf, {
filename = v.file,
lnum = v.line,
col = v.column,
text = v.title,
type = v.label,
})
end
vim.fn.setqflist({}, 'r', {
title = 'Todos',
items = qf,
})
vim.cmd('close')
vim.cmd(winnr .. 'wincmd w')
vim.cmd('botright copen')
end
-- }}}
local function indexof(t, v) -- {{{
for i, x in ipairs(t) do
if x == v then
return i
end
end
return -1
end
-- }}}
local function extend(t1, t2) -- {{{
for _, v in ipairs(t2) do
table.insert(t1, v)
end
end
-- }}}
local function compare_todo(a, b) -- {{{
local i = indexof(labels, a.label)
local j = indexof(labels, b.label)
if i < j then
return true
else
return false
end
end
-- }}}
local function max_len(t, k) -- {{{
local l = 0
for _, v in ipairs(t) do
l = math.max(#v[k], l)
end
return l
end
-- }}}
local function on_exit(id, data, event) -- {{{
if id ~= todo_jobid then
return
end
logger.info('todomanager job exit with:' .. data)
table.sort(todos, compare_todo)
local lw = max_len(todos, 'label') + 1
local fw = max_len(todos, 'file') + 1
local lines = {}
for _, v in ipairs(todos) do
table.insert(
lines,
prefix
.. v.label
.. string.rep(' ', lw - #v.label)
.. v.file
.. string.rep(' ', fw - #v.file)
.. v.title
)
end
local ma = vim.fn.getbufvar(bufnr, '&ma')
vim.fn.setbufvar(bufnr, '&ma', 1)
-- update the buffer only when the buffer exists.
if vim.fn.bufexists(bufnr) == 1 then
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
end
vim.fn.setbufvar(bufnr, '&ma', ma)
end
-- }}}
-- labels to command line regex
local function get_labels_regex() -- {{{
local sep = ''
if grep_default_exe == 'rg' then
sep = '|'
elseif grep_default_exe == 'grep' then
sep = '\\|'
else
sep = '|'
end
local rst = ''
local i = 1
for _, v in ipairs(labels) do
rst = rst .. prefix .. v .. [[\b]]
if i ~= #labels then
rst = rst .. sep
end
i = i + 1
end
return rst
end
-- }}}
local function get_labels_partten() -- {{{
local sep = '|'
local rst = [[\v]]
local i = 1
local p = prefix
if prefix == '@' then
p = [[\@]]
end
for _, v in ipairs(labels) do
rst = rst .. p .. v .. [[>]]
if i ~= #labels then
rst = rst .. sep
end
i = i + 1
end
return rst
end
-- }}}
local function update_todo_content() -- {{{
if vim.g.spacevim_todo_labels ~= nil then
labels = vim.g.spacevim_todo_labels
end
if vim.g.spacevim_todo_prefix ~= nil then
prefix = vim.g.spacevim_todo_prefix
end
todos = {}
labels_regex = get_labels_regex()
labels_partten = get_labels_partten()
local argv = { grep_default_exe }
extend(argv, grep_default_opt)
extend(argv, { labels_regex })
if
sys.isWindows
and (grep_default_exe == 'rg' or grep_default_exe == 'ag' or grep_default_exe == 'pt')
then
extend(argv, { '.' })
elseif sys.isWindows and grep_default_exe == 'findstr' then
extend(argv, { '*.*' })
elseif not sys.isWindows and grep_default_exe == 'rg' then
extend(argv, { '.' })
end
extend(argv, grep_default_ropt)
logger.info('cmd:' .. vim.inspect(argv))
logger.info(' labels_partten: ' .. labels_partten)
todo_jobid = jobstart(argv, {
on_stdout = stdout,
on_stderr = stderr,
on_exit = on_exit,
})
logger.info('jobid:' .. todo_jobid)
end
-- }}}
local function open_win() -- {{{
if bufnr ~= 0 and vim.fn.bufexists(bufnr) == 1 then
vim.cmd('bd ' .. bufnr)
end
vim.cmd('botright split __todo_manager__')
winnr = vim.fn.winnr('#')
local lines = vim.o.lines * 30 / 100
vim.cmd('resize ' .. lines)
vim.cmd([[
setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline nospell nonu norelativenumber winfixheight nomodifiable
set filetype=SpaceVimTodoManager
]])
bufnr = vim.fn.bufnr('%')
update_todo_content()
vim.api.nvim_buf_set_keymap(bufnr, 'n', '<Enter>', '', {
callback = open_todo,
})
vim.api.nvim_buf_set_keymap(bufnr, 'n', '<C-q>', '', {
callback = apply_to_quickfix,
})
end
-- }}}
function M.list() -- {{{
if not project_manager_registered then
require('spacevim.plugin.projectmanager').reg_callback(M.on_cwd_changed, 'update_todo_content')
end
open_win()
end
-- }}}
function M.on_cwd_changed()
if vim.api.nvim_buf_is_valid(bufnr) then
update_todo_content()
end
end
return M