From 88a2e4f90652e50367dc46a4e6514633a3f2c758 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 19 Jun 2023 10:13:23 +0100 Subject: [PATCH] feat(watcher): move from libuv poll to file event watcher --- .github/workflows/ci.yml | 1 - lua/gitsigns.lua | 18 +++-- lua/gitsigns/attach.lua | 4 -- lua/gitsigns/cache.lua | 4 +- lua/gitsigns/config.lua | 6 +- lua/gitsigns/manager.lua | 85 ++++++++++++++--------- test/gitdir_watcher_spec.lua | 85 +++++++++++++---------- test/gitsigns_spec.lua | 127 +++++++++++++++++------------------ test/gs_helpers.lua | 96 +++++++++++--------------- test/highlights_spec.lua | 5 +- vim.yml | 24 +++++++ 11 files changed, 245 insertions(+), 210 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3527c4882..551c94e46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,6 @@ jobs: runs-on: ubuntu-latest strategy: - fail-fast: true matrix: neovim_branch: - 'v0.8.3' diff --git a/lua/gitsigns.lua b/lua/gitsigns.lua index 62d4e23d8..334e29c43 100644 --- a/lua/gitsigns.lua +++ b/lua/gitsigns.lua @@ -13,7 +13,7 @@ local uv = vim.loop local M = {} -local cwd_watcher ---@type uv_fs_poll_t +local cwd_watcher ---@type uv_fs_event_t? local update_cwd_head = void(function() local paths = vim.fs.find('.git', { @@ -29,7 +29,7 @@ local update_cwd_head = void(function() if cwd_watcher then cwd_watcher:stop() else - cwd_watcher = assert(uv.new_fs_poll()) + cwd_watcher = assert(uv.new_fs_event()) end local cwd = assert(vim.loop.cwd()) @@ -70,10 +70,18 @@ local update_cwd_head = void(function() return end + local debounce_trailing = require('gitsigns.debounce').debounce_trailing + + local update_head = debounce_trailing(100, void(function() + local new_head = git.get_repo_info(cwd).abbrev_head + scheduler() + vim.g.gitsigns_head = new_head + end)) + -- Watch .git/HEAD to detect branch changes cwd_watcher:start( towatch, - config.watch_gitdir.interval, + {}, void(function(err) local __FUNC__ = 'cwd_watcher_cb' if err then @@ -82,9 +90,7 @@ local update_cwd_head = void(function() end dprint('Git cwd dir update') - local new_head = git.get_repo_info(cwd).abbrev_head - scheduler() - vim.g.gitsigns_head = new_head + update_head() end) ) end) diff --git a/lua/gitsigns/attach.lua b/lua/gitsigns/attach.lua index b6e304e82..e44cd379c 100644 --- a/lua/gitsigns/attach.lua +++ b/lua/gitsigns/attach.lua @@ -352,10 +352,6 @@ local attach_throttled = throttle_by_id(function(cbuf, ctx, aucmd) -- Initial update manager.update(cbuf, cache[cbuf]) - - if config.keymaps and not vim.tbl_isempty(config.keymaps) then - require('gitsigns.mappings')(config.keymaps, cbuf) - end end) --- Detach Gitsigns from all buffers it is attached to. diff --git a/lua/gitsigns/cache.lua b/lua/gitsigns/cache.lua index 54cf3e90d..93841bda4 100644 --- a/lua/gitsigns/cache.lua +++ b/lua/gitsigns/cache.lua @@ -17,7 +17,7 @@ local M = { --- @field hunks_staged? Gitsigns.Hunk.Hunk[] --- --- @field staged_diffs Gitsigns.Hunk.Hunk[] ---- @field gitdir_watcher? uv_poll_t +--- @field gitdir_watcher? uv_fs_event_t --- @field git_obj Gitsigns.GitObj --- @field commit? string local CacheEntry = M.CacheEntry @@ -57,6 +57,8 @@ function CacheEntry:invalidate() self.hunks_staged = nil end +--- @param o Gitsigns.CacheEntry +--- @return Gitsigns.CacheEntry function CacheEntry.new(o) o.staged_diffs = o.staged_diffs or {} return setmetatable(o, { __index = CacheEntry }) diff --git a/lua/gitsigns/config.lua b/lua/gitsigns/config.lua index 0f44127d4..00d9413cd 100644 --- a/lua/gitsigns/config.lua +++ b/lua/gitsigns/config.lua @@ -62,7 +62,7 @@ end --- @field sign_priority integer --- @field _on_attach_pre fun(bufnr: integer, callback: fun(_: table)) --- @field on_attach fun(bufnr: integer) ---- @field watch_gitdir { enable: boolean, interval: integer, follow_files: boolean } +--- @field watch_gitdir { enable: boolean, follow_files: boolean } --- @field max_file_length integer --- @field update_debounce integer --- @field status_formatter fun(_: table): string @@ -295,7 +295,6 @@ M.schema = { deep_extend = true, default = { enable = true, - interval = 1000, follow_files = true, }, description = [[ @@ -307,9 +306,6 @@ M.schema = { • `enable`: Whether the watcher is enabled. - • `interval`: - Interval the watcher waits between polls of the gitdir in milliseconds. - • `follow_files`: If a file is moved with `git mv`, switch the buffer to the new location. ]], diff --git a/lua/gitsigns/manager.lua b/lua/gitsigns/manager.lua index 3bf342955..54d7872c3 100644 --- a/lua/gitsigns/manager.lua +++ b/lua/gitsigns/manager.lua @@ -497,7 +497,7 @@ local function handle_moved(bufnr, bcache, old_relpath) git_obj.orig_relpath = nil do_update = true end - else + -- else -- File removed from index, do nothing end @@ -519,59 +519,78 @@ local function handle_moved(bufnr, bcache, old_relpath) end end +-- vim.inspect but on one line +--- @param x any +--- @return string +local function inspect(x) + return vim.inspect(x, {indent = '', newline = ' '}) +end + +local watch_gitdir_handler = debounce_trailing(100, void(function(bufnr) + local bcache = cache[bufnr] + + if not bcache then + -- Very occasionally an external git operation may cause the buffer to + -- detach and update the git dir simultaneously. When this happens this + -- handler will trigger but there will be no cache. + dprint('Has detached, aborting') + return + end + + local git_obj = bcache.git_obj + + git_obj.repo:update_abbrev_head() + + scheduler() + Status:update(bufnr, { head = git_obj.repo.abbrev_head }) + + local was_tracked = git_obj.object_name ~= nil + local old_relpath = git_obj.relpath + + git_obj:update_file_info() + + if config.watch_gitdir.follow_files and was_tracked and not git_obj.object_name then + -- File was tracked but is no longer tracked. Must of been removed or + -- moved. Check if it was moved and switch to it. + handle_moved(bufnr, bcache, old_relpath) + end + + bcache:invalidate() + + M.update(bufnr, bcache) +end)) + --- @param bufnr integer --- @param gitdir string ---- @return uv_fs_poll_t? +--- @return uv_fs_event_t? function M.watch_gitdir(bufnr, gitdir) if not config.watch_gitdir.enable then return end dprintf('Watching git dir') - local w = assert(uv.new_fs_poll()) + local w = assert(uv.new_fs_event()) w:start( gitdir, - config.watch_gitdir.interval, - void(function(err) + {}, + function(err, filename, events) local __FUNC__ = 'watcher_cb' if err then dprintf('Git dir update error: %s', err) return end - dprint('Git dir update') - local bcache = cache[bufnr] + local info = string.format("Git dir update: '%s' %s", filename, inspect(events)) - if not bcache then - -- Very occasionally an external git operation may cause the buffer to - -- detach and update the git dir simultaneously. When this happens this - -- handler will trigger but there will be no cache. - dprint('Has detached, aborting') + if vim.endswith(filename, '.lock') then + dprintf("%s (ignoring)", info) return end - local git_obj = bcache.git_obj - - git_obj.repo:update_abbrev_head() - - scheduler() - Status:update(bufnr, { head = git_obj.repo.abbrev_head }) + dprint(info) - local was_tracked = git_obj.object_name ~= nil - local old_relpath = git_obj.relpath - - git_obj:update_file_info() - - if config.watch_gitdir.follow_files and was_tracked and not git_obj.object_name then - -- File was tracked but is no longer tracked. Must of been removed or - -- moved. Check if it was moved and switch to it. - handle_moved(bufnr, bcache, old_relpath) - end - - bcache:invalidate() - - M.update(bufnr, bcache) - end) + watch_gitdir_handler(bufnr) + end ) return w end diff --git a/test/gitdir_watcher_spec.lua b/test/gitdir_watcher_spec.lua index b8574a509..0aed098d6 100644 --- a/test/gitdir_watcher_spec.lua +++ b/test/gitdir_watcher_spec.lua @@ -1,7 +1,5 @@ local helpers = require('test.gs_helpers') -local Screen = require('test.screen') - local clear = helpers.clear local exec_lua = helpers.exec_lua local edit = helpers.edit @@ -11,11 +9,11 @@ local cleanup = helpers.cleanup local command = helpers.command local test_config = helpers.test_config local match_debug_messages = helpers.match_debug_messages -local p = helpers.p +local match_dag = helpers.match_dag +local n, p, np = helpers.n, helpers.p, helpers.np local setup_gitsigns = helpers.setup_gitsigns local test_file = helpers.test_file local git = helpers.git -local get_buf_name = helpers.curbufmeths.get_name local it = helpers.it(it) @@ -48,12 +46,12 @@ describe('gitdir_watcher', function() match_debug_messages { 'attach(1): Attaching (trigger=BufReadPost)', - p"run_job: git .* config user.name", - p"run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD", - p('run_job: git .* ls%-files .* '..vim.pesc(test_file)), - 'watch_gitdir(1): Watching git dir', - p'run_job: git .* show :0:dummy.txt', - 'update(1): updates: 1, jobs: 5', + np"run_job: git .* config user.name", + np"run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD", + np('run_job: git .* ls%-files .* '..vim.pesc(test_file)), + n'watch_gitdir(1): Watching git dir', + np'run_job: git .* show :0:dummy.txt', + n'update(1): updates: 1, jobs: 5', } eq({[1] = test_file}, get_bufs()) @@ -63,16 +61,21 @@ describe('gitdir_watcher', function() local test_file2 = test_file..'2' git{'mv', test_file, test_file2} + match_dag { + "watcher_cb(1): Git dir update: 'index.lock' { rename = true } (ignoring)", + "watcher_cb(1): Git dir update: 'index' { rename = true }", + "watcher_cb(1): Git dir update: 'index' { rename = true }", + } + match_debug_messages { - 'watcher_cb(1): Git dir update', - p'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', - p('run_job: git .* ls%-files .* '..vim.pesc(test_file)), - p'run_job: git .* diff %-%-name%-status %-C %-%-cached', - 'handle_moved(1): File moved to dummy.txt2', - p('run_job: git .* ls%-files .* '..vim.pesc(test_file2)), - p'handle_moved%(1%): Renamed buffer 1 from .*/dummy.txt to .*/dummy.txt2', - p'run_job: git .* show :0:dummy.txt2', - 'update(1): updates: 2, jobs: 10' + np'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', + np('run_job: git .* ls%-files .* '..vim.pesc(test_file)), + np'run_job: git .* diff %-%-name%-status %-C %-%-cached', + n'handle_moved(1): File moved to dummy.txt2', + np('run_job: git .* ls%-files .* '..vim.pesc(test_file2)), + np'handle_moved%(1%): Renamed buffer 1 from .*/dummy.txt to .*/dummy.txt2', + np'run_job: git .* show :0:dummy.txt2', + n'update(1): updates: 2, jobs: 10' } eq({[1] = test_file2}, get_bufs()) @@ -83,16 +86,21 @@ describe('gitdir_watcher', function() git{'mv', test_file2, test_file3} + match_dag { + "watcher_cb(1): Git dir update: 'index.lock' { rename = true } (ignoring)", + "watcher_cb(1): Git dir update: 'index' { rename = true }", + "watcher_cb(1): Git dir update: 'index' { rename = true }", + } + match_debug_messages { - 'watcher_cb(1): Git dir update', p'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', - p('run_job: git .* ls%-files .* '..vim.pesc(test_file2)), - p'run_job: git .* diff %-%-name%-status %-C %-%-cached', - 'handle_moved(1): File moved to dummy.txt3', - p('run_job: git .* ls%-files .* '..vim.pesc(test_file3)), - p'handle_moved%(1%): Renamed buffer 1 from .*/dummy.txt2 to .*/dummy.txt3', - p'run_job: git .* show :0:dummy.txt3', - 'update(1): updates: 3, jobs: 15' + np('run_job: git .* ls%-files .* '..vim.pesc(test_file2)), + np'run_job: git .* diff %-%-name%-status %-C %-%-cached', + n'handle_moved(1): File moved to dummy.txt3', + np('run_job: git .* ls%-files .* '..vim.pesc(test_file3)), + np'handle_moved%(1%): Renamed buffer 1 from .*/dummy.txt2 to .*/dummy.txt3', + np'run_job: git .* show :0:dummy.txt3', + n'update(1): updates: 3, jobs: 15' } eq({[1] = test_file3}, get_bufs()) @@ -101,17 +109,22 @@ describe('gitdir_watcher', function() git{'mv', test_file3, test_file} + match_dag { + "watcher_cb(1): Git dir update: 'index.lock' { rename = true } (ignoring)", + "watcher_cb(1): Git dir update: 'index' { rename = true }", + "watcher_cb(1): Git dir update: 'index' { rename = true }", + } + match_debug_messages { - 'watcher_cb(1): Git dir update', p'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', - p('run_job: git .* ls%-files .* '..vim.pesc(test_file3)), - p'run_job: git .* diff %-%-name%-status %-C %-%-cached', - p('run_job: git .* ls%-files .* '..vim.pesc(test_file)), - 'handle_moved(1): Moved file reset', - p('run_job: git .* ls%-files .* '..vim.pesc(test_file)), - p'handle_moved%(1%): Renamed buffer 1 from .*/dummy.txt3 to .*/dummy.txt', - p'run_job: git .* show :0:dummy.txt', - 'update(1): updates: 4, jobs: 21' + np('run_job: git .* ls%-files .* '..vim.pesc(test_file3)), + np'run_job: git .* diff %-%-name%-status %-C %-%-cached', + np('run_job: git .* ls%-files .* '..vim.pesc(test_file)), + n'handle_moved(1): Moved file reset', + np('run_job: git .* ls%-files .* '..vim.pesc(test_file)), + np'handle_moved%(1%): Renamed buffer 1 from .*/dummy.txt3 to .*/dummy.txt', + np'run_job: git .* show :0:dummy.txt', + n'update(1): updates: 4, jobs: 21' } eq({[1] = test_file}, get_bufs()) diff --git a/test/gitsigns_spec.lua b/test/gitsigns_spec.lua index b1eeb81a4..290feb12b 100644 --- a/test/gitsigns_spec.lua +++ b/test/gitsigns_spec.lua @@ -21,10 +21,9 @@ local test_file = helpers.test_file local git = helpers.git local scratch = helpers.scratch local newfile = helpers.newfile -local debug_messages = helpers.debug_messages local match_dag = helpers.match_dag local match_lines = helpers.match_lines -local p = helpers.p +local n, p, np = helpers.n, helpers.p, helpers.np local match_debug_messages = helpers.match_debug_messages local setup_gitsigns = helpers.setup_gitsigns local setup_test_repo = helpers.setup_test_repo @@ -86,17 +85,15 @@ describe('gitsigns', function() command('Gitsigns clear_debug') edit(test_file) - expectf(function() - match_dag(debug_messages(), { - 'attach(1): Attaching (trigger=BufReadPost)', - p'run_job: git .* config user.name', - p'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', - p('run_job: git .* ls%-files %-%-stage %-%-others %-%-exclude%-standard %-%-eol '..vim.pesc(test_file)), - 'watch_gitdir(1): Watching git dir', - p'run_job: git .* show :0:dummy.txt', - 'update(1): updates: 1, jobs: 6' - }) - end) + match_dag { + 'attach(1): Attaching (trigger=BufReadPost)', + p'run_job: git .* config user.name', + p'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', + p('run_job: git .* ls%-files %-%-stage %-%-others %-%-exclude%-standard %-%-eol '..vim.pesc(test_file)), + 'watch_gitdir(1): Watching git dir', + p'run_job: git .* show :0:dummy.txt', + 'update(1): updates: 1, jobs: 6' + } check { status = {head='', added=18, changed=0, removed=0}, @@ -118,11 +115,11 @@ describe('gitsigns', function() edit(tmpfile) match_debug_messages { - 'attach(1): Attaching (trigger=BufReadPost)', - p'run_job: git .* config user.name', - p'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', - 'new: Not in git repo', - 'attach(1): Empty git obj', + n'attach(1): Attaching (trigger=BufReadPost)', + np'run_job: git .* config user.name', + np'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', + n'new: Not in git repo', + n'attach(1): Empty git obj', } command('Gitsigns clear_debug') @@ -130,11 +127,11 @@ describe('gitsigns', function() command("write") match_debug_messages { - 'attach(1): Attaching (trigger=BufWritePost)', - p'run_job: git .* config user.name', - p'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', - 'new: Not in git repo', - 'attach(1): Empty git obj' + n'attach(1): Attaching (trigger=BufWritePost)', + np'run_job: git .* config user.name', + np'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', + n'new: Not in git repo', + n'attach(1): Empty git obj' } end) @@ -152,12 +149,12 @@ describe('gitsigns', function() -- Check all keymaps get set match_lines(res, { - 'n mhS *@lua require"gitsigns".stage_buffer()', - 'n mhU *@lua require"gitsigns".reset_buffer_index()', - 'n mhp *@lua require"gitsigns".preview_hunk()', - 'n mhr *@lua require"gitsigns".reset_hunk()', - 'n mhs *@lua require"gitsigns".stage_hunk()', - 'n mhu *@lua require"gitsigns".undo_stage_hunk()', + n'n mhS *@lua require"gitsigns".stage_buffer()', + n'n mhU *@lua require"gitsigns".reset_buffer_index()', + n'n mhp *@lua require"gitsigns".preview_hunk()', + n'n mhr *@lua require"gitsigns".reset_hunk()', + n'n mhs *@lua require"gitsigns".stage_hunk()', + n'n mhu *@lua require"gitsigns".undo_stage_hunk()', }) end) end) @@ -167,9 +164,9 @@ describe('gitsigns', function() edit(scratch..'/.git/index') match_debug_messages { - 'attach(1): Attaching (trigger=BufReadPost)', - 'new: In git dir', - 'attach(1): Empty git obj' + n'attach(1): Attaching (trigger=BufReadPost)', + n'new: In git dir', + n'attach(1): Empty git obj' } end) @@ -183,11 +180,11 @@ describe('gitsigns', function() edit(ignored_file) match_debug_messages { - 'attach(1): Attaching (trigger=BufReadPost)', - p'run_job: git .* config user.name', - p'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', - p'run_job: git .* ls%-files .*/dummy_ignored.txt', - 'attach(1): Cannot resolve file in repo', + n'attach(1): Attaching (trigger=BufReadPost)', + np'run_job: git .* config user.name', + np'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', + np'run_job: git .* ls%-files .*/dummy_ignored.txt', + n'attach(1): Cannot resolve file in repo', } check {status = {head='master'}} @@ -198,11 +195,11 @@ describe('gitsigns', function() edit(newfile) match_debug_messages { - 'attach(1): Attaching (trigger=BufNewFile)', - p'run_job: git .* config user.name', - p'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', - p('run_job: git .* ls%-files %-%-stage %-%-others %-%-exclude%-standard %-%-eol '..vim.pesc(newfile)), - 'attach(1): Not a file', + n'attach(1): Attaching (trigger=BufNewFile)', + np'run_job: git .* config user.name', + np'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', + np('run_job: git .* ls%-files %-%-stage %-%-others %-%-exclude%-standard %-%-eol '..vim.pesc(newfile)), + n'attach(1): Not a file', } check {status = {head='master'}} @@ -213,8 +210,8 @@ describe('gitsigns', function() edit(scratch..'/does/not/exist') match_debug_messages { - 'attach(1): Attaching (trigger=BufNewFile)', - 'attach(1): Not a path', + n'attach(1): Attaching (trigger=BufNewFile)', + n'attach(1): Not a path', } helpers.pcall_err(get_buf_var, 'gitsigns_head') @@ -225,8 +222,8 @@ describe('gitsigns', function() command("Gitsigns clear_debug") command("copen") match_debug_messages { - 'attach(2): Attaching (trigger=BufReadPost)', - 'attach(2): Non-normal buffer', + n'attach(2): Attaching (trigger=BufReadPost)', + n'attach(2): Non-normal buffer', } end) @@ -349,12 +346,12 @@ describe('gitsigns', function() edit(test_file) match_debug_messages { - 'attach(1): Attaching (trigger=BufReadPost)', - p'run_job: git .* config user.name', - p'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', - p'run_job: git .* rev%-parse %-%-short HEAD', - p'run_job: git .* %-%-git%-dir .* %-%-stage %-%-others %-%-exclude%-standard %-%-eol.*', - 'attach(1): User on_attach() returned false', + n'attach(1): Attaching (trigger=BufReadPost)', + np'run_job: git .* config user.name', + np'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', + np'run_job: git .* rev%-parse %-%-short HEAD', + np'run_job: git .* %-%-git%-dir .* %-%-stage %-%-others %-%-exclude%-standard %-%-eol.*', + n'attach(1): User on_attach() returned false', } end) end) @@ -462,30 +459,30 @@ describe('gitsigns', function() command('Gitsigns clear_debug') edit(newfile) match_debug_messages{ - 'attach(1): Attaching (trigger=BufNewFile)', - p'run_job: git .* config user.name', - p'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', - p'run_job: git .* ls%-files .*', - 'attach(1): Not a file', + n'attach(1): Attaching (trigger=BufNewFile)', + np'run_job: git .* config user.name', + np'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', + np'run_job: git .* ls%-files .*', + n'attach(1): Not a file', } command('Gitsigns clear_debug') command("write") local messages = { - 'attach(1): Attaching (trigger=BufWritePost)', - p"run_job: git .* config user.name", - p'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', - p'run_job: git .* ls%-files .*', - 'watch_gitdir(1): Watching git dir', - p'run_job: git .* show :0:newfile.txt' + n'attach(1): Attaching (trigger=BufWritePost)', + np"run_job: git .* config user.name", + np'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD', + np'run_job: git .* ls%-files .*', + n'watch_gitdir(1): Watching git dir', + np'run_job: git .* show :0:newfile.txt' } if not internal_diff then - table.insert(messages, p'run_job: git .* diff .* /tmp/lua_.* /tmp/lua_.*') + table.insert(messages, np'run_job: git .* diff .* /tmp/lua_.* /tmp/lua_.*') end local jobs = internal_diff and 8 or 9 - table.insert(messages, "update(1): updates: 1, jobs: "..jobs) + table.insert(messages, n("update(1): updates: 1, jobs: "..jobs)) match_debug_messages(messages) diff --git a/test/gs_helpers.lua b/test/gs_helpers.lua index 6d4f69262..84f96e225 100644 --- a/test/gs_helpers.lua +++ b/test/gs_helpers.lua @@ -123,63 +123,38 @@ function M.write_to_file(path, text) f:close() end -local function spec_text(s) - if type(s) == 'table' then - return s.text - end - return s -end - -function M.match_lines(lines, spec) - local i = 1 - for lid, line in ipairs(lines) do - if line ~= '' then - local s = spec[i] - if s then - if s.pattern then - matches(s.text, line) - else - eq(s, line) - end - else - local extra = {} - for j=lid,#lines do - table.insert(extra, lines[j]) - end - error('Unexpected extra text:\n '..table.concat(extra, '\n ')) - end - i = i + 1 +--- @param line string +--- @param spec string|{next:boolean, pattern:boolean, text:string} +--- @return boolean +local function match_spec_elem(line, spec) + + if spec.pattern then + if line:match(spec.text) then + return true end - end - - if i < #spec + 1 then - local msg = {'lines:'} - for _, l in ipairs(lines) do - msg[#msg+1] = string.format(' - "%s"', l) + elseif spec.next then + -- local matcher = spec.pattern and matches or eq + -- matcher(spec.text, line) + if spec.pattern then + matches(spec.text, line) + else + eq(spec.text, line) end - error(('Did not match pattern \'%s\' with %s'):format(spec_text(spec[i]), table.concat(msg, '\n'))) + return true end + + return spec == line end -local function match_lines2(lines, spec) +--- Match lines in spec. Not all lines have to match +--- @param lines string[] +--- @param spec table +function M.match_lines(lines, spec) local i = 1 for _, line in ipairs(lines) do - if line ~= '' then - local s = spec[i] - if s then - if s.pattern then - if string.match(line, s.text) then - i = i + 1 - end - elseif s.next then - eq(s.text, line) - i = i + 1 - else - if s == line then - i = i + 1 - end - end - end + local s = spec[i] + if line ~= '' and s and match_spec_elem(line, s) then + i = i + 1 end end @@ -200,21 +175,30 @@ local function match_lines2(lines, spec) end function M.p(str) - return {text=str, pattern=true} + return {text = str, pattern = true} end function M.n(str) - return {text=str, next=true} + return {text = str, next = true} end +function M.np(str) + return {text = str, pattern = true, next = true} +end + +--- @return string[] function M.debug_messages() return exec_lua("return require'gitsigns.debug.log'.messages") end -function M.match_dag(lines, spec) - for _, s in ipairs(spec) do - match_lines2(lines, {s}) - end +--- @param spec table +function M.match_dag(spec) + M.expectf(function() + local messages = M.debug_messages() + for _, s in ipairs(spec) do + M.match_lines(messages, {s}) + end + end) end function M.match_debug_messages(spec) diff --git a/test/highlights_spec.lua b/test/highlights_spec.lua index 8400b2f86..c8da55f7c 100644 --- a/test/highlights_spec.lua +++ b/test/highlights_spec.lua @@ -9,7 +9,6 @@ local cleanup = helpers.cleanup local test_config = helpers.test_config local expectf = helpers.expectf local match_dag = helpers.match_dag -local debug_messages = helpers.debug_messages local p = helpers.p local setup_gitsigns = helpers.setup_gitsigns @@ -61,7 +60,7 @@ describe('highlights', function() setup_gitsigns(config) expectf(function() - match_dag(debug_messages(), { + match_dag { p'Deriving GitSignsAdd from DiffAdd', p'Deriving GitSignsAddLn from DiffAdd', p'Deriving GitSignsAddNr from GitSignsAdd', @@ -69,7 +68,7 @@ describe('highlights', function() p'Deriving GitSignsChangeNr from GitSignsChange', p'Deriving GitSignsDelete from DiffDelete', p'Deriving GitSignsDeleteNr from GitSignsDelete', - }) + } end) -- eq('GitSignsChange xxx links to DiffChange', diff --git a/vim.yml b/vim.yml index c257a9f31..173994aa1 100644 --- a/vim.yml +++ b/vim.yml @@ -13,3 +13,27 @@ globals: package.config: any: true property: read-only + + # busted + describe: + args: + - type: string + required: true + - type: function + required: true + it: + args: + - type: string + required: true + - type: function + required: true + before_each: + args: + - type: function + required: true + after_each: + args: + - type: function + required: true + pending: + args: []