diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 61dc4f14..4607e621 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: "Output of :Guard info" - render: "markdown" + label: "Actual behaviour" + validations: + required: true + + - type: textarea + attributes: + 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" @@ -40,29 +47,15 @@ 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: textarea - attributes: - 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: "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 + - type: checkboxes + attributes: + 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 + required: true diff --git a/CUSTOMIZE.md b/CUSTOMIZE.md index 617fd7da..763c4b81 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/README.md b/README.md index 07a40de7..39e4d568 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, @@ -57,12 +57,15 @@ 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, + -- how frequently can linters be called + lint_interval = 500 } ``` - 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 +95,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/doc/guard.nvim.txt b/doc/guard.nvim.txt index 12b3698f..1d871b78 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 23 ============================================================================== Table of Contents *guard.nvim-table-of-contents* @@ -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, @@ -76,12 +76,15 @@ 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, + -- how frequently can linters be called + lint_interval = 500 } < - 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 +115,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>. @@ -150,6 +156,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 653d9e49..14475057 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? @@ -26,3 +28,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 new file mode 100644 index 00000000..26a58f57 --- /dev/null +++ b/lua/guard/api.lua @@ -0,0 +1,70 @@ +-- 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) + 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.fmt_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() + 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 + +---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]), + vim.bo[buf].ft + ) + 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() + vim.cmd('checkhealth guard') +end + +return M diff --git a/lua/guard/events.lua b/lua/guard/events.lua index 25da79b9..64f7eece 100644 --- a/lua/guard/events.lua +++ b/lua/guard/events.lua @@ -7,35 +7,176 @@ 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 +M.user_fmt_autocmds = {} +M.user_lint_autocmds = {} + +local debounce_timer = nil +local function debounced_lint(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(util.getopt('lint_interval'), 0, function() + debounce_timer:stop() + debounce_timer:close() + debounce_timer = nil + vim.schedule(function() + require('guard.lint').do_lint(opt.buf) + end) + 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) + 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 + 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 +---@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', + }) + ) +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 +---@param ft string +---@return boolean +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 +function M.try_attach_fmt_to_buf(buf) + if not M.check_fmt_should_attach(buf) then return end 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 +---@param buf number +---@param events string[] +---@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 + + 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 + lazy_debounced_lint(opt) + end + end, + }) + else + au(ev, { + group = M.group, + buffer = buf, + callback = lazy_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) + 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.watch_ft(ft) +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_on_filetype(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 @@ -47,7 +188,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 +210,9 @@ function M.maybe_default_to_lsp(config, ft, buf) pattern = ft, }) == 0 then - M.watch_ft(ft) + M.fmt_on_filetype(ft, config.formatter) end - M.try_attach_to_buf(buf) + M.try_attach_fmt_to_buf(buf) end end @@ -83,10 +224,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') @@ -96,8 +234,9 @@ function M.create_lspattach_autocmd() }) end -local debounce_timer = nil -function M.register_lint(ft, events) +---@param ft string +---@param events string[] +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') @@ -109,43 +248,47 @@ 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, ft) end, }) end +---@param events EventOption[] +---@param ft string +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) + table.insert( + M.user_fmt_autocmds[ft], + api.nvim_create_autocmd( + event.name, + maybe_fill_auoption(event.opt or {}, function(opt) + require('guard.format').do_fmt(opt.buf) + end) + ) + ) + end) +end + +---@param config LintConfig +---@param ft string +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(config.events):each(function(event) + table.insert( + 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) + end)) + end) + ) + ) + end) +end + return M diff --git a/lua/guard/filetype.lua b/lua/guard/filetype.lua index 74e60448..ea9da7e9 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.watch_ft(it) - events.fmt_attach_to_existing(it) + + if type(config) == 'table' and config.events then + -- use user's custom events + events.fmt_attach_custom(it, config.events) + else + events.fmt_on_filetype(it, self.formatter) + events.fmt_attach_to_existing(it) + end end return self end @@ -88,17 +94,20 @@ 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 + 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) + + if type(config) == 'table' and config.events then + -- use user's custom events + events.lint_attach_custom(it, config) + else + events.lint_on_filetype(it, evs) + events.lint_attach_to_existing(it, evs) + end end return self end @@ -107,7 +116,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 diff --git a/lua/guard/format.lua b/lua/guard/format.lua index e10cd170..fa364561 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/health.lua b/lua/guard/health.lua index b39fdc47..04ff9970 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 diff --git a/lua/guard/init.lua b/lua/guard/init.lua deleted file mode 100644 index e3f5dd5e..00000000 --- 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/lua/guard/lint.lua b/lua/guard/lint.lua index 8bf50f3e..827e4f1b 100644 --- a/lua/guard/lint.lua +++ b/lua/guard/lint.lua @@ -1,68 +1,91 @@ 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 ft = require('guard.filetype') + local M = {} +local ns = api.nvim_create_namespace('Guard') +local custom_ns = {} +---@param buf number? function M.do_lint(buf) buf = buf or api.nvim_get_current_buf() ---@type LintConfig[] - local linters, generic_linters - local generic_config = ft_handler['*'] - local buf_config = ft_handler[vim.bo[buf].filetype] + local linters = util.eval( + vim.tbl_map( + util.toolcopy, + (vim.tbl_get(ft, vim.bo[buf].filetype, 'linter') or vim.tbl_get(ft, '*', 'linter')) + ) + ) - if generic_config and generic_config.linter then - generic_linters = generic_config.linter - end + linters = vim.tbl_filter(function(config) + return util.should_run(config, buf) + end, linters) - 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 + coroutine.resume(coroutine.create(function() + vd.reset(ns, buf) + vim.iter(linters):each(function(linter) + M.do_lint_single(buf, linter) + end) + end)) +end - linters = util.eval(linters) +---@param buf number +---@param config LintConfig +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) - linters = vim.tbl_filter(function(config) - return util.should_run(config, buf) - end, linters) + if not util.should_run(lint, buf) then + return + end local prev_lines = api.nvim_buf_get_lines(buf, 0, -1, false) - vd.reset(ns, buf) + if custom and not custom_ns[config] then + custom_ns[config] = api.nvim_create_namespace(tostring(config)) + end + local cns = custom and custom_ns[config] or ns - coroutine.resume(coroutine.create(function() - local results = {} - - for _, lint in ipairs(linters) do - local data - if lint.cmd then - lint.cwd = lint.cwd or cwd - data = spawn.transform(util.get_cmd(lint, fname), lint, prev_lines) - else - data = lint.fn(prev_lines) - end - if #data > 0 then - vim.list_extend(results, lint.parse(data, buf)) - end + 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 not api.nvim_buf_is_valid(buf) or not results or #results == 0 then - return + 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(ns, buf, results) - end) - 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 f6622f3c..a511120f 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 25ebebdf..214d4832 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 @@ -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, @@ -172,6 +173,8 @@ function M.getopt(opt) fmt_on_save = true, lsp_as_default_formatter = false, save_on_fmt = true, + auto_lint = true, + lint_interval = 500, } if not vim.g.guard_config @@ -196,8 +199,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 }) @@ -205,28 +209,29 @@ 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 vim.tbl_map(function(x) - if type(x) == 'function' then - return x() - else - return x - end - end, xs) + return xs and vim.tbl_map(M.eval1, xs) or {} 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 be51196b..094a62b3 100644 --- a/plugin/guard.lua +++ b/plugin/guard.lua @@ -10,96 +10,41 @@ local ft_handler = require('guard.filetype') local cmds = { fmt = function() - require('guard.format').do_fmt() + require('guard.api').fmt() end, - enable = 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 + + lint = function() + require('guard.api').lint() end, - disable = 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 + + ['enable-fmt'] = function() + require('guard.api').enable_fmt() + end, + + ['disable-fmt'] = function() + require('guard.api').disable_fmt() + end, + + ['enable-lint'] = function() + require('guard.api').enable_lint() end, + + ['disable-lint'] = function() + require('guard.api').disable_lint() + vim.diagnostic.reset(api.nvim_get_namespaces()['Guard']) + 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] - if f then - f(opts) - else - vim.notify('[Guard]: Invalid subcommand: ' .. opts.args) - end + or function() + vim.notify('[Guard]: Invalid subcommand: ' .. opts.args) + end + f() end, { nargs = '+', complete = function(arg_lead, cmdline, _) diff --git a/spec/command_spec.lua b/spec/command_spec.lua index 10eaaa9b..8df91cde 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 = require('guard.filetype') describe('commands', function() - require('guard.filetype')('lua'):fmt({ + ft('lua'):fmt({ cmd = 'stylua', args = { '-' }, stdin = true, @@ -23,48 +24,80 @@ 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') - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' =42', - }) + vim.cmd('Guard disable-fmt') + 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', }) -- 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', - ' =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('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) diff --git a/spec/format_spec.lua b/spec/format_spec.lua index 81d93888..ebbbcc50 100644 --- a/spec/format_spec.lua +++ b/spec/format_spec.lua @@ -1,10 +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 @@ -16,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"', - }) - require('guard.format').do_fmt(bufnr) + 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() @@ -42,14 +52,10 @@ describe('format module', function() args = { '-s', ' ' }, stdin = true, }) - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' = "test"', - }) - require('guard.format').do_fmt(bufnr) + 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() @@ -58,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"', - }) - require('guard.format').do_fmt(bufnr) + 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() @@ -85,27 +87,50 @@ describe('format module', function() end end) - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'foo', - 'bar', - }) - require('guard.format').do_fmt(bufnr) + 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 - require('guard.format').do_fmt(bufnr) + 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 - require('guard.format').do_fmt(bufnr) + 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) diff --git a/spec/lint_spec.lua b/spec/lint_spec.lua new file mode 100644 index 00000000..714d5f14 --- /dev/null +++ b/spec/lint_spec.lua @@ -0,0 +1,211 @@ +---@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'] +local vd = vim.diagnostic + +describe('lint module', function() + local bufnr + before_each(function() + for k, _ in pairs(ft) do + ft[k] = nil + end + + 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) + vim.cmd('silent! write! /tmp/lint_spec_test.lua') + end) + teardown(function() + vd.reset() + 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 = { + 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', + }), + } + + 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 + end + ft('lua'):lint(mock_linter_regex) + + gapi.lint() + 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, + }, + }, vd.get()) + 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() + 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, + }, + { + 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', + }, + }, vd.get()) + end) + + it('can define a linter for all filetypes', function() + ft('*'):lint(mock_linter) + + gapi.lint() + vim.wait(100) + + same({ + { + bufnr = bufnr, + col = 1, + end_col = 1, + lnum = 1, + end_lnum = 1, + message = 'foo', + 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', + }, + }, vd.get()) + end) +end) diff --git a/spec/settings_spec.lua b/spec/settings_spec.lua index bfd39e3a..a75802f9 100644 --- a/spec/settings_spec.lua +++ b/spec/settings_spec.lua @@ -4,9 +4,33 @@ local api = vim.api 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 + local ill_lua = { + '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) @@ -17,6 +41,12 @@ 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) + teardown(function() + vd.reset() end) it('can override fmt_on_save before setting up formatter', function() @@ -30,10 +60,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({ @@ -49,10 +76,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) @@ -63,16 +87,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() @@ -86,12 +104,10 @@ 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.cmd('Guard fmt') + vim.wait(100) + gapi.fmt() vim.wait(500) same(true, vim.bo[bufnr].modified) end) @@ -103,12 +119,9 @@ 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')) - vim.cmd('Guard fmt') + gapi.fmt() vim.wait(500) same(false, vim.bo[bufnr].modified) @@ -120,17 +133,42 @@ 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) - api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'local a', - ' =42', - }) - vim.cmd('Guard fmt') + api.nvim_buf_set_lines(bufnr, 0, -1, false, ill_lua) + gapi.fmt() 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)