mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-01-24 02:50:03 +08:00
302 lines
7.1 KiB
Lua
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
|