From 2d6797567c7813e1061f78b4d380357579a132c6 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 9 Dec 2024 16:50:52 +0000 Subject: [PATCH 01/37] chore: minor code simplification --- plugin/guard.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/plugin/guard.lua b/plugin/guard.lua index be51196..e86353e 100644 --- a/plugin/guard.lua +++ b/plugin/guard.lua @@ -95,11 +95,10 @@ local cmds = { api.nvim_create_user_command('Guard', function(opts) local f = cmds[opts.args] - if f then - f(opts) - else - vim.notify('[Guard]: Invalid subcommand: ' .. opts.args) - end + or function(_opts) + vim.notify('[Guard]: Invalid subcommand: ' .. _opts.args) + end + f(opts) end, { nargs = '+', complete = function(arg_lead, cmdline, _) From d2b03a1c1538bc576436bf3cd9ca99db17b5bc73 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 9 Dec 2024 20:54:25 +0000 Subject: [PATCH 02/37] feat!: event api --- lua/guard/init.lua | 16 ---------------- plugin/guard.lua | 19 +++++++++++++++++-- 2 files changed, 17 insertions(+), 18 deletions(-) delete mode 100644 lua/guard/init.lua diff --git a/lua/guard/init.lua b/lua/guard/init.lua deleted file mode 100644 index e3f5dd5..0000000 --- a/lua/guard/init.lua +++ /dev/null @@ -1,16 +0,0 @@ -return { - setup = function(opt) - vim.deprecate( - 'Calling require("guard").setup', - 'vim.g.guard_config', - '1.1.0', - 'guard.nvim', - true - ) - vim.g.guard_config = vim.tbl_extend('force', { - fmt_on_save = true, - lsp_as_default_formatter = false, - save_on_fmt = true, - }, opt or {}) - end, -} diff --git a/plugin/guard.lua b/plugin/guard.lua index e86353e..1f8be35 100644 --- a/plugin/guard.lua +++ b/plugin/guard.lua @@ -12,7 +12,12 @@ local cmds = { fmt = function() require('guard.format').do_fmt() end, - enable = function(opts) + + lint = function() + require('guard.lint').do_lint() + end, + + ['enable-fmt'] = function(opts) local group = events.group local arg = opts.args local bufnr = (#opts.fargs == 1) and api.nvim_get_current_buf() or tonumber(arg) @@ -24,7 +29,8 @@ local cmds = { require('guard.events').try_attach_to_buf(bufnr) end end, - disable = function(opts) + + ['disable-fmt'] = function(opts) local group = events.group local arg = opts.args local bufnr = (#opts.fargs == 1) and api.nvim_get_current_buf() or tonumber(arg) @@ -36,6 +42,15 @@ local cmds = { api.nvim_del_autocmd(bufau[1].id) end end, + + ['enable-lint'] = function() + print('TODO') + end, + + ['disable-lint'] = function() + print('TODO') + end, + info = function() local util = require('guard.util') local group = events.group From 7911e945adde48f3f6c61c99ff3a064d45fdd6f0 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 9 Dec 2024 20:59:15 +0000 Subject: [PATCH 03/37] fix: test --- spec/command_spec.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/command_spec.lua b/spec/command_spec.lua index 10eaaa9..401beed 100644 --- a/spec/command_spec.lua +++ b/spec/command_spec.lua @@ -44,7 +44,7 @@ describe('commands', function() same(api.nvim_buf_get_lines(bufnr, 0, -1, false), { 'local a = 42' }) -- disable - vim.cmd('Guard disable') + vim.cmd('Guard disable-fmt') api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'local a', ' =42', @@ -57,7 +57,7 @@ describe('commands', function() }) -- enable - vim.cmd('Guard enable') + vim.cmd('Guard enable-fmt') -- make changes to trigger format api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'local a', From bd33a3b4a354bbd5ab75aeae1c56cbb743327840 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Tue, 10 Dec 2024 08:38:17 +0000 Subject: [PATCH 04/37] feat: provide command interface --- README.md | 6 +- lua/guard/api.lua | 114 ++++++++++++++++++++++++++++++++ lua/guard/events.lua | 144 ++++++++++++++++++++++++++++------------- lua/guard/filetype.lua | 11 ++-- lua/guard/util.lua | 22 +++---- plugin/guard.lua | 94 ++++----------------------- spec/lint_spec.lua | 0 7 files changed, 245 insertions(+), 146 deletions(-) create mode 100644 lua/guard/api.lua create mode 100644 spec/lint_spec.lua diff --git a/README.md b/README.md index 07a40de..5f1a45e 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,7 @@ vim.g.guard_config = { ``` - Use `Guard fmt` to manually call format, when there is a visual selection only the selection is formatted. **NOTE**: Regional formatting just sends your selection to the formatter, if there's not enough context incoherent formatting might occur (e.g. indent being erased) -- `Guard disable` disables auto format for the current buffer, you can also `Guard disable 16` (the buffer number) -- Use `Guard enable` to re-enable auto format, usage is the same as `Guard disable` +- `enable-fmt`, `disable-fmt` turns auto formatting on and off for the current buffer. Format c files with clang-format and lint with clang-tidy: @@ -92,6 +91,9 @@ Lint all your files with `codespell` ft('*'):lint('codespell') ``` +- Use `Guard Lint` to lint manually. +- `enable-lint` and `disable-lint` controls auto linting for the current buffer. + You can also easily create your own configuration that's not in `guard-collection`, see [CUSTOMIZE.md](./CUSTOMIZE.md). For more niche use cases, [ADVANCED.md](./ADVANCED.md) demonstrates how to: diff --git a/lua/guard/api.lua b/lua/guard/api.lua new file mode 100644 index 0000000..c650d6b --- /dev/null +++ b/lua/guard/api.lua @@ -0,0 +1,114 @@ +-- These are considered public API and changing their signature would be a breaking change +local M = {} +local api = vim.api +local events = require('guard.events') + +---Format bufnr or current buffer +---@param bufnr number? +function M.fmt(bufnr) + require('guard.format').do_fmt(bufnr) +end + +---Lint bufnr or current buffer +---@param bufnr number? +function M.lint(bufnr) + require('guard.lint').do_lint(bufnr) +end + +---Enable format for bufnr or current buffer +---@param bufnr number? +function M.enable_fmt(bufnr) + require('guard.events').try_attach_fmt_to_buf(bufnr or api.nvim_get_current_buf()) +end + +---Disable format for bufnr or current buffer +---@param bufnr number? +function M.disable_fmt(bufnr) + local aus = events.get_format_autocmds(bufnr or api.nvim_get_current_buf()) + vim.iter(aus):each(function(au) + api.nvim_del_autocmd(au.id) + end) +end + +---Enable lint for bufnr or current buffer +---@param bufnr number? +function M.enable_lint(bufnr) + local buf = bufnr or api.nvim_get_current_buf() + local ft = require('guard.filetype')[vim.bo[buf].ft] or {} + if ft.linter and #ft.linter > 0 then + events.try_attach_lint_to_buf(buf, require('guard.util').linter_events(ft.linter[1])) + end +end + +---Disable format for bufnr or current buffer +---@param bufnr number? +function M.disable_lint(bufnr) + local aus = events.get_lint_autocmds(bufnr or api.nvim_get_current_buf()) + vim.iter(aus):each(function(au) + api.nvim_del_autocmd(au.id) + end) +end + +---Show guard info for current buffer +function M.info() + local util = require('guard.util') + local buf = api.nvim_get_current_buf() + local ft = require('guard.filetype')[vim.bo[buf].ft] or {} + local formatters = ft.formatter or {} + local linters = ft.linter or {} + local fmtau = events.get_format_autocmds(buf) + local lintau = events.get_lint_autocmds(buf) + + util.open_info_win() + local lines = { + '# Guard info (press Esc or q to close)', + '## Settings:', + ('- `fmt_on_save`: %s'):format(util.getopt('fmt_on_save')), + ('- `lsp_as_default_formatter`: %s'):format(util.getopt('lsp_as_default_formatter')), + ('- `save_on_fmt`: %s'):format(util.getopt('save_on_fmt')), + '', + ('## Current buffer has filetype %s:'):format(vim.bo[buf].ft), + ('- %s formatter autocmds attached'):format(#fmtau), + ('- %s linter autocmds attached'):format(#lintau), + '- formatters:', + '', + '```lua', + } + + vim.list_extend( + lines, + vim + .iter(formatters) + :map(function(formatter) + return vim.split(vim.inspect(formatter), '\n', { trimempty = true }) + end) + :flatten() + :totable() + ) + + vim.list_extend(lines, { + '```', + '', + '- linters:', + '', + '```lua', + }) + + vim.list_extend( + lines, + vim + .iter(linters) + :map(function(linter) + return vim.split(vim.inspect(linter), '\n', { trimempty = true }) + end) + :flatten() + :totable() + ) + + vim.list_extend(lines, { '```' }) + + api.nvim_buf_set_lines(0, 0, -1, true, lines) + api.nvim_set_option_value('modifiable', false, { buf = 0 }) +end + +return M diff --git a/lua/guard/events.lua b/lua/guard/events.lua index 25da79b..c6773ec 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -7,8 +7,71 @@ local iter = vim.iter local M = {} M.group = api.nvim_create_augroup('Guard', { clear = true }) -function M.try_attach_to_buf(buf) - if not util.check_should_attach(buf) then +local debounce_timer = nil +local debounced_lint = function(opt) + if debounce_timer then + debounce_timer:stop() + debounce_timer = nil + end + ---@diagnostic disable-next-line: undefined-field + debounce_timer = assert(uv.new_timer()) --[[uv_timer_t]] + debounce_timer:start(500, 0, function() + debounce_timer:stop() + debounce_timer:close() + debounce_timer = nil + vim.schedule(function() + require('guard.lint').do_lint(opt.buf) + end) + end) +end + +---@param bufnr number +---@return vim.api.keyset.get_autocmds.ret[] +function M.get_format_autocmds(bufnr) + if not api.nvim_buf_is_valid(bufnr) then + return {} + end + return api.nvim_get_autocmds({ group = M.group, event = 'BufWritePre', buffer = bufnr }) +end + +---@param bufnr number +---@return vim.api.keyset.get_autocmds.ret[] +function M.get_lint_autocmds(bufnr) + if not api.nvim_buf_is_valid(bufnr) then + return {} + end + local aus = api.nvim_get_autocmds({ + group = M.group, + event = { 'BufWritePost', 'BufEnter', 'TextChanged', 'InsertLeave' }, + buffer = bufnr, + }) + return vim.list_extend( + aus, + api.nvim_get_autocmds({ + group = M.group, + event = 'User', + pattern = 'GuardFmt', + buffer = bufnr, + }) + ) +end + +---@param buf number +---@return boolean +function M.check_fmt_should_attach(buf) + -- check if it's not attached already and has an underlying file + return #M.get_format_autocmds(buf) == 0 and vim.bo[buf].buftype ~= 'nofile' +end + +---@param buf number +---@return boolean +function M.check_lint_should_attach(buf) + return #M.get_lint_autocmds(buf) == 0 and vim.bo[buf].buftype ~= 'nofile' +end + +---@param buf number +function M.try_attach_fmt_to_buf(buf) + if not M.check_fmt_should_attach(buf) then return end au('BufWritePre', { @@ -22,18 +85,46 @@ function M.try_attach_to_buf(buf) }) end +---@param buf number +---@param events string[] +function M.try_attach_lint_to_buf(buf, events) + if not M.check_lint_should_attach(buf) then + return + end + + for _, ev in ipairs(events) do + if ev == 'User GuardFmt' then + au('User', { + group = M.group, + pattern = 'GuardFmt', + callback = function(opt) + if opt.data.status == 'done' then + debounced_lint(opt) + end + end, + }) + else + au(ev, { + group = M.group, + buffer = buf, + callback = debounced_lint, + }) + end + end +end + ---@param ft string function M.fmt_attach_to_existing(ft) local bufs = api.nvim_list_bufs() for _, buf in ipairs(bufs) do if vim.bo[buf].ft == ft then - M.try_attach_to_buf(buf) + M.try_attach_fmt_to_buf(buf) end end end ---@param ft string -function M.watch_ft(ft) +function M.fmt_watch_ft(ft) -- check if all cmds executable before registering formatter iter(require('guard.filetype')[ft].formatter):any(function(config) if type(config) == 'table' and config.cmd and vim.fn.executable(config.cmd) ~= 1 then @@ -47,7 +138,7 @@ function M.watch_ft(ft) group = M.group, pattern = ft, callback = function(args) - M.try_attach_to_buf(args.buf) + M.try_attach_fmt_to_buf(args.buf) end, desc = 'guard', }) @@ -69,9 +160,9 @@ function M.maybe_default_to_lsp(config, ft, buf) pattern = ft, }) == 0 then - M.watch_ft(ft) + M.fmt_watch_ft(ft) end - M.try_attach_to_buf(buf) + M.try_attach_fmt_to_buf(buf) end end @@ -96,8 +187,7 @@ function M.create_lspattach_autocmd() }) end -local debounce_timer = nil -function M.register_lint(ft, events) +function M.lint_watch_ft(ft, events) iter(require('guard.filetype')[ft].linter):any(function(config) if config.cmd and vim.fn.executable(config.cmd) ~= 1 then report_error(config.cmd .. ' not executable') @@ -109,41 +199,7 @@ function M.register_lint(ft, events) pattern = ft, group = M.group, callback = function(args) - local cb = function(opt) - if debounce_timer then - debounce_timer:stop() - debounce_timer = nil - end - ---@diagnostic disable-next-line: undefined-field - debounce_timer = assert(uv.new_timer()) --[[uv_timer_t]] - debounce_timer:start(500, 0, function() - debounce_timer:stop() - debounce_timer:close() - debounce_timer = nil - vim.schedule(function() - require('guard.lint').do_lint(opt.buf) - end) - end) - end - for _, ev in ipairs(events) do - if ev == 'User GuardFmt' then - au('User', { - group = M.group, - pattern = 'GuardFmt', - callback = function(opt) - if opt.data.status == 'done' then - cb(opt) - end - end, - }) - else - au(ev, { - group = M.group, - buffer = args.buf, - callback = cb, - }) - end - end + M.try_attach_lint_to_buf(args.buf, events) end, }) end diff --git a/lua/guard/filetype.lua b/lua/guard/filetype.lua index 74e6044..05fa4aa 100644 --- a/lua/guard/filetype.lua +++ b/lua/guard/filetype.lua @@ -73,7 +73,7 @@ local function box(ft) M[it] = box(it) M[it].formatter = self.formatter end - events.watch_ft(it) + events.fmt_watch_ft(it) events.fmt_attach_to_existing(it) end return self @@ -88,17 +88,14 @@ local function box(ft) util.toolcopy(try_as('linter', config)), } local events = require('guard.events') - local evs = { 'User GuardFmt', 'BufWritePost', 'BufEnter' } - if config.stdin then - table.insert(events, 'TextChanged') - table.insert(events, 'InsertLeave') - end + -- TODO: the events might not be correct if we add more linters later + local evs = util.linter_events(config) for _, it in ipairs(self:ft()) do if it ~= ft then M[it] = box(it) M[it].linter = self.linter end - events.register_lint(it, evs) + events.lint_watch_ft(it, evs) end return self end diff --git a/lua/guard/util.lua b/lua/guard/util.lua index 25ebebd..32b147c 100644 --- a/lua/guard/util.lua +++ b/lua/guard/util.lua @@ -196,8 +196,9 @@ function M.open_info_win() width = math.ceil(width * 0.6), border = 'single', }) - vim.bo.ft = 'markdown' + api.nvim_set_option_value('filetype', 'markdown', { buf = buf }) api.nvim_set_option_value('bufhidden', 'wipe', { buf = buf }) + api.nvim_set_option_value('buftype', 'nofile', { buf = buf }) api.nvim_set_option_value('conceallevel', 3, { win = win }) api.nvim_set_option_value('relativenumber', false, { win = win }) api.nvim_set_option_value('number', false, { win = win }) @@ -217,16 +218,15 @@ function M.eval(xs) end, xs) end ----@param buf number ----@return boolean -function M.check_should_attach(buf) - local bo = vim.bo[buf] - -- check if it's not attached already and has an underlying file - return #api.nvim_get_autocmds({ - group = M.group, - event = 'BufWritePre', - buffer = buf, - }) == 0 and bo.buftype ~= 'nofile' +---@param config LintConfig +---@return string[] +function M.linter_events(config) + local events = { 'User GuardFmt', 'BufWritePost', 'BufEnter' } + if config.stdin then + table.insert(events, 'TextChanged') + table.insert(events, 'InsertLeave') + end + return events end return M diff --git a/plugin/guard.lua b/plugin/guard.lua index 1f8be35..ed4d490 100644 --- a/plugin/guard.lua +++ b/plugin/guard.lua @@ -10,110 +10,40 @@ local ft_handler = require('guard.filetype') local cmds = { fmt = function() - require('guard.format').do_fmt() + require('guard.api').fmt() end, lint = function() - require('guard.lint').do_lint() + require('guard.api').lint() end, - ['enable-fmt'] = function(opts) - local group = events.group - local arg = opts.args - local bufnr = (#opts.fargs == 1) and api.nvim_get_current_buf() or tonumber(arg) - if not bufnr or not api.nvim_buf_is_valid(bufnr) then - return - end - local bufau = api.nvim_get_autocmds({ group = group, event = 'BufWritePre', buffer = bufnr }) - if #bufau == 0 then - require('guard.events').try_attach_to_buf(bufnr) - end + ['enable-fmt'] = function() + require('guard.api').enable_fmt() end, - ['disable-fmt'] = function(opts) - local group = events.group - local arg = opts.args - local bufnr = (#opts.fargs == 1) and api.nvim_get_current_buf() or tonumber(arg) - if not bufnr or not api.nvim_buf_is_valid(bufnr) then - return - end - local bufau = api.nvim_get_autocmds({ group = group, event = 'BufWritePre', buffer = bufnr }) - if #bufau ~= 0 then - api.nvim_del_autocmd(bufau[1].id) - end + ['disable-fmt'] = function() + require('guard.api').disable_fmt() end, ['enable-lint'] = function() - print('TODO') + require('guard.api').enable_lint() end, ['disable-lint'] = function() - print('TODO') + require('guard.api').disable_lint() end, info = function() - local util = require('guard.util') - local group = events.group - local buf = api.nvim_get_current_buf() - local ft = require('guard.filetype')[vim.bo[buf].ft] or {} - local formatters = ft.formatter or {} - local linters = ft.linter or {} - local fmtau = api.nvim_get_autocmds({ group = group, event = 'BufWritePre', buffer = buf }) - local lintau = api.nvim_get_autocmds({ group = group, event = 'BufWritePost', buffer = buf }) - util.open_info_win() - local lines = { - '# Guard info (press Esc or q to close)', - '## Settings:', - ('- `fmt_on_save`: %s'):format(util.getopt('fmt_on_save')), - ('- `lsp_as_default_formatter`: %s'):format(util.getopt('lsp_as_default_formatter')), - ('- `save_on_fmt`: %s'):format(util.getopt('save_on_fmt')), - '', - ('## Current buffer has filetype %s:'):format(vim.bo[buf].ft), - ('- %s formatter autocmds attached'):format(#fmtau), - ('- %s linter autocmds attached'):format(#lintau), - '- formatters:', - '', - '```lua', - } - vim.list_extend( - lines, - vim - .iter(formatters) - :map(function(formatter) - return vim.split(vim.inspect(formatter), '\n', { trimempty = true }) - end) - :flatten() - :totable() - ) - vim.list_extend(lines, { - '```', - '', - '- linters:', - '', - '```lua', - }) - vim.list_extend( - lines, - vim - .iter(linters) - :map(function(linter) - return vim.split(vim.inspect(linter), '\n', { trimempty = true }) - end) - :flatten() - :totable() - ) - vim.list_extend(lines, { '```' }) - api.nvim_buf_set_lines(0, 0, -1, true, lines) - api.nvim_set_option_value('modifiable', false, { buf = 0 }) + require('guard.api').info() end, } api.nvim_create_user_command('Guard', function(opts) local f = cmds[opts.args] - or function(_opts) - vim.notify('[Guard]: Invalid subcommand: ' .. _opts.args) + or function() + vim.notify('[Guard]: Invalid subcommand: ' .. opts.args) end - f(opts) + f() end, { nargs = '+', complete = function(arg_lead, cmdline, _) diff --git a/spec/lint_spec.lua b/spec/lint_spec.lua new file mode 100644 index 0000000..e69de29 From 7b7fb543c3414a17d4a5f6e8fb236ad7af33bd67 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Dec 2024 08:38:37 +0000 Subject: [PATCH 05/37] chore(doc): auto generate docs --- doc/guard.nvim.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/guard.nvim.txt b/doc/guard.nvim.txt index 335598f..00ccbc6 100644 --- a/doc/guard.nvim.txt +++ b/doc/guard.nvim.txt @@ -1,4 +1,4 @@ -*guard.nvim.txt* For NVIM v0.8.0 Last change: 2024 December 09 +*guard.nvim.txt* For NVIM v0.8.0 Last change: 2024 December 10 ============================================================================== Table of Contents *guard.nvim-table-of-contents* @@ -80,8 +80,7 @@ To register formatters and linters: < - Use `Guard fmt` to manually call format, when there is a visual selection only the selection is formatted. **NOTE**: Regional formatting just sends your selection to the formatter, if there’s not enough context incoherent formatting might occur (e.g. indent being erased) -- `Guard disable` disables auto format for the current buffer, you can also `Guard disable 16` (the buffer number) -- Use `Guard enable` to re-enable auto format, usage is the same as `Guard disable` +- `enable-fmt`, `disable-fmt` turns auto formatting on and off for the current buffer. Format c files with clang-format and lint with clang-tidy: @@ -112,6 +111,9 @@ Lint all your files with `codespell` ft('*'):lint('codespell') < +- Use `Guard Lint` to lint manually. +- `enable-lint` and `disable-lint` controls auto linting for the current buffer. + You can also easily create your own configuration that’s not in `guard-collection`, see CUSTOMIZE.md <./CUSTOMIZE.md>. From 771fa7b391e14f9102085bf4821166152ea79380 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Thu, 12 Dec 2024 10:11:07 +0000 Subject: [PATCH 06/37] feat: add error reporting for linters --- lua/guard/lint.lua | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lua/guard/lint.lua b/lua/guard/lint.lua index 8bf50f3..95dd024 100644 --- a/lua/guard/lint.lua +++ b/lua/guard/lint.lua @@ -44,13 +44,26 @@ function M.do_lint(buf) local results = {} for _, lint in ipairs(linters) do + ---@type string local data + if lint.cmd then lint.cwd = lint.cwd or cwd - data = spawn.transform(util.get_cmd(lint, fname), lint, prev_lines) + local out = spawn.transform(util.get_cmd(lint, fname), lint, prev_lines) + + -- TODO: unify this error handling logic with formatter + if type(out) == 'table' then + -- indicates error + vim.notify( + '[Guard]: ' .. ('%s exited with code %d\n%s'):format(out.cmd, out.code, out.stderr), + vim.log.levels.WARN + ) + data = '' + end else data = lint.fn(prev_lines) end + if #data > 0 then vim.list_extend(results, lint.parse(data, buf)) end From 11930998841749383cd4eeaf1fa39bdfe1234e56 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Dec 2024 10:11:31 +0000 Subject: [PATCH 07/37] chore(doc): auto generate docs --- doc/guard.nvim.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/guard.nvim.txt b/doc/guard.nvim.txt index 00ccbc6..9498873 100644 --- a/doc/guard.nvim.txt +++ b/doc/guard.nvim.txt @@ -1,4 +1,4 @@ -*guard.nvim.txt* For NVIM v0.8.0 Last change: 2024 December 10 +*guard.nvim.txt* For NVIM v0.8.0 Last change: 2024 December 12 ============================================================================== Table of Contents *guard.nvim-table-of-contents* From 596b3a3118658d349c4aaed3cbea7decc1fe2e13 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Thu, 12 Dec 2024 10:32:00 +0000 Subject: [PATCH 08/37] ci: add linter test --- lua/guard/events.lua | 1 - lua/guard/util.lua | 1 + spec/format_spec.lua | 13 +++++----- spec/lint_spec.lua | 61 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 7 deletions(-) diff --git a/lua/guard/events.lua b/lua/guard/events.lua index c6773ec..b8bc8cf 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -51,7 +51,6 @@ function M.get_lint_autocmds(bufnr) group = M.group, event = 'User', pattern = 'GuardFmt', - buffer = bufnr, }) ) end diff --git a/lua/guard/util.lua b/lua/guard/util.lua index 32b147c..8dcecf8 100644 --- a/lua/guard/util.lua +++ b/lua/guard/util.lua @@ -172,6 +172,7 @@ function M.getopt(opt) fmt_on_save = true, lsp_as_default_formatter = false, save_on_fmt = true, + auto_lint = true, } if not vim.g.guard_config diff --git a/spec/format_spec.lua b/spec/format_spec.lua index 81d9388..1211a2f 100644 --- a/spec/format_spec.lua +++ b/spec/format_spec.lua @@ -2,6 +2,7 @@ local api = vim.api local equal = assert.equal local ft = require('guard.filetype') +local gapi = require('guard.api') describe('format module', function() local bufnr @@ -26,7 +27,7 @@ describe('format module', function() 'local a', ' = "test"', }) - require('guard.format').do_fmt(bufnr) + gapi.fmt() vim.wait(500) local line = api.nvim_buf_get_lines(bufnr, 0, -1, false)[1] equal([[local a = 'test']], line) @@ -46,7 +47,7 @@ describe('format module', function() 'local a', ' = "test"', }) - require('guard.format').do_fmt(bufnr) + gapi.fmt() vim.wait(500) local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.same({ "'test'", '= a local ' }, lines) @@ -62,7 +63,7 @@ describe('format module', function() 'local a', ' = "test"', }) - require('guard.format').do_fmt(bufnr) + gapi.fmt() vim.wait(500) local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.same({ 'local a = "test"nil' }, lines) @@ -89,21 +90,21 @@ describe('format module', function() 'foo', 'bar', }) - require('guard.format').do_fmt(bufnr) + gapi.fmt() vim.wait(500) local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.same({ 'def' }, lines) vim.g.some_flag_idk = true - require('guard.format').do_fmt(bufnr) + gapi.fmt() vim.wait(500) lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.same({ 'abc' }, lines) vim.g.some_flag_idk = false - require('guard.format').do_fmt(bufnr) + gapi.fmt() vim.wait(500) lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) assert.are.same({ 'def' }, lines) diff --git a/spec/lint_spec.lua b/spec/lint_spec.lua index e69de29..48454c8 100644 --- a/spec/lint_spec.lua +++ b/spec/lint_spec.lua @@ -0,0 +1,61 @@ +---@diagnostic disable: undefined-field, undefined-global +local api = vim.api +local same = assert.are.same +local ft = require('guard.filetype') +local lint = require('guard.lint') +local gapi = require('guard.api') +local ns = api.nvim_get_namespaces()['Guard'] + +describe('lint module', function() + local bufnr + before_each(function() + for k, _ in pairs(ft) do + ft[k] = nil + end + + bufnr = api.nvim_create_buf(true, false) + vim.bo[bufnr].filetype = 'lua' + api.nvim_set_current_buf(bufnr) + vim.cmd('silent! write! /tmp/lint_spec_test.lua') + end) + + local mock_linter_regex = { + fn = function() + return '/tmp/lint_spec_test.lua:1:1: warning: Very important error message [error code 114514]' + end, + parse = lint.from_regex({ + source = 'mock_linter_regex', + regex = ':(%d+):(%d+):%s+(%w+):%s+(.-)%s+%[(.-)%]', + groups = { 'lnum', 'col', 'severity', 'message', 'code' }, + offset = 0, + severities = { + information = lint.severities.info, + hint = lint.severities.info, + note = lint.severities.style, + }, + }), + } + + local mock_linter_json = {} + + it('can lint with single linter', function() + ft('lua'):lint(mock_linter_regex) + + gapi.lint() + vim.wait(1000) + + same({ + { + source = 'mock_linter_regex', + bufnr = bufnr, + col = 1, + end_col = 1, + lnum = 1, + end_lnum = 1, + message = 'Very important error message[error code 114514]', + namespace = ns, + severity = 2, + }, + }, vim.diagnostic.get()) + end) +end) From fbcbafd8eaead00a6dea4443e742c577069060db Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Thu, 12 Dec 2024 10:54:43 +0000 Subject: [PATCH 09/37] ci: more tests --- lua/guard/util.lua | 16 +++++++----- spec/lint_spec.lua | 65 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/lua/guard/util.lua b/lua/guard/util.lua index 8dcecf8..3ba4bcc 100644 --- a/lua/guard/util.lua +++ b/lua/guard/util.lua @@ -210,13 +210,15 @@ end ---@param xs (FmtConfig | LintConfig)[] ---@return (FmtConfigTable | LintConfigTable)[] function M.eval(xs) - return vim.tbl_map(function(x) - if type(x) == 'function' then - return x() - else - return x - end - end, xs) + return xs + and vim.tbl_map(function(x) + if type(x) == 'function' then + return x() + else + return x + end + end, xs) + or {} end ---@param config LintConfig diff --git a/spec/lint_spec.lua b/spec/lint_spec.lua index 48454c8..74d9247 100644 --- a/spec/lint_spec.lua +++ b/spec/lint_spec.lua @@ -13,6 +13,7 @@ describe('lint module', function() ft[k] = nil end + vim.diagnostic.reset(ns, bufnr) bufnr = api.nvim_create_buf(true, false) vim.bo[bufnr].filetype = 'lua' api.nvim_set_current_buf(bufnr) @@ -36,13 +37,62 @@ describe('lint module', function() }), } - local mock_linter_json = {} + local mock_linter_json = { + fn = function() + return vim.json.encode({ + source = 'mock_linter_json', + bufnr = bufnr, + col = 1, + end_col = 9, + lnum = 1, + end_lnum = 0, + message = 'Very important error message', + namespace = ns, + severity = 'warning', + }) + end, + parse = lint.from_json({ + get_diagnostics = function(...) + return { vim.json.decode(...) } + end, + attributes = { + lnum = 'lnum', + end_lnum = 'end_lnum', + col = 'col', + end_col = 'end_col', + message = 'message', + code = 'severity', + }, + source = 'mock_linter_json', + }), + } it('can lint with single linter', function() ft('lua'):lint(mock_linter_regex) gapi.lint() - vim.wait(1000) + vim.wait(100) + + same({ + { + source = 'mock_linter_regex', + bufnr = bufnr, + col = 1, + end_col = 1, + lnum = 1, + end_lnum = 1, + message = 'Very important error message[error code 114514]', + namespace = ns, + severity = 2, + }, + }, vim.diagnostic.get()) + end) + + it('can lint with multiple linters', function() + ft('lua'):lint(mock_linter_regex):append(mock_linter_json) + + gapi.lint() + vim.wait(100) same({ { @@ -56,6 +106,17 @@ describe('lint module', function() namespace = ns, severity = 2, }, + { + bufnr = bufnr, + col = 0, + end_col = 0, + end_lnum = 0, + lnum = 0, + message = 'Very important error message[warning]', + namespace = ns, + severity = 2, + source = 'mock_linter_json', + }, }, vim.diagnostic.get()) end) end) From 8e12ec8a1e1b9d46a71e8b7ed8cf2b4ce532ae22 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Thu, 12 Dec 2024 16:13:41 +0000 Subject: [PATCH 10/37] chore: update bug report issue template --- .github/ISSUE_TEMPLATE/bug_report.yml | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 61dc4f1..2901d51 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -40,8 +40,16 @@ body: git clone https://github.com/nvimdev/guard-collection /tmp echo "vim.opt.rtp:append('/tmp/guard.nvim')" >> /tmp/repro.lua echo "vim.opt.rtp:append('/tmp/guard-collection')" >> /tmp/repro.lua + nvim --clean -u /tmp/repro.lua validations: required: true + - type: checkboxes + attributes: + - options: + - label: I understand that if my repro step is too complicated (e.g. here is my 1k+ line config and please help me), developers might not be able to help. + required: true + - label: I can confirm that my reproduction step only involves `vim.opt.rtp` and configuration themselves + required: true - type: textarea attributes: label: "Expected behaviour" @@ -53,16 +61,3 @@ body: label: "Actual behaviour" validations: required: true - - type: textarea - attributes: - label: "The minimal config used to reproduce this issue." - description: | - Run with `nvim -u /tmp/repro.lua` - placeholder: | - vim.opt.rtp:append('/tmp/guard.nvim') - vim.opt.rtp:append('/tmp/guard-collection') - -- do anything else you need to do to reproduce the issue - - render: "Lua" - validations: - required: true From 5510a1fc58211f10ad17aa146c9941156136e1db Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Thu, 12 Dec 2024 16:14:59 +0000 Subject: [PATCH 11/37] Update bug_report.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 2901d51..6214e24 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -45,7 +45,8 @@ body: required: true - type: checkboxes attributes: - - options: + label: Are you sure this is a min repro? + options: - label: I understand that if my repro step is too complicated (e.g. here is my 1k+ line config and please help me), developers might not be able to help. required: true - label: I can confirm that my reproduction step only involves `vim.opt.rtp` and configuration themselves From 964e5e380e6bb7d8ed2080797c857e18156b814e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Dec 2024 12:35:54 +0000 Subject: [PATCH 12/37] chore(doc): auto generate docs --- doc/guard.nvim.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/guard.nvim.txt b/doc/guard.nvim.txt index 9498873..e8ba5a1 100644 --- a/doc/guard.nvim.txt +++ b/doc/guard.nvim.txt @@ -1,4 +1,4 @@ -*guard.nvim.txt* For NVIM v0.8.0 Last change: 2024 December 12 +*guard.nvim.txt* For NVIM v0.8.0 Last change: 2024 December 13 ============================================================================== Table of Contents *guard.nvim-table-of-contents* From 4ad9e63894efa70d94750b4734d99c57bcc83abc Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Sun, 15 Dec 2024 19:22:03 +0000 Subject: [PATCH 13/37] wip: add custom event support for formatting --- CUSTOMIZE.md | 1 + lua/guard/_meta.lua | 6 +++++ lua/guard/api.lua | 4 +++- lua/guard/events.lua | 52 +++++++++++++++++++++++++++++++----------- lua/guard/filetype.lua | 10 ++++++-- lua/guard/format.lua | 2 +- spec/settings_spec.lua | 12 ++++++---- 7 files changed, 65 insertions(+), 22 deletions(-) diff --git a/CUSTOMIZE.md b/CUSTOMIZE.md index 617fd7d..763c4b8 100644 --- a/CUSTOMIZE.md +++ b/CUSTOMIZE.md @@ -24,6 +24,7 @@ A tool is specified as follows: -- special parse -- function: linter only, parses linter output to neovim diagnostic + events -- { name: string, opt: autocmd options }: override default events, for formatter autocmds only the first one is used (passed in from `:fmt`) } ``` diff --git a/lua/guard/_meta.lua b/lua/guard/_meta.lua index 653d9e4..159c5e8 100644 --- a/lua/guard/_meta.lua +++ b/lua/guard/_meta.lua @@ -26,3 +26,9 @@ ---@field timeout integer? ---@alias LintConfig LintConfigTable|fun(): LintConfigTable + +---@alias AuOption vim.api.keyset.create_autocmd + +---@class EventOption +---@field name string +---@field opt AuOption? diff --git a/lua/guard/api.lua b/lua/guard/api.lua index c650d6b..a5adefc 100644 --- a/lua/guard/api.lua +++ b/lua/guard/api.lua @@ -24,10 +24,12 @@ end ---Disable format for bufnr or current buffer ---@param bufnr number? function M.disable_fmt(bufnr) - local aus = events.get_format_autocmds(bufnr or api.nvim_get_current_buf()) + local buf = bufnr or api.nvim_get_current_buf() + local aus = events.get_format_autocmds(buf) vim.iter(aus):each(function(au) api.nvim_del_autocmd(au.id) end) + events.user_fmt_autocmds[vim.bo[buf].ft] = {} end ---Enable lint for bufnr or current buffer diff --git a/lua/guard/events.lua b/lua/guard/events.lua index b8bc8cf..d62c548 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -7,6 +7,8 @@ local iter = vim.iter local M = {} M.group = api.nvim_create_augroup('Guard', { clear = true }) +M.user_fmt_autocmds = {} + local debounce_timer = nil local debounced_lint = function(opt) if debounce_timer then @@ -25,13 +27,30 @@ local debounced_lint = function(opt) end) end +local function lazy_fmt(opt) + if vim.bo[opt.buf].modified and getopt('fmt_on_save') then + require('guard.format').do_fmt(opt.buf) + end +end + +---@param opt AuOption +---@param cb function +---@return AuOption +local function maybe_fill_auoption(opt, cb) + local result = vim.deepcopy(opt, false) + result.callback = (not result.command and not result.callback) and cb or result.callback + result.group = M.group + return result +end + ---@param bufnr number ---@return vim.api.keyset.get_autocmds.ret[] function M.get_format_autocmds(bufnr) if not api.nvim_buf_is_valid(bufnr) then return {} end - return api.nvim_get_autocmds({ group = M.group, event = 'BufWritePre', buffer = bufnr }) + return M.user_fmt_autocmds[vim.bo[bufnr].ft] + or api.nvim_get_autocmds({ group = M.group, event = 'BufWritePre', buffer = bufnr }) end ---@param bufnr number @@ -76,11 +95,7 @@ function M.try_attach_fmt_to_buf(buf) au('BufWritePre', { group = M.group, buffer = buf, - callback = function(opt) - if vim.bo[opt.buf].modified and getopt('fmt_on_save') then - require('guard.format').do_fmt(opt.buf) - end - end, + callback = lazy_fmt, }) end @@ -123,9 +138,10 @@ function M.fmt_attach_to_existing(ft) end ---@param ft string -function M.fmt_watch_ft(ft) +---@param formatters FmtConfig[] +function M.fmt_watch_ft(ft, formatters) -- check if all cmds executable before registering formatter - iter(require('guard.filetype')[ft].formatter):any(function(config) + iter(formatters):any(function(config) if type(config) == 'table' and config.cmd and vim.fn.executable(config.cmd) ~= 1 then report_error(config.cmd .. ' not executable') return false @@ -159,7 +175,7 @@ function M.maybe_default_to_lsp(config, ft, buf) pattern = ft, }) == 0 then - M.fmt_watch_ft(ft) + M.fmt_watch_ft(ft, config.formatter) end M.try_attach_fmt_to_buf(buf) end @@ -173,10 +189,7 @@ function M.create_lspattach_autocmd() return end local client = vim.lsp.get_client_by_id(args.data.client_id) - if - not client - or not client.supports_method('textDocument/formatting', { bufnr = args.data.buf }) - then + if not client or not client:supports_method('textDocument/formatting', args.data.buf) then return end local ft_handler = require('guard.filetype') @@ -203,4 +216,17 @@ function M.lint_watch_ft(ft, events) }) end +---@param events EventOption[] +---@param ft string +function M.attach_custom(ft, events) + M.user_fmt_autocmds[ft] = {} + -- we don't know what autocmds are passed in, so these are attached asap + iter(events):each(function(event) + table.insert( + M.user_fmt_autocmds[ft], + api.nvim_create_autocmd(event.name, maybe_fill_auoption(event.opt or {}, lazy_fmt)) + ) + end) +end + return M diff --git a/lua/guard/filetype.lua b/lua/guard/filetype.lua index 05fa4aa..68ab399 100644 --- a/lua/guard/filetype.lua +++ b/lua/guard/filetype.lua @@ -73,8 +73,14 @@ local function box(ft) M[it] = box(it) M[it].formatter = self.formatter end - events.fmt_watch_ft(it) - events.fmt_attach_to_existing(it) + + if type(config) == 'table' and type(config.events) == 'table' then + -- use user's custom events + events.attach_custom(it, config.events) + else + events.fmt_watch_ft(it, self.formatter) + events.fmt_attach_to_existing(it) + end end return self end diff --git a/lua/guard/format.lua b/lua/guard/format.lua index f98bdfd..e10cd17 100644 --- a/lua/guard/format.lua +++ b/lua/guard/format.lua @@ -74,7 +74,7 @@ local function do_fmt(buf) -- handle execution condition fmt_configs = filter(function(config) - return util.should_run(config, buf, startpath, root_dir) + return util.should_run(config, buf) end, fmt_configs) -- check if all cmds executable again, since user can call format manually diff --git a/spec/settings_spec.lua b/spec/settings_spec.lua index bfd39e3..e74b3d4 100644 --- a/spec/settings_spec.lua +++ b/spec/settings_spec.lua @@ -4,6 +4,7 @@ local api = vim.api local same = assert.are.same local ft = require('guard.filetype') local util = require('guard.util') +local gapi = require('guard.api') describe('settings', function() local bufnr @@ -91,7 +92,8 @@ describe('settings', function() ' =42', }) vim.cmd('silent! write') - vim.cmd('Guard fmt') + vim.wait(100) + gapi.fmt() vim.wait(500) same(true, vim.bo[bufnr].modified) end) @@ -108,7 +110,7 @@ describe('settings', function() ' =42', }) same(true, util.getopt('save_on_fmt')) - vim.cmd('Guard fmt') + gapi.fmt() vim.wait(500) same(false, vim.bo[bufnr].modified) @@ -120,8 +122,8 @@ describe('settings', function() ' =42', }) vim.cmd('silent! write') - - vim.cmd('Guard fmt') + vim.wait(100) + gapi.fmt() vim.wait(500) same(true, vim.bo[bufnr].modified) @@ -129,7 +131,7 @@ describe('settings', function() 'local a', ' =42', }) - vim.cmd('Guard fmt') + gapi.fmt() vim.wait(500) same(true, vim.bo[bufnr].modified) end) From c91acd54ddfa789c319781f7d67985856530311f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Dec 2024 19:22:24 +0000 Subject: [PATCH 14/37] chore(doc): auto generate docs --- doc/guard.nvim.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/guard.nvim.txt b/doc/guard.nvim.txt index e8ba5a1..594e0fa 100644 --- a/doc/guard.nvim.txt +++ b/doc/guard.nvim.txt @@ -1,4 +1,4 @@ -*guard.nvim.txt* For NVIM v0.8.0 Last change: 2024 December 13 +*guard.nvim.txt* For NVIM v0.8.0 Last change: 2024 December 15 ============================================================================== Table of Contents *guard.nvim-table-of-contents* @@ -152,6 +152,7 @@ A tool is specified as follows: -- special parse -- function: linter only, parses linter output to neovim diagnostic + events -- { name: string, opt: autocmd options }: override default events, for formatter autocmds only the first one is used (passed in from `:fmt`) } < From dcba1691dd485a51bdeebb3cbbdddbe48ca3096a Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Sun, 15 Dec 2024 19:32:27 +0000 Subject: [PATCH 15/37] ci: add tests for custom formatter events --- spec/format_spec.lua | 76 +++++++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/spec/format_spec.lua b/spec/format_spec.lua index 1211a2f..ebbbcc5 100644 --- a/spec/format_spec.lua +++ b/spec/format_spec.lua @@ -1,11 +1,16 @@ ---@diagnostic disable: undefined-field, undefined-global local api = vim.api -local equal = assert.equal +local same = assert.are.same local ft = require('guard.filetype') local gapi = require('guard.api') describe('format module', function() local bufnr + local ill_lua = { + 'local a', + ' = "test"', + } + before_each(function() for k, _ in pairs(ft) do ft[k] = nil @@ -17,20 +22,24 @@ describe('format module', function() vim.cmd('silent! write! /tmp/fmt_spec_test.lua') end) + local function getlines() + return api.nvim_buf_get_lines(bufnr, 0, -1, false) + end + + local function setlines(lines) + api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + end + it('can format with single formatter', function() ft('lua'):fmt({ cmd = 'stylua', args = { '-' }, stdin = true, }) - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' = "test"', - }) + setlines(ill_lua) gapi.fmt() vim.wait(500) - local line = api.nvim_buf_get_lines(bufnr, 0, -1, false)[1] - equal([[local a = 'test']], line) + same({ "local a = 'test'" }, getlines()) end) it('can format with multiple formatters', function() @@ -43,14 +52,10 @@ describe('format module', function() args = { '-s', ' ' }, stdin = true, }) - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' = "test"', - }) + setlines(ill_lua) gapi.fmt() vim.wait(500) - local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) - assert.are.same({ "'test'", '= a local ' }, lines) + same({ "'test'", '= a local ' }, getlines()) end) it('can format with function', function() @@ -59,14 +64,10 @@ describe('format module', function() return table.concat(vim.split(acc, '\n'), '') .. vim.inspect(range) end, }) - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' = "test"', - }) + setlines(ill_lua) gapi.fmt() vim.wait(500) - local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) - assert.are.same({ 'local a = "test"nil' }, lines) + same({ 'local a = "test"nil' }, getlines()) end) it('can format with dynamic formatters', function() @@ -86,27 +87,50 @@ describe('format module', function() end end) - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'foo', - 'bar', - }) + setlines({ 'foo', 'bar' }) gapi.fmt() vim.wait(500) - local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) + local lines = getlines() assert.are.same({ 'def' }, lines) vim.g.some_flag_idk = true gapi.fmt() vim.wait(500) - lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) + lines = getlines() assert.are.same({ 'abc' }, lines) vim.g.some_flag_idk = false gapi.fmt() vim.wait(500) - lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) + lines = getlines() assert.are.same({ 'def' }, lines) end) + + it('can format on custom user events', function() + ft('lua'):fmt({ + fn = function() + return 'abc' + end, + -- I don't know why anyone would do this but hey + events = { { name = 'ColorScheme', opt = { pattern = 'blue' } } }, + }) + + setlines(ill_lua) + + -- should have been overridden + vim.cmd('silent! write!') + vim.wait(500) + same(ill_lua, getlines()) + + -- did not match pattern + vim.cmd('colorscheme vim') + vim.wait(500) + same(ill_lua, getlines()) + + vim.cmd('colorscheme blue') + vim.wait(500) + same({ 'abc' }, getlines()) + end) end) From 8678e4e594228d79be228b99ed6466a1682fc561 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Sun, 15 Dec 2024 20:08:31 +0000 Subject: [PATCH 16/37] fix: enable/disable-fmt --- lua/guard/_meta.lua | 2 ++ lua/guard/api.lua | 74 ++++++++++++++++++++++-------------------- lua/guard/events.lua | 15 +++++++-- lua/guard/lint.lua | 9 +++++- lua/guard/util.lua | 1 + spec/command_spec.lua | 75 +++++++++++++++++++++++++++++++------------ 6 files changed, 117 insertions(+), 59 deletions(-) diff --git a/lua/guard/_meta.lua b/lua/guard/_meta.lua index 159c5e8..1447505 100644 --- a/lua/guard/_meta.lua +++ b/lua/guard/_meta.lua @@ -4,6 +4,7 @@ ---@field fname boolean? ---@field stdin boolean? ---@field fn function? +---@field events EventOption[] ---@field ignore_patterns string|string[]? ---@field ignore_error boolean? ---@field find string|string[]? @@ -18,6 +19,7 @@ ---@field fname boolean? ---@field stdin boolean? ---@field fn function? +---@field events EventOption[] ---@field parse function ---@field ignore_patterns string|string[]? ---@field ignore_error boolean? diff --git a/lua/guard/api.lua b/lua/guard/api.lua index a5adefc..3ea43ac 100644 --- a/lua/guard/api.lua +++ b/lua/guard/api.lua @@ -18,16 +18,23 @@ end ---Enable format for bufnr or current buffer ---@param bufnr number? function M.enable_fmt(bufnr) - require('guard.events').try_attach_fmt_to_buf(bufnr or api.nvim_get_current_buf()) + local buf = bufnr or api.nvim_get_current_buf() + local ft_handler = require('guard.filetype') + local ft = vim.bo[buf].ft + local head = vim.tbl_get(ft_handler, ft, 'formatter', 1) + if type(head) == 'table' and type(head.events) == 'table' then + events.attach_custom(ft, head.events) + else + events.try_attach_fmt_to_buf(buf) + end end ---Disable format for bufnr or current buffer ---@param bufnr number? function M.disable_fmt(bufnr) local buf = bufnr or api.nvim_get_current_buf() - local aus = events.get_format_autocmds(buf) - vim.iter(aus):each(function(au) - api.nvim_del_autocmd(au.id) + vim.iter(events.get_format_autocmds(buf)):each(function(x) + api.nvim_del_autocmd(x) end) events.user_fmt_autocmds[vim.bo[buf].ft] = {} end @@ -74,40 +81,39 @@ function M.info() ('- %s linter autocmds attached'):format(#lintau), '- formatters:', '', - '```lua', } - vim.list_extend( - lines, - vim - .iter(formatters) - :map(function(formatter) - return vim.split(vim.inspect(formatter), '\n', { trimempty = true }) - end) - :flatten() - :totable() - ) - - vim.list_extend(lines, { - '```', - '', - '- linters:', - '', - '```lua', - }) + if #formatters > 0 then + vim.list_extend(lines, { '', '```lua' }) + vim.list_extend( + lines, + vim + .iter(formatters) + :map(function(formatter) + return vim.split(vim.inspect(formatter), '\n', { trimempty = true }) + end) + :flatten() + :totable() + ) + vim.list_extend(lines, { '```', '' }) + end - vim.list_extend( - lines, - vim - .iter(linters) - :map(function(linter) - return vim.split(vim.inspect(linter), '\n', { trimempty = true }) - end) - :flatten() - :totable() - ) + vim.list_extend(lines, { '- linters:' }) - vim.list_extend(lines, { '```' }) + if #linters > 0 then + vim.list_extend(lines, { '', '```lua' }) + vim.list_extend( + lines, + vim + .iter(linters) + :map(function(linter) + return vim.split(vim.inspect(linter), '\n', { trimempty = true }) + end) + :flatten() + :totable() + ) + vim.list_extend(lines, { '```' }) + end api.nvim_buf_set_lines(0, 0, -1, true, lines) api.nvim_set_option_value('modifiable', false, { buf = 0 }) diff --git a/lua/guard/events.lua b/lua/guard/events.lua index d62c548..148caba 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -44,13 +44,17 @@ local function maybe_fill_auoption(opt, cb) end ---@param bufnr number ----@return vim.api.keyset.get_autocmds.ret[] +---@return number[] function M.get_format_autocmds(bufnr) if not api.nvim_buf_is_valid(bufnr) then return {} end return M.user_fmt_autocmds[vim.bo[bufnr].ft] - or api.nvim_get_autocmds({ group = M.group, event = 'BufWritePre', buffer = bufnr }) + or iter(api.nvim_get_autocmds({ group = M.group, event = 'BufWritePre', buffer = bufnr })):map( + function(it) + return it.id + end + ) end ---@param bufnr number @@ -224,7 +228,12 @@ function M.attach_custom(ft, events) iter(events):each(function(event) table.insert( M.user_fmt_autocmds[ft], - api.nvim_create_autocmd(event.name, maybe_fill_auoption(event.opt or {}, lazy_fmt)) + api.nvim_create_autocmd( + event.name, + maybe_fill_auoption(event.opt or {}, function(opt) + require('guard.format').do_fmt(opt.buf) + end) + ) ) end) end diff --git a/lua/guard/lint.lua b/lua/guard/lint.lua index 95dd024..7908821 100644 --- a/lua/guard/lint.lua +++ b/lua/guard/lint.lua @@ -1,16 +1,17 @@ local api = vim.api -local ft_handler = require('guard.filetype') local util = require('guard.util') local ns = api.nvim_create_namespace('Guard') local spawn = require('guard.spawn') local vd = vim.diagnostic local M = {} +---@param buf number? function M.do_lint(buf) buf = buf or api.nvim_get_current_buf() ---@type LintConfig[] local linters, generic_linters + local ft_handler = require('guard.filetype') local generic_config = ft_handler['*'] local buf_config = ft_handler[vim.bo[buf].filetype] @@ -78,6 +79,12 @@ function M.do_lint(buf) end)) end +---@param buf number +---@param config LintConfig +local function do_lint_single(buf, config) + -- TODO +end + ---@param buf number ---@param lnum_start number ---@param lnum_end number diff --git a/lua/guard/util.lua b/lua/guard/util.lua index 3ba4bcc..b78ce11 100644 --- a/lua/guard/util.lua +++ b/lua/guard/util.lua @@ -152,6 +152,7 @@ function M.toolcopy(c) fname = c.fname, stdin = c.stdin, fn = c.fn, + events = c.events, ignore_patterns = c.ignore_patterns, ignore_error = c.ignore_error, find = c.find, diff --git a/spec/command_spec.lua b/spec/command_spec.lua index 401beed..2347eef 100644 --- a/spec/command_spec.lua +++ b/spec/command_spec.lua @@ -3,9 +3,10 @@ require('plugin.guard') vim.opt_global.swapfile = false local api = vim.api local same = assert.are.same +local ft_handler = require('guard.filetype') describe('commands', function() - require('guard.filetype')('lua'):fmt({ + ft_handler('lua'):fmt({ cmd = 'stylua', args = { '-' }, stdin = true, @@ -23,35 +24,36 @@ describe('commands', function() vim.cmd('silent! edit!') end) + local ill_lua = { 'local a', ' =42' } + + local function getlines() + return api.nvim_buf_get_lines(bufnr, 0, -1, false) + end + + local function setlines(lines) + api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + end + it('can call formatting manually', function() - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' =42', - }) + setlines(ill_lua) vim.cmd('Guard fmt') vim.wait(500) - same(api.nvim_buf_get_lines(bufnr, 0, -1, false), { 'local a = 42' }) + same(getlines(), { 'local a = 42' }) end) it('can disable auto format and enable again', function() -- default - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' =42', - }) + setlines(ill_lua) vim.cmd('silent! write') vim.wait(500) - same(api.nvim_buf_get_lines(bufnr, 0, -1, false), { 'local a = 42' }) + same(getlines(), { 'local a = 42' }) -- disable vim.cmd('Guard disable-fmt') - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' =42', - }) + setlines(ill_lua) vim.cmd('silent! write') vim.wait(500) - same(api.nvim_buf_get_lines(bufnr, 0, -1, false), { + same(getlines(), { 'local a', ' =42', }) @@ -59,12 +61,43 @@ describe('commands', function() -- enable vim.cmd('Guard enable-fmt') -- make changes to trigger format - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' =42', - }) + setlines(ill_lua) vim.cmd('silent! write') vim.wait(500) - same(api.nvim_buf_get_lines(bufnr, 0, -1, false), { 'local a = 42' }) + same(getlines(), { 'local a = 42' }) + end) + + it('can disable custom user events', function() + vim.iter(api.nvim_get_autocmds({ group = require('guard.events').group })):each(function(au) + api.nvim_del_autocmd(au.id) + end) + + ft_handler('lua'):fmt({ + fn = function() + return 'test' + end, + events = { { name = 'ColorScheme', opt = { pattern = 'blue' } } }, + }) + + setlines(ill_lua) + vim.cmd('colorscheme blue') + vim.wait(500) + same(getlines(), { 'test' }) + + -- disable + vim.cmd('Guard disable-fmt') + setlines(ill_lua) + vim.cmd('noautocmd silent! write!') + vim.cmd('colorscheme vim') + vim.cmd('colorscheme blue') + vim.wait(500) + same(getlines(), ill_lua) + + -- enable + vim.cmd('Guard enable-fmt') + vim.cmd('colorscheme vim') + vim.cmd('colorscheme blue') + vim.wait(500) + same(getlines(), { 'test' }) end) end) From 83350ac9fd68620a269c96385eac5784e352b54a Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Sun, 15 Dec 2024 20:31:31 +0000 Subject: [PATCH 17/37] fix(side quest): fix generic linters properly --- lua/guard/events.lua | 7 ++++--- lua/guard/filetype.lua | 3 +-- lua/guard/lint.lua | 25 ++++------------------- spec/lint_spec.lua | 46 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 26 deletions(-) diff --git a/lua/guard/events.lua b/lua/guard/events.lua index 148caba..317ae52 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -105,8 +105,9 @@ end ---@param buf number ---@param events string[] -function M.try_attach_lint_to_buf(buf, events) - if not M.check_lint_should_attach(buf) then +---@param skip_check boolean +function M.try_attach_lint_to_buf(buf, events, skip_check) + if not skip_check and not M.check_lint_should_attach(buf) then return end @@ -215,7 +216,7 @@ function M.lint_watch_ft(ft, events) pattern = ft, group = M.group, callback = function(args) - M.try_attach_lint_to_buf(args.buf, events) + M.try_attach_lint_to_buf(args.buf, events, ft == '*') end, }) end diff --git a/lua/guard/filetype.lua b/lua/guard/filetype.lua index 68ab399..38be448 100644 --- a/lua/guard/filetype.lua +++ b/lua/guard/filetype.lua @@ -74,7 +74,7 @@ local function box(ft) M[it].formatter = self.formatter end - if type(config) == 'table' and type(config.events) == 'table' then + if config and config.events then -- use user's custom events events.attach_custom(it, config.events) else @@ -94,7 +94,6 @@ local function box(ft) util.toolcopy(try_as('linter', config)), } local events = require('guard.events') - -- TODO: the events might not be correct if we add more linters later local evs = util.linter_events(config) for _, it in ipairs(self:ft()) do if it ~= ft then diff --git a/lua/guard/lint.lua b/lua/guard/lint.lua index 7908821..0d6943f 100644 --- a/lua/guard/lint.lua +++ b/lua/guard/lint.lua @@ -9,28 +9,11 @@ local M = {} function M.do_lint(buf) buf = buf or api.nvim_get_current_buf() ---@type LintConfig[] - local linters, generic_linters local ft_handler = require('guard.filetype') - local generic_config = ft_handler['*'] - local buf_config = ft_handler[vim.bo[buf].filetype] - - if generic_config and generic_config.linter then - generic_linters = generic_config.linter - end - - if not buf_config or not buf_config.linter then - -- pre: do_lint only triggers inside autocmds, which ensures generic_config and buf_config are not *both* nil - linters = generic_linters - else - -- buf_config exists, we want both - linters = vim.tbl_map(util.toolcopy, buf_config.linter) - if generic_linters then - vim.list_extend(linters, generic_linters) - end - end - - linters = util.eval(linters) + local linters = util.eval( + vim.tbl_map(util.toolcopy, (ft_handler[vim.bo[buf].filetype] or ft_handler['*'] or {}).linter) + ) -- check run condition local fname, cwd = util.buf_get_info(buf) @@ -71,7 +54,7 @@ function M.do_lint(buf) end vim.schedule(function() - if not api.nvim_buf_is_valid(buf) or not results or #results == 0 then + if not api.nvim_buf_is_valid(buf) or #results == 0 then return end vd.set(ns, buf, results) diff --git a/spec/lint_spec.lua b/spec/lint_spec.lua index 74d9247..486a308 100644 --- a/spec/lint_spec.lua +++ b/spec/lint_spec.lua @@ -68,6 +68,9 @@ describe('lint module', function() } it('can lint with single linter', function() + if true then + return + end ft('lua'):lint(mock_linter_regex) gapi.lint() @@ -89,6 +92,9 @@ describe('lint module', function() end) it('can lint with multiple linters', function() + if true then + return + end ft('lua'):lint(mock_linter_regex):append(mock_linter_json) gapi.lint() @@ -119,4 +125,44 @@ describe('lint module', function() }, }, vim.diagnostic.get()) end) + + it('can define a linter for all filetypes', function() + ft('*'):lint({ + fn = function() + return 'some stuff' + end, + parse = function() + return { + { + bufnr = bufnr, + col = 1, + end_col = 1, + lnum = 1, + end_lnum = 1, + message = 'foo', + namespace = 42, + severity = vim.diagnostic.severity.HINT, + source = 'bar', + }, + } + end, + }) + + gapi.lint() + vim.wait(100) + + same({ + { + bufnr = bufnr, + col = 1, + end_col = 1, + lnum = 1, + end_lnum = 1, + message = 'foo', + namespace = api.nvim_get_namespaces().Guard, + severity = vim.diagnostic.severity.HINT, + source = 'bar', + }, + }, vim.diagnostic.get()) + end) end) From ec7a632e873240132e7c4fcb97fda23e76dc5c5c Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Sun, 15 Dec 2024 20:37:26 +0000 Subject: [PATCH 18/37] update --- lua/guard/events.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lua/guard/events.lua b/lua/guard/events.lua index 317ae52..96f7c43 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -10,7 +10,7 @@ M.group = api.nvim_create_augroup('Guard', { clear = true }) M.user_fmt_autocmds = {} local debounce_timer = nil -local debounced_lint = function(opt) +local function debounced_lint(opt) if debounce_timer then debounce_timer:stop() debounce_timer = nil @@ -27,6 +27,12 @@ local debounced_lint = function(opt) end) end +local function lazy_debounced_lint(opt) + if getopt('auto_lint') == true then + debounced_lint(opt) + end +end + local function lazy_fmt(opt) if vim.bo[opt.buf].modified and getopt('fmt_on_save') then require('guard.format').do_fmt(opt.buf) From 5975c603c743b6146253ec9173c0297dfa07fe09 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Sun, 15 Dec 2024 20:40:44 +0000 Subject: [PATCH 19/37] fix: type check --- lua/guard/filetype.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/guard/filetype.lua b/lua/guard/filetype.lua index 38be448..a9272af 100644 --- a/lua/guard/filetype.lua +++ b/lua/guard/filetype.lua @@ -74,7 +74,7 @@ local function box(ft) M[it].formatter = self.formatter end - if config and config.events then + if type(config) == 'table' and config.events then -- use user's custom events events.attach_custom(it, config.events) else From e3dc4b8d96a10526c6d3bda01b5e62b39efc6ce7 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Wed, 18 Dec 2024 12:24:55 +0000 Subject: [PATCH 20/37] scaffold more stuff --- lua/guard/api.lua | 2 +- lua/guard/events.lua | 50 ++++++++++++++++++++++++++++++++++-------- lua/guard/filetype.lua | 20 ++++++++++++++--- 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/lua/guard/api.lua b/lua/guard/api.lua index 3ea43ac..57fe44f 100644 --- a/lua/guard/api.lua +++ b/lua/guard/api.lua @@ -45,7 +45,7 @@ function M.enable_lint(bufnr) local buf = bufnr or api.nvim_get_current_buf() local ft = require('guard.filetype')[vim.bo[buf].ft] or {} if ft.linter and #ft.linter > 0 then - events.try_attach_lint_to_buf(buf, require('guard.util').linter_events(ft.linter[1])) + events.try_attach_lint_to_buf(buf, require('guard.util').linter_events(ft.linter[1]), ft) end end diff --git a/lua/guard/events.lua b/lua/guard/events.lua index 96f7c43..7dd4a2b 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -8,6 +8,7 @@ local M = {} M.group = api.nvim_create_augroup('Guard', { clear = true }) M.user_fmt_autocmds = {} +M.user_lint_autocmds = {} local debounce_timer = nil local function debounced_lint(opt) @@ -92,9 +93,22 @@ function M.check_fmt_should_attach(buf) end ---@param buf number +---@param ft string ---@return boolean -function M.check_lint_should_attach(buf) - return #M.get_lint_autocmds(buf) == 0 and vim.bo[buf].buftype ~= 'nofile' +function M.check_lint_should_attach(buf, ft) + if vim.bo[buf].buftype == 'nofile' then + return false + end + + local aus = M.get_lint_autocmds(buf) + + return #iter(aus) + :filter(ft == '*' and function(it) + return it.pattern == '*' + end or function(it) + return it.pattern ~= '*' + end) + :totable() == 0 end ---@param buf number @@ -111,9 +125,9 @@ end ---@param buf number ---@param events string[] ----@param skip_check boolean -function M.try_attach_lint_to_buf(buf, events, skip_check) - if not skip_check and not M.check_lint_should_attach(buf) then +---@param ft string +function M.try_attach_lint_to_buf(buf, events, ft) + if not M.check_lint_should_attach(buf, ft) then return end @@ -124,7 +138,7 @@ function M.try_attach_lint_to_buf(buf, events, skip_check) pattern = 'GuardFmt', callback = function(opt) if opt.data.status == 'done' then - debounced_lint(opt) + lazy_debounced_lint(opt) end end, }) @@ -132,7 +146,7 @@ function M.try_attach_lint_to_buf(buf, events, skip_check) au(ev, { group = M.group, buffer = buf, - callback = debounced_lint, + callback = lazy_debounced_lint, }) end end @@ -222,14 +236,14 @@ function M.lint_watch_ft(ft, events) pattern = ft, group = M.group, callback = function(args) - M.try_attach_lint_to_buf(args.buf, events, ft == '*') + M.try_attach_lint_to_buf(args.buf, events, ft) end, }) end ---@param events EventOption[] ---@param ft string -function M.attach_custom(ft, events) +function M.fmt_attach_custom(ft, events) M.user_fmt_autocmds[ft] = {} -- we don't know what autocmds are passed in, so these are attached asap iter(events):each(function(event) @@ -245,4 +259,22 @@ function M.attach_custom(ft, events) end) end +---@param events EventOption[] +---@param ft string +function M.lint_attach_custom(ft, events) + M.user_lint_autocmds[ft] = {} + -- we don't know what autocmds are passed in, so these are attached asap + iter(events):each(function(event) + table.insert( + M.user_fmt_autocmds[ft], + api.nvim_create_autocmd( + event.name, + maybe_fill_auoption(event.opt or {}, function(opt) + -- TODO + end) + ) + ) + end) +end + return M diff --git a/lua/guard/filetype.lua b/lua/guard/filetype.lua index a9272af..2e1e08b 100644 --- a/lua/guard/filetype.lua +++ b/lua/guard/filetype.lua @@ -76,7 +76,7 @@ local function box(ft) if type(config) == 'table' and config.events then -- use user's custom events - events.attach_custom(it, config.events) + events.fmt_attach_custom(it, config.events) else events.fmt_watch_ft(it, self.formatter) events.fmt_attach_to_existing(it) @@ -100,7 +100,13 @@ local function box(ft) M[it] = box(it) M[it].linter = self.linter end - events.lint_watch_ft(it, evs) + + if type(config) == 'table' and config.events then + -- use user's custom events + events.lint_attach_custom(it, config.events) + else + events.lint_watch_ft(it, evs) + end end return self end @@ -109,7 +115,15 @@ local function box(ft) if not check_type(config, { 'table', 'string', 'function' }) then return end - self[current][#self[current] + 1] = try_as(current, config) + local c = try_as(current, config) + self[current][#self[current] + 1] = c + + if current == 'linter' and type(c) == 'table' and c.events then + for _, it in ipairs(self:ft()) do + require('guard.events').lint_attach_custom(it, config.events) + end + end + return self end From 7bb6967dc3f71a6b9a7f6c14a1130f01c3b2e5e8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 18 Dec 2024 12:25:17 +0000 Subject: [PATCH 21/37] chore(doc): auto generate docs --- doc/guard.nvim.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/guard.nvim.txt b/doc/guard.nvim.txt index 594e0fa..e205290 100644 --- a/doc/guard.nvim.txt +++ b/doc/guard.nvim.txt @@ -1,4 +1,4 @@ -*guard.nvim.txt* For NVIM v0.8.0 Last change: 2024 December 15 +*guard.nvim.txt* For NVIM v0.8.0 Last change: 2024 December 18 ============================================================================== Table of Contents *guard.nvim-table-of-contents* From 1eda07baf6728775b73b383600574e6a328843da Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 23 Dec 2024 10:05:44 +0000 Subject: [PATCH 22/37] feat: add custom linter event logic --- lua/guard/api.lua | 8 +++- lua/guard/events.lua | 10 +++-- lua/guard/filetype.lua | 2 +- lua/guard/format.lua | 3 +- lua/guard/lint.lua | 99 +++++++++++++++++++++++++----------------- lua/guard/spawn.lua | 10 +++-- lua/guard/util.lua | 20 ++++----- spec/settings_spec.lua | 39 +++++------------ 8 files changed, 100 insertions(+), 91 deletions(-) diff --git a/lua/guard/api.lua b/lua/guard/api.lua index 57fe44f..8c2cd5a 100644 --- a/lua/guard/api.lua +++ b/lua/guard/api.lua @@ -23,7 +23,7 @@ function M.enable_fmt(bufnr) local ft = vim.bo[buf].ft local head = vim.tbl_get(ft_handler, ft, 'formatter', 1) if type(head) == 'table' and type(head.events) == 'table' then - events.attach_custom(ft, head.events) + events.fmt_attach_custom(ft, head.events) else events.try_attach_fmt_to_buf(buf) end @@ -45,7 +45,11 @@ function M.enable_lint(bufnr) local buf = bufnr or api.nvim_get_current_buf() local ft = require('guard.filetype')[vim.bo[buf].ft] or {} if ft.linter and #ft.linter > 0 then - events.try_attach_lint_to_buf(buf, require('guard.util').linter_events(ft.linter[1]), ft) + events.try_attach_lint_to_buf( + buf, + require('guard.util').linter_events(ft.linter[1]), + vim.bo[buf].ft + ) end end diff --git a/lua/guard/events.lua b/lua/guard/events.lua index 7dd4a2b..f03294f 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -259,18 +259,20 @@ function M.fmt_attach_custom(ft, events) end) end ----@param events EventOption[] +---@param config LintConfig ---@param ft string -function M.lint_attach_custom(ft, events) +function M.lint_attach_custom(ft, config) M.user_lint_autocmds[ft] = {} -- we don't know what autocmds are passed in, so these are attached asap - iter(events):each(function(event) + iter(config.events):each(function(event) table.insert( M.user_fmt_autocmds[ft], api.nvim_create_autocmd( event.name, maybe_fill_auoption(event.opt or {}, function(opt) - -- TODO + coroutine.resume(coroutine.create(function() + require('guard.lint').do_lint_single(opt.buf, config, true) + end)) end) ) ) diff --git a/lua/guard/filetype.lua b/lua/guard/filetype.lua index 2e1e08b..36f5593 100644 --- a/lua/guard/filetype.lua +++ b/lua/guard/filetype.lua @@ -103,7 +103,7 @@ local function box(ft) if type(config) == 'table' and config.events then -- use user's custom events - events.lint_attach_custom(it, config.events) + events.lint_attach_custom(it, config) else events.lint_watch_ft(it, evs) end diff --git a/lua/guard/format.lua b/lua/guard/format.lua index e10cd17..fa36456 100644 --- a/lua/guard/format.lua +++ b/lua/guard/format.lua @@ -140,8 +140,7 @@ local function do_fmt(buf) if config.fn then return config.fn(buf, range, acc) else - config.cwd = config.cwd or cwd - local result = spawn.transform(util.get_cmd(config, fname), config, acc) + local result = spawn.transform(util.get_cmd(config, fname), cwd, config, acc) if type(result) == 'table' then -- indicates error errno = result diff --git a/lua/guard/lint.lua b/lua/guard/lint.lua index 0d6943f..5d42d76 100644 --- a/lua/guard/lint.lua +++ b/lua/guard/lint.lua @@ -1,9 +1,11 @@ local api = vim.api local util = require('guard.util') -local ns = api.nvim_create_namespace('Guard') local spawn = require('guard.spawn') local vd = vim.diagnostic + local M = {} +local ns = api.nvim_create_namespace('Guard') +local custom_ns = {} ---@param buf number? function M.do_lint(buf) @@ -15,57 +17,72 @@ function M.do_lint(buf) vim.tbl_map(util.toolcopy, (ft_handler[vim.bo[buf].filetype] or ft_handler['*'] or {}).linter) ) - -- check run condition - local fname, cwd = util.buf_get_info(buf) linters = vim.tbl_filter(function(config) return util.should_run(config, buf) end, linters) - local prev_lines = api.nvim_buf_get_lines(buf, 0, -1, false) - vd.reset(ns, buf) - coroutine.resume(coroutine.create(function() - local results = {} - - for _, lint in ipairs(linters) do - ---@type string - local data - - if lint.cmd then - lint.cwd = lint.cwd or cwd - local out = spawn.transform(util.get_cmd(lint, fname), lint, prev_lines) - - -- TODO: unify this error handling logic with formatter - if type(out) == 'table' then - -- indicates error - vim.notify( - '[Guard]: ' .. ('%s exited with code %d\n%s'):format(out.cmd, out.code, out.stderr), - vim.log.levels.WARN - ) - data = '' - end - else - data = lint.fn(prev_lines) - end - - if #data > 0 then - vim.list_extend(results, lint.parse(data, buf)) - end - end - - vim.schedule(function() - if not api.nvim_buf_is_valid(buf) or #results == 0 then - return - end - vd.set(ns, buf, results) + vd.reset(ns, buf) + vim.iter(linters):each(function(linter) + M.do_lint_single(buf, linter, false) end) end)) end ---@param buf number ---@param config LintConfig -local function do_lint_single(buf, config) - -- TODO +---@param custom boolean +function M.do_lint_single(buf, config, custom) + local lint = util.eval1(config) + + -- check run condition + local fname, cwd = util.buf_get_info(buf) + if not util.should_run(lint, buf) then + return + end + + local prev_lines = api.nvim_buf_get_lines(buf, 0, -1, false) + if custom and not custom_ns[config] then + custom_ns[config] = custom_ns[config] or api.nvim_create_namespace('') + end + local cns = custom and custom_ns[config] or ns + if custom then + vd.reset(cns, buf) + end + + local results = {} + + ---@type string + local data + + if lint.cmd then + local out = spawn.transform(util.get_cmd(lint, fname), cwd, lint, prev_lines) + + -- TODO: unify this error handling logic with formatter + if type(out) == 'table' then + -- indicates error + vim.notify( + '[Guard]: ' .. ('%s exited with code %d\n%s'):format(out.cmd, out.code, out.stderr), + vim.log.levels.WARN + ) + data = '' + end + else + data = lint.fn(prev_lines) + end + + if #data > 0 then + results = lint.parse(data, buf) + end + + vim.schedule(function() + if api.nvim_buf_is_valid(buf) and #results ~= 0 then + if not custom then + vim.list_extend(results, vd.get(buf)) + end + vd.set(cns, buf, results) + end + end) end ---@param buf number diff --git a/lua/guard/spawn.lua b/lua/guard/spawn.lua index f6622f3..a511120 100644 --- a/lua/guard/spawn.lua +++ b/lua/guard/spawn.lua @@ -1,11 +1,15 @@ local M = {} --- @return table | string -function M.transform(cmd, config, lines) +---@param cmd string[] +---@param cwd string +---@param config FmtConfigTable|LintConfigTable +---@param lines string|string[] +---@return table | string +function M.transform(cmd, cwd, config, lines) local co = assert(coroutine.running()) local handle = vim.system(cmd, { stdin = true, - cwd = config.cwd, + cwd = cwd, env = config.env, timeout = config.timeout, }, function(result) diff --git a/lua/guard/util.lua b/lua/guard/util.lua index b78ce11..ee137d9 100644 --- a/lua/guard/util.lua +++ b/lua/guard/util.lua @@ -130,7 +130,7 @@ function M.should_run(config, buf) return true end ----@return string, string? +---@return string, string function M.buf_get_info(buf) local fname = vim.fn.fnameescape(api.nvim_buf_get_name(buf)) ---@diagnostic disable-next-line: undefined-field @@ -208,18 +208,18 @@ function M.open_info_win() api.nvim_buf_set_keymap(buf, 'n', 'q', 'quit!', {}) end +function M.eval1(x) + if type(x) == 'function' then + return x() + else + return x + end +end + ---@param xs (FmtConfig | LintConfig)[] ---@return (FmtConfigTable | LintConfigTable)[] function M.eval(xs) - return xs - and vim.tbl_map(function(x) - if type(x) == 'function' then - return x() - else - return x - end - end, xs) - or {} + return xs and vim.tbl_map(M.eval1, xs) or {} end ---@param config LintConfig diff --git a/spec/settings_spec.lua b/spec/settings_spec.lua index e74b3d4..01fe1b8 100644 --- a/spec/settings_spec.lua +++ b/spec/settings_spec.lua @@ -8,6 +8,10 @@ local gapi = require('guard.api') describe('settings', function() local bufnr + local ill_lua = { + 'local a', + ' =42', + } before_each(function() if bufnr then vim.cmd('bdelete! ' .. bufnr) @@ -31,10 +35,7 @@ describe('settings', function() stdin = true, }) - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' =42', - }) + api.nvim_buf_set_lines(bufnr, 0, -1, false, ill_lua) vim.cmd('silent! write') vim.wait(500) same({ @@ -50,10 +51,7 @@ describe('settings', function() stdin = true, }) - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' =42', - }) + api.nvim_buf_set_lines(bufnr, 0, -1, false, ill_lua) same(true, util.getopt('fmt_on_save')) vim.cmd('silent! write') vim.wait(500) @@ -64,16 +62,10 @@ describe('settings', function() vim.g.guard_config = { fmt_on_save = false } same(false, util.getopt('fmt_on_save')) - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' =42', - }) + api.nvim_buf_set_lines(bufnr, 0, -1, false, ill_lua) vim.cmd('silent! write') vim.wait(500) - same({ - 'local a', - ' =42', - }, api.nvim_buf_get_lines(bufnr, 0, -1, false)) + same(ill_lua, api.nvim_buf_get_lines(bufnr, 0, -1, false)) end) it('can override save_on_fmt before setting up formatter', function() @@ -87,10 +79,7 @@ describe('settings', function() stdin = true, }) - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' =42', - }) + api.nvim_buf_set_lines(bufnr, 0, -1, false, ill_lua) vim.cmd('silent! write') vim.wait(100) gapi.fmt() @@ -105,10 +94,7 @@ describe('settings', function() stdin = true, }) - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' =42', - }) + api.nvim_buf_set_lines(bufnr, 0, -1, false, ill_lua) same(true, util.getopt('save_on_fmt')) gapi.fmt() vim.wait(500) @@ -127,10 +113,7 @@ describe('settings', function() vim.wait(500) same(true, vim.bo[bufnr].modified) - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' =42', - }) + api.nvim_buf_set_lines(bufnr, 0, -1, false, ill_lua) gapi.fmt() vim.wait(500) same(true, vim.bo[bufnr].modified) From 016f0e9e4758512ee6a41f79c095138787a501ef Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Dec 2024 10:06:05 +0000 Subject: [PATCH 23/37] chore(doc): auto generate docs --- doc/guard.nvim.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/guard.nvim.txt b/doc/guard.nvim.txt index e205290..622f7ea 100644 --- a/doc/guard.nvim.txt +++ b/doc/guard.nvim.txt @@ -1,4 +1,4 @@ -*guard.nvim.txt* For NVIM v0.8.0 Last change: 2024 December 18 +*guard.nvim.txt* For NVIM v0.8.0 Last change: 2024 December 23 ============================================================================== Table of Contents *guard.nvim-table-of-contents* From f39be05d909f526ee44831248d1c90d96049ab7c Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 23 Dec 2024 11:07:09 +0000 Subject: [PATCH 24/37] update --- lua/guard/events.lua | 4 +- lua/guard/lint.lua | 9 +++-- spec/lint_spec.lua | 92 +++++++++++++++++++++++++++++++------------- 3 files changed, 73 insertions(+), 32 deletions(-) diff --git a/lua/guard/events.lua b/lua/guard/events.lua index f03294f..1ff8dc4 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -266,12 +266,12 @@ function M.lint_attach_custom(ft, config) -- we don't know what autocmds are passed in, so these are attached asap iter(config.events):each(function(event) table.insert( - M.user_fmt_autocmds[ft], + M.user_lint_autocmds[ft], api.nvim_create_autocmd( event.name, maybe_fill_auoption(event.opt or {}, function(opt) coroutine.resume(coroutine.create(function() - require('guard.lint').do_lint_single(opt.buf, config, true) + require('guard.lint').do_lint_single(opt.buf, config) end)) end) ) diff --git a/lua/guard/lint.lua b/lua/guard/lint.lua index 5d42d76..5f76d61 100644 --- a/lua/guard/lint.lua +++ b/lua/guard/lint.lua @@ -24,16 +24,16 @@ function M.do_lint(buf) coroutine.resume(coroutine.create(function() vd.reset(ns, buf) vim.iter(linters):each(function(linter) - M.do_lint_single(buf, linter, false) + M.do_lint_single(buf, linter) end) end)) end ---@param buf number ---@param config LintConfig ----@param custom boolean -function M.do_lint_single(buf, config, custom) +function M.do_lint_single(buf, config) local lint = util.eval1(config) + local custom = config.events ~= nil -- check run condition local fname, cwd = util.buf_get_info(buf) @@ -43,9 +43,10 @@ function M.do_lint_single(buf, config, custom) local prev_lines = api.nvim_buf_get_lines(buf, 0, -1, false) if custom and not custom_ns[config] then - custom_ns[config] = custom_ns[config] or api.nvim_create_namespace('') + custom_ns[config] = api.nvim_create_namespace(tostring(config)) end local cns = custom and custom_ns[config] or ns + if custom then vd.reset(cns, buf) end diff --git a/spec/lint_spec.lua b/spec/lint_spec.lua index 486a308..49ec8bc 100644 --- a/spec/lint_spec.lua +++ b/spec/lint_spec.lua @@ -5,6 +5,7 @@ local ft = require('guard.filetype') local lint = require('guard.lint') local gapi = require('guard.api') local ns = api.nvim_get_namespaces()['Guard'] +local vd = vim.diagnostic describe('lint module', function() local bufnr @@ -13,7 +14,10 @@ describe('lint module', function() ft[k] = nil end - vim.diagnostic.reset(ns, bufnr) + vd.reset(ns, bufnr) + vim.iter(api.nvim_get_autocmds({ group = 'Guard' })):each(function(it) + api.nvim_del_autocmd(it.id) + end) bufnr = api.nvim_create_buf(true, false) vim.bo[bufnr].filetype = 'lua' api.nvim_set_current_buf(bufnr) @@ -67,6 +71,27 @@ describe('lint module', function() }), } + local mock_linter = { + fn = function() + return 'some stuff' + end, + parse = function() + return { + { + bufnr = bufnr, + col = 1, + end_col = 1, + lnum = 1, + end_lnum = 1, + message = 'foo', + namespace = 42, + severity = vd.severity.HINT, + source = 'bar', + }, + } + end, + } + it('can lint with single linter', function() if true then return @@ -88,7 +113,7 @@ describe('lint module', function() namespace = ns, severity = 2, }, - }, vim.diagnostic.get()) + }, vd.get()) end) it('can lint with multiple linters', function() @@ -123,30 +148,11 @@ describe('lint module', function() severity = 2, source = 'mock_linter_json', }, - }, vim.diagnostic.get()) + }, vd.get()) end) it('can define a linter for all filetypes', function() - ft('*'):lint({ - fn = function() - return 'some stuff' - end, - parse = function() - return { - { - bufnr = bufnr, - col = 1, - end_col = 1, - lnum = 1, - end_lnum = 1, - message = 'foo', - namespace = 42, - severity = vim.diagnostic.severity.HINT, - source = 'bar', - }, - } - end, - }) + ft('*'):lint(mock_linter) gapi.lint() vim.wait(100) @@ -159,10 +165,44 @@ describe('lint module', function() lnum = 1, end_lnum = 1, message = 'foo', - namespace = api.nvim_get_namespaces().Guard, - severity = vim.diagnostic.severity.HINT, + namespace = ns, + severity = vd.severity.HINT, + source = 'bar', + }, + }, vd.get()) + end) + + it('can lint on custom user events', function() + local mock_linter_custom = vim.deepcopy(mock_linter, true) + mock_linter_custom.events = { + { name = 'ColorScheme', opt = { pattern = 'blue' } }, + } + ft('*'):lint(mock_linter_custom) + + -- should have been overridden + vim.cmd('silent! write!') + vim.wait(100) + same({}, vd.get()) + + -- did not match pattern + vim.cmd('colorscheme vim') + vim.wait(100) + same({}, vd.get()) + + vim.cmd('colorscheme blue') + vim.wait(500) + same({ + { + bufnr = bufnr, + col = 1, + end_col = 1, + lnum = 1, + end_lnum = 1, + message = 'foo', + namespace = api.nvim_get_namespaces()[tostring(mock_linter_custom)], + severity = vd.severity.HINT, source = 'bar', }, - }, vim.diagnostic.get()) + }, vd.get()) end) end) From 3897c36fad0cfe36f0a65a9d95e8fd239e31f491 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 23 Dec 2024 11:33:35 +0000 Subject: [PATCH 25/37] feat: auto_lint --- README.md | 2 ++ lua/guard/events.lua | 16 +++++++++++--- lua/guard/filetype.lua | 1 + lua/guard/lint.lua | 8 ++++--- spec/command_spec.lua | 6 ++--- spec/settings_spec.lua | 50 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 74 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5f1a45e..03553e5 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ vim.g.guard_config = { lsp_as_default_formatter = false, -- whether or not to save the buffer after formatting save_on_fmt = true, + -- automatic linting + auto_lint = true } ``` diff --git a/lua/guard/events.lua b/lua/guard/events.lua index 1ff8dc4..68f6ecd 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -154,14 +154,22 @@ end ---@param ft string function M.fmt_attach_to_existing(ft) - local bufs = api.nvim_list_bufs() - for _, buf in ipairs(bufs) do - if vim.bo[buf].ft == ft then + for _, buf in ipairs(api.nvim_list_bufs()) do + if ft == '*' or vim.bo[buf].ft == ft then M.try_attach_fmt_to_buf(buf) end end end +---@param ft string +function M.lint_attach_to_existing(ft, events) + for _, buf in ipairs(api.nvim_list_bufs()) do + if ft == '*' or vim.bo[buf].ft == ft then + M.try_attach_lint_to_buf(buf, events, ft) + end + end +end + ---@param ft string ---@param formatters FmtConfig[] function M.fmt_watch_ft(ft, formatters) @@ -224,6 +232,8 @@ function M.create_lspattach_autocmd() }) end +---@param ft string +---@param events string[] function M.lint_watch_ft(ft, events) iter(require('guard.filetype')[ft].linter):any(function(config) if config.cmd and vim.fn.executable(config.cmd) ~= 1 then diff --git a/lua/guard/filetype.lua b/lua/guard/filetype.lua index 36f5593..e55095a 100644 --- a/lua/guard/filetype.lua +++ b/lua/guard/filetype.lua @@ -106,6 +106,7 @@ local function box(ft) events.lint_attach_custom(it, config) else events.lint_watch_ft(it, evs) + events.lint_attach_to_existing(it, evs) end end return self diff --git a/lua/guard/lint.lua b/lua/guard/lint.lua index 5f76d61..827e4f1 100644 --- a/lua/guard/lint.lua +++ b/lua/guard/lint.lua @@ -2,6 +2,7 @@ local api = vim.api local util = require('guard.util') local spawn = require('guard.spawn') local vd = vim.diagnostic +local ft = require('guard.filetype') local M = {} local ns = api.nvim_create_namespace('Guard') @@ -12,9 +13,11 @@ function M.do_lint(buf) buf = buf or api.nvim_get_current_buf() ---@type LintConfig[] - local ft_handler = require('guard.filetype') local linters = util.eval( - vim.tbl_map(util.toolcopy, (ft_handler[vim.bo[buf].filetype] or ft_handler['*'] or {}).linter) + vim.tbl_map( + util.toolcopy, + (vim.tbl_get(ft, vim.bo[buf].filetype, 'linter') or vim.tbl_get(ft, '*', 'linter')) + ) ) linters = vim.tbl_filter(function(config) @@ -52,7 +55,6 @@ function M.do_lint_single(buf, config) end local results = {} - ---@type string local data diff --git a/spec/command_spec.lua b/spec/command_spec.lua index 2347eef..8df91cd 100644 --- a/spec/command_spec.lua +++ b/spec/command_spec.lua @@ -3,10 +3,10 @@ require('plugin.guard') vim.opt_global.swapfile = false local api = vim.api local same = assert.are.same -local ft_handler = require('guard.filetype') +local ft = require('guard.filetype') describe('commands', function() - ft_handler('lua'):fmt({ + ft('lua'):fmt({ cmd = 'stylua', args = { '-' }, stdin = true, @@ -72,7 +72,7 @@ describe('commands', function() api.nvim_del_autocmd(au.id) end) - ft_handler('lua'):fmt({ + ft('lua'):fmt({ fn = function() return 'test' end, diff --git a/spec/settings_spec.lua b/spec/settings_spec.lua index 01fe1b8..a770253 100644 --- a/spec/settings_spec.lua +++ b/spec/settings_spec.lua @@ -5,6 +5,9 @@ local same = assert.are.same local ft = require('guard.filetype') local util = require('guard.util') local gapi = require('guard.api') +local lint = require('guard.lint') +local ns = api.nvim_get_namespaces()['Guard'] +local vd = vim.diagnostic describe('settings', function() local bufnr @@ -12,6 +15,22 @@ describe('settings', function() 'local a', ' =42', } + local mock_linter_regex = { + fn = function() + return '/tmp/lint_spec_test.lua:1:1: warning: Very important error message [error code 114514]' + end, + parse = lint.from_regex({ + source = 'mock_linter_regex', + regex = ':(%d+):(%d+):%s+(%w+):%s+(.-)%s+%[(.-)%]', + groups = { 'lnum', 'col', 'severity', 'message', 'code' }, + offset = 0, + severities = { + information = lint.severities.info, + hint = lint.severities.info, + note = lint.severities.style, + }, + }), + } before_each(function() if bufnr then vim.cmd('bdelete! ' .. bufnr) @@ -22,6 +41,9 @@ describe('settings', function() vim.cmd('noautocmd silent! write! /tmp/settings_spec_test.lua') vim.cmd('silent! edit!') vim.g.guard_config = nil + vim.iter(api.nvim_get_autocmds({ group = 'Guard' })):each(function(it) + api.nvim_del_autocmd(it.id) + end) end) it('can override fmt_on_save before setting up formatter', function() @@ -118,4 +140,32 @@ describe('settings', function() vim.wait(500) same(true, vim.bo[bufnr].modified) end) + + it('can change auto_lint option to control lint behaviour', function() + ft('*'):lint(mock_linter_regex) + + same(true, util.getopt('auto_lint')) + vim.cmd('silent! write!') + vim.wait(500) + same({ + { + source = 'mock_linter_regex', + bufnr = bufnr, + col = 1, + end_col = 1, + lnum = 1, + end_lnum = 1, + message = 'Very important error message[error code 114514]', + namespace = ns, + severity = 2, + }, + }, vd.get()) + + vim.g.guard_config = { auto_lint = false } + same(false, util.getopt('auto_lint')) + vd.reset(ns) + vim.cmd('silent! write!') + vim.wait(500) + same({}, vd.get()) + end) end) From 0042dbe1311e1671fbede09d1195b191e7b3749d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Dec 2024 11:33:56 +0000 Subject: [PATCH 26/37] chore(doc): auto generate docs --- doc/guard.nvim.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/guard.nvim.txt b/doc/guard.nvim.txt index 622f7ea..461dfaf 100644 --- a/doc/guard.nvim.txt +++ b/doc/guard.nvim.txt @@ -76,6 +76,8 @@ To register formatters and linters: lsp_as_default_formatter = false, -- whether or not to save the buffer after formatting save_on_fmt = true, + -- automatic linting + auto_lint = true } < From c8ea85fd91178a2682b1288b6ab34602752c4ebb Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 23 Dec 2024 11:37:14 +0000 Subject: [PATCH 27/37] special treat --- plugin/guard.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/guard.lua b/plugin/guard.lua index ed4d490..094a62b 100644 --- a/plugin/guard.lua +++ b/plugin/guard.lua @@ -31,6 +31,7 @@ local cmds = { ['disable-lint'] = function() require('guard.api').disable_lint() + vim.diagnostic.reset(api.nvim_get_namespaces()['Guard']) end, info = function() From fb45f0fa18ce634da56f80942229e35e5787ff2e Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 23 Dec 2024 11:44:21 +0000 Subject: [PATCH 28/37] fix global state --- spec/lint_spec.lua | 3 +++ spec/settings_spec.lua | 3 +++ 2 files changed, 6 insertions(+) diff --git a/spec/lint_spec.lua b/spec/lint_spec.lua index 49ec8bc..714d5f1 100644 --- a/spec/lint_spec.lua +++ b/spec/lint_spec.lua @@ -23,6 +23,9 @@ describe('lint module', function() api.nvim_set_current_buf(bufnr) vim.cmd('silent! write! /tmp/lint_spec_test.lua') end) + teardown(function() + vd.reset() + end) local mock_linter_regex = { fn = function() diff --git a/spec/settings_spec.lua b/spec/settings_spec.lua index a770253..a75802f 100644 --- a/spec/settings_spec.lua +++ b/spec/settings_spec.lua @@ -45,6 +45,9 @@ describe('settings', function() api.nvim_del_autocmd(it.id) end) end) + teardown(function() + vd.reset() + end) it('can override fmt_on_save before setting up formatter', function() same(true, util.getopt('fmt_on_save')) From a2cd3ea9def53895b229777198a4cdb0ff353b12 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 23 Dec 2024 11:54:21 +0000 Subject: [PATCH 29/37] feat: add lint interval option + naming changes --- README.md | 6 ++++-- lua/guard/events.lua | 8 ++++---- lua/guard/filetype.lua | 4 ++-- lua/guard/util.lua | 1 + 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 03553e5..39e4d56 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ ft('lang'):fmt('format-tool-1') :lint('lint-tool-1') :extra(extra_args) --- change this anywhere in your config, these are the defaults +-- change this anywhere in your config (or not), these are the defaults vim.g.guard_config = { -- format on write to buffer fmt_on_save = true, @@ -58,7 +58,9 @@ vim.g.guard_config = { -- whether or not to save the buffer after formatting save_on_fmt = true, -- automatic linting - auto_lint = true + auto_lint = true, + -- how frequently can linters be called + lint_interval = 500 } ``` diff --git a/lua/guard/events.lua b/lua/guard/events.lua index 68f6ecd..1ca39ec 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -18,7 +18,7 @@ local function debounced_lint(opt) end ---@diagnostic disable-next-line: undefined-field debounce_timer = assert(uv.new_timer()) --[[uv_timer_t]] - debounce_timer:start(500, 0, function() + debounce_timer:start(util.getopt('lint_interval'), 0, function() debounce_timer:stop() debounce_timer:close() debounce_timer = nil @@ -172,7 +172,7 @@ end ---@param ft string ---@param formatters FmtConfig[] -function M.fmt_watch_ft(ft, formatters) +function M.fmt_on_ft(ft, formatters) -- check if all cmds executable before registering formatter iter(formatters):any(function(config) if type(config) == 'table' and config.cmd and vim.fn.executable(config.cmd) ~= 1 then @@ -208,7 +208,7 @@ function M.maybe_default_to_lsp(config, ft, buf) pattern = ft, }) == 0 then - M.fmt_watch_ft(ft, config.formatter) + M.fmt_on_ft(ft, config.formatter) end M.try_attach_fmt_to_buf(buf) end @@ -234,7 +234,7 @@ end ---@param ft string ---@param events string[] -function M.lint_watch_ft(ft, events) +function M.lint_on_ft(ft, events) iter(require('guard.filetype')[ft].linter):any(function(config) if config.cmd and vim.fn.executable(config.cmd) ~= 1 then report_error(config.cmd .. ' not executable') diff --git a/lua/guard/filetype.lua b/lua/guard/filetype.lua index e55095a..14a0363 100644 --- a/lua/guard/filetype.lua +++ b/lua/guard/filetype.lua @@ -78,7 +78,7 @@ local function box(ft) -- use user's custom events events.fmt_attach_custom(it, config.events) else - events.fmt_watch_ft(it, self.formatter) + events.fmt_on_ft(it, self.formatter) events.fmt_attach_to_existing(it) end end @@ -105,7 +105,7 @@ local function box(ft) -- use user's custom events events.lint_attach_custom(it, config) else - events.lint_watch_ft(it, evs) + events.lint_on_ft(it, evs) events.lint_attach_to_existing(it, evs) end end diff --git a/lua/guard/util.lua b/lua/guard/util.lua index ee137d9..214d483 100644 --- a/lua/guard/util.lua +++ b/lua/guard/util.lua @@ -174,6 +174,7 @@ function M.getopt(opt) lsp_as_default_formatter = false, save_on_fmt = true, auto_lint = true, + lint_interval = 500, } if not vim.g.guard_config From 9cb6efbe32181167852f67b4797d4daf8a13e7f4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Dec 2024 11:54:41 +0000 Subject: [PATCH 30/37] chore(doc): auto generate docs --- doc/guard.nvim.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/guard.nvim.txt b/doc/guard.nvim.txt index 461dfaf..1d871b7 100644 --- a/doc/guard.nvim.txt +++ b/doc/guard.nvim.txt @@ -68,7 +68,7 @@ To register formatters and linters: :lint('lint-tool-1') :extra(extra_args) - -- change this anywhere in your config, these are the defaults + -- change this anywhere in your config (or not), these are the defaults vim.g.guard_config = { -- format on write to buffer fmt_on_save = true, @@ -77,7 +77,9 @@ To register formatters and linters: -- whether or not to save the buffer after formatting save_on_fmt = true, -- automatic linting - auto_lint = true + auto_lint = true, + -- how frequently can linters be called + lint_interval = 500 } < From 101e2db3c7c1064ef35b3e7ec8583814f04c96a4 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 23 Dec 2024 12:03:12 +0000 Subject: [PATCH 31/37] Update lua/guard/filetype.lua Co-authored-by: glepnir --- lua/guard/filetype.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/guard/filetype.lua b/lua/guard/filetype.lua index 14a0363..e9339ca 100644 --- a/lua/guard/filetype.lua +++ b/lua/guard/filetype.lua @@ -78,7 +78,7 @@ local function box(ft) -- use user's custom events events.fmt_attach_custom(it, config.events) else - events.fmt_on_ft(it, self.formatter) + events.fmt_on_filetype(it, self.formatter) events.fmt_attach_to_existing(it) end end From cde7233d5649463dec931ea242515b9834ef5308 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 23 Dec 2024 12:03:19 +0000 Subject: [PATCH 32/37] Update lua/guard/filetype.lua Co-authored-by: glepnir --- lua/guard/filetype.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/guard/filetype.lua b/lua/guard/filetype.lua index e9339ca..ea9da7e 100644 --- a/lua/guard/filetype.lua +++ b/lua/guard/filetype.lua @@ -105,7 +105,7 @@ local function box(ft) -- use user's custom events events.lint_attach_custom(it, config) else - events.lint_on_ft(it, evs) + events.lint_on_filetype(it, evs) events.lint_attach_to_existing(it, evs) end end From bd3a151201dcf0d78063d56600c4abf270c6c4f6 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 23 Dec 2024 12:03:29 +0000 Subject: [PATCH 33/37] Update lua/guard/events.lua Co-authored-by: glepnir --- lua/guard/events.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/guard/events.lua b/lua/guard/events.lua index 1ca39ec..251c0f5 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -234,7 +234,7 @@ end ---@param ft string ---@param events string[] -function M.lint_on_ft(ft, events) +function M.lint_on_filetype(ft, events) iter(require('guard.filetype')[ft].linter):any(function(config) if config.cmd and vim.fn.executable(config.cmd) ~= 1 then report_error(config.cmd .. ' not executable') From 422dedbd2216073b926ff7adc304ded9111d72a1 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 23 Dec 2024 12:03:36 +0000 Subject: [PATCH 34/37] Update lua/guard/events.lua Co-authored-by: glepnir --- lua/guard/events.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/guard/events.lua b/lua/guard/events.lua index 251c0f5..7e6dbb4 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -208,7 +208,7 @@ function M.maybe_default_to_lsp(config, ft, buf) pattern = ft, }) == 0 then - M.fmt_on_ft(ft, config.formatter) + M.fmt_on_filetype(ft, config.formatter) end M.try_attach_fmt_to_buf(buf) end From 46649a9eac3e537fb6b5b5e2a6d20ea388fc3759 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 23 Dec 2024 12:03:45 +0000 Subject: [PATCH 35/37] Update lua/guard/events.lua Co-authored-by: glepnir --- lua/guard/events.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/guard/events.lua b/lua/guard/events.lua index 7e6dbb4..75b5c99 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -172,7 +172,7 @@ end ---@param ft string ---@param formatters FmtConfig[] -function M.fmt_on_ft(ft, formatters) +function M.fmt_on_filetype(ft, formatters) -- check if all cmds executable before registering formatter iter(formatters):any(function(config) if type(config) == 'table' and config.cmd and vim.fn.executable(config.cmd) ~= 1 then From 6d36c7df7e2f7fc968d3dd4b7e80fe1f0e2b0d57 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 23 Dec 2024 13:26:56 +0000 Subject: [PATCH 36/37] feat: unify guard info with healthcheck --- lua/guard/api.lua | 62 +++----------------------------------------- lua/guard/events.lua | 16 +++++++----- lua/guard/health.lua | 59 +++++++++++++++++++++++++++++++++++------ 3 files changed, 63 insertions(+), 74 deletions(-) diff --git a/lua/guard/api.lua b/lua/guard/api.lua index 8c2cd5a..26a58f5 100644 --- a/lua/guard/api.lua +++ b/lua/guard/api.lua @@ -33,8 +33,8 @@ end ---@param bufnr number? function M.disable_fmt(bufnr) local buf = bufnr or api.nvim_get_current_buf() - vim.iter(events.get_format_autocmds(buf)):each(function(x) - api.nvim_del_autocmd(x) + vim.iter(events.get_format_autocmds(buf)):each(function(it) + api.nvim_del_autocmd(it.id) end) events.user_fmt_autocmds[vim.bo[buf].ft] = {} end @@ -64,63 +64,7 @@ end ---Show guard info for current buffer function M.info() - local util = require('guard.util') - local buf = api.nvim_get_current_buf() - local ft = require('guard.filetype')[vim.bo[buf].ft] or {} - local formatters = ft.formatter or {} - local linters = ft.linter or {} - local fmtau = events.get_format_autocmds(buf) - local lintau = events.get_lint_autocmds(buf) - - util.open_info_win() - local lines = { - '# Guard info (press Esc or q to close)', - '## Settings:', - ('- `fmt_on_save`: %s'):format(util.getopt('fmt_on_save')), - ('- `lsp_as_default_formatter`: %s'):format(util.getopt('lsp_as_default_formatter')), - ('- `save_on_fmt`: %s'):format(util.getopt('save_on_fmt')), - '', - ('## Current buffer has filetype %s:'):format(vim.bo[buf].ft), - ('- %s formatter autocmds attached'):format(#fmtau), - ('- %s linter autocmds attached'):format(#lintau), - '- formatters:', - '', - } - - if #formatters > 0 then - vim.list_extend(lines, { '', '```lua' }) - vim.list_extend( - lines, - vim - .iter(formatters) - :map(function(formatter) - return vim.split(vim.inspect(formatter), '\n', { trimempty = true }) - end) - :flatten() - :totable() - ) - vim.list_extend(lines, { '```', '' }) - end - - vim.list_extend(lines, { '- linters:' }) - - if #linters > 0 then - vim.list_extend(lines, { '', '```lua' }) - vim.list_extend( - lines, - vim - .iter(linters) - :map(function(linter) - return vim.split(vim.inspect(linter), '\n', { trimempty = true }) - end) - :flatten() - :totable() - ) - vim.list_extend(lines, { '```' }) - end - - api.nvim_buf_set_lines(0, 0, -1, true, lines) - api.nvim_set_option_value('modifiable', false, { buf = 0 }) + vim.cmd('checkhealth guard') end return M diff --git a/lua/guard/events.lua b/lua/guard/events.lua index 75b5c99..64f7eec 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -51,17 +51,19 @@ local function maybe_fill_auoption(opt, cb) end ---@param bufnr number ----@return number[] +---@return vim.api.keyset.get_autocmds.ret[] function M.get_format_autocmds(bufnr) if not api.nvim_buf_is_valid(bufnr) then return {} end - return M.user_fmt_autocmds[vim.bo[bufnr].ft] - or iter(api.nvim_get_autocmds({ group = M.group, event = 'BufWritePre', buffer = bufnr })):map( - function(it) - return it.id - end - ) + local caus = M.user_fmt_autocmds[vim.bo[bufnr].ft] + return caus + and iter(api.nvim_get_autocmds({ group = M.group })) + :filter(function(it) + return vim.tbl_contains(caus, it.id) + end) + :totable() + or api.nvim_get_autocmds({ group = M.group, event = 'BufWritePre', buffer = bufnr }) end ---@param bufnr number diff --git a/lua/guard/health.lua b/lua/guard/health.lua index b39fdc4..04ff997 100644 --- a/lua/guard/health.lua +++ b/lua/guard/health.lua @@ -1,6 +1,8 @@ local fn, health = vim.fn, vim.health +local ok, error, info = health.ok, health.error, health.info local filetype = require('guard.filetype') -local ok, error = health.ok, health.error +local util = require('guard.util') +local events = require('guard.events') local M = {} local function executable_check() @@ -27,19 +29,60 @@ local function executable_check() table.insert(checked, conf.cmd) end end - if pcall(require, 'mason') then - health.warn( - 'It seems that mason.nvim is installed,' - .. 'in which case checkhealth may be inaccurate.' - .. ' Please add your mason bin path to PATH to avoid potential issues.' - ) - end end + if pcall(require, 'mason') then + health.warn( + 'It seems that mason.nvim is installed,' + .. 'in which case checkhealth may be inaccurate.' + .. ' Please add your mason bin path to PATH to avoid potential issues.' + ) + end +end + +local function dump_settings() + ok(('fmt_on_save: %s'):format(util.getopt('fmt_on_save'))) + ok(('lsp_as_default_formatter: %s'):format(util.getopt('lsp_as_default_formatter'))) + ok(('save_on_fmt: %s'):format(util.getopt('save_on_fmt'))) end +local function dump_tools(buf) + local fmtau = events.get_format_autocmds(buf) + local lintau = events.get_lint_autocmds(buf) + local ft = vim.bo[buf].ft + + ok(('Current buffer has filetype %s:'):format(ft)) + info(('%s formatter autocmds attached'):format(#fmtau)) + info(('%s linter autocmds attached'):format(#lintau)) + + local conf = filetype[ft] or {} + local formatters = conf.formatter or {} + local linters = conf.linter or {} + + if #formatters > 0 then + info('formatters:') + vim.iter(formatters):map(vim.inspect):each(info) + end + + if #linters > 0 then + info('formatters:') + vim.iter(linters):map(vim.inspect):each(info) + end +end + +-- TODO: add custom autocmds info M.check = function() health.start('Executable check') executable_check() + + health.start('Settings') + dump_settings() + + local orig_bufnr = vim.fn.bufnr('#') + if not vim.api.nvim_buf_is_valid(orig_bufnr) then + return + end + health.start(('Tools registered for current buffer (bufnr %s)'):format(orig_bufnr)) + dump_tools(orig_bufnr) end return M From a5f309ef61e510d14bec4edecef64a93634a5be2 Mon Sep 17 00:00:00 2001 From: xiaoshihou Date: Mon, 23 Dec 2024 13:29:55 +0000 Subject: [PATCH 37/37] chore: tweak issue template --- .github/ISSUE_TEMPLATE/bug_report.yml | 37 ++++++++++++--------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 6214e24..4607e62 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,37 +1,44 @@ -# https://github.com/mrcjkb/haskell-tools.nvim/blob/master/.github/ISSUE_TEMPLATE/bug_report.yml +# Based on https://github.com/mrcjkb/haskell-tools.nvim/blob/master/.github/ISSUE_TEMPLATE/bug_report.yml name: Bug Report description: Report a problem with guard.nvim labels: [bug] body: - - type: input attributes: label: "Neovim version (nvim -v)" placeholder: "v0.10.0" validations: required: true + - type: input attributes: label: "Operating system/version" placeholder: "Fedora Linux 40" validations: required: true + - type: textarea attributes: - label: "Output of :checkhealth guard" - render: "console" - placeholder: | - Perhaps the tools are not in your $PATH? + label: "Expected behaviour" + description: "Describe the behaviour you expect." + validations: + required: true + + - type: textarea + attributes: + label: "Actual behaviour" validations: required: true + - type: textarea attributes: - label: "Output of :Guard info" - render: "markdown" + label: "Output of :checkhealth guard" + render: "console" placeholder: | - Are the autocmds attached? + Perhaps the tools are not in your $PATH? validations: required: true + - type: textarea attributes: label: "How to reproduce the issue" @@ -43,6 +50,7 @@ body: nvim --clean -u /tmp/repro.lua validations: required: true + - type: checkboxes attributes: label: Are you sure this is a min repro? @@ -51,14 +59,3 @@ body: required: true - label: I can confirm that my reproduction step only involves `vim.opt.rtp` and configuration themselves required: true - - type: textarea - attributes: - label: "Expected behaviour" - description: "Describe the behaviour you expect." - validations: - required: true - - type: textarea - attributes: - label: "Actual behaviour" - validations: - required: true