diff --git a/.luarc.json b/.luarc.json index bfbf500..eb18f65 100644 --- a/.luarc.json +++ b/.luarc.json @@ -2,14 +2,8 @@ "runtime.version": "LuaJIT", "runtime.path": ["lua/?.lua", "lua/?/init.lua"], "diagnostics.globals": ["vim", "jit"], - "workspace.library": [ - "$VIMRUNTIME/lua", - "${3rd}/luv/library", - "${3rd}/busted/library", - "${3rd}/luassert/library" - ], + "workspace.library": ["$VIMRUNTIME/lua", "${3rd}/luv/library", "${3rd}/busted/library", "${3rd}/luassert/library"], "workspace.checkThirdParty": false, - "diagnostics.libraryFiles": "Disable", "workspace.ignoreDir": [".direnv"], "completion.callSnippet": "Replace" } diff --git a/.styluaignore b/.styluaignore deleted file mode 100644 index 9b42106..0000000 --- a/.styluaignore +++ /dev/null @@ -1 +0,0 @@ -.direnv/ diff --git a/README.md b/README.md index eee728d..5d0022f 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,6 @@ with language-aware syntax highlighting. - `:Gdiff` unified diff against any revision - Background-only diff colors for `&diff` buffers - Inline merge conflict detection, highlighting, and resolution -- Email-quoted diff highlighting (`> diff ...` prefixes, arbitrary nesting - depth) - Vim syntax fallback, configurable blend/priorities ## Requirements diff --git a/flake.nix b/flake.nix index 2221bdf..38e98cd 100644 --- a/flake.nix +++ b/flake.nix @@ -13,8 +13,7 @@ ... }: let - forEachSystem = - f: nixpkgs.lib.genAttrs (import systems) (system: f nixpkgs.legacyPackages.${system}); + forEachSystem = f: nixpkgs.lib.genAttrs (import systems) (system: f nixpkgs.legacyPackages.${system}); in { formatter = forEachSystem (pkgs: pkgs.nixfmt-tree); @@ -24,12 +23,7 @@ let ts-plugin = pkgs.vimPlugins.nvim-treesitter.withPlugins (p: [ p.diff ]); diff-grammar = pkgs.vimPlugins.nvim-treesitter-parsers.diff; - luaEnv = pkgs.luajit.withPackages ( - ps: with ps; [ - busted - nlua - ] - ); + luaEnv = pkgs.luajit.withPackages (ps: with ps; [ busted nlua ]); busted-with-grammar = pkgs.writeShellScriptBin "busted" '' nvim_bin=$(which nvim) tmpdir=$(mktemp -d) @@ -40,14 +34,14 @@ ''; in pkgs.mkShell { - packages = [ - busted-with-grammar - pkgs.prettier - pkgs.stylua - pkgs.selene - pkgs.lua-language-server - ]; - }; + packages = [ + busted-with-grammar + pkgs.prettier + pkgs.stylua + pkgs.selene + pkgs.lua-language-server + ]; + }; }); }; } diff --git a/lua/diffs/highlight.lua b/lua/diffs/highlight.lua index 30c3a59..b31d630 100644 --- a/lua/diffs/highlight.lua +++ b/lua/diffs/highlight.lua @@ -65,7 +65,6 @@ end ---@field hide_prefix boolean ---@field highlights diffs.Highlights ---@field defer_vim_syntax? boolean ----@field syntax_only? boolean ---@param bufnr integer ---@param ns integer @@ -75,7 +74,6 @@ end ---@param col_offset integer ---@param covered_lines? table ---@param priorities diffs.PrioritiesConfig ----@param force_high_priority? boolean ---@return integer local function highlight_treesitter( bufnr, @@ -85,8 +83,7 @@ local function highlight_treesitter( line_map, col_offset, covered_lines, - priorities, - force_high_priority + priorities ) local code = table.concat(code_lines, '\n') if code == '' then @@ -126,9 +123,8 @@ local function highlight_treesitter( local buf_sc = sc + col_offset local buf_ec = ec + col_offset - local meta_prio = tonumber(metadata.priority) or 100 local priority = tree_lang == 'diff' - and ((col_offset > 0 or force_high_priority) and (priorities.syntax + meta_prio - 100) or meta_prio) + and (col_offset > 0 and priorities.syntax or (tonumber(metadata.priority) or 100)) or priorities.syntax pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_sr, buf_sc, { @@ -371,24 +367,14 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) header_map[i] = hunk.header_start_line - 1 + i end extmark_count = extmark_count - + highlight_treesitter( - bufnr, - ns, - hunk.header_lines, - 'diff', - header_map, - qw, - nil, - p, - qw > 0 or pw > 1 - ) + + highlight_treesitter(bufnr, ns, hunk.header_lines, 'diff', header_map, qw, nil, p) end local at_raw_line - if (qw > 0 or pw > 1) and opts.highlights.treesitter.enabled then + if qw > 0 and opts.highlights.treesitter.enabled then local at_buf_line = hunk.start_line - 1 at_raw_line = vim.api.nvim_buf_get_lines(bufnr, at_buf_line, at_buf_line + 1, false)[1] - if qw > 0 and at_raw_line then + if at_raw_line then local at_logical = at_raw_line:sub(qw + 1) local at_map = { [0] = at_buf_line } extmark_count = extmark_count @@ -399,13 +385,7 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) ---@type diffs.IntraChanges? local intra = nil local intra_cfg = opts.highlights.intra - if - not opts.syntax_only - and intra_cfg - and intra_cfg.enabled - and pw == 1 - and #hunk.lines <= intra_cfg.max_lines - then + if intra_cfg and intra_cfg.enabled and pw == 1 and #hunk.lines <= intra_cfg.max_lines then dbg('computing intra for hunk %s:%d (%d lines)', hunk.filename, hunk.start_line, #hunk.lines) intra = diff.compute_intra_hunks(hunk.lines, intra_cfg.algorithm) if intra then @@ -437,7 +417,7 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) end if - (qw > 0 or pw > 1) + qw > 0 and hunk.header_start_line and hunk.header_lines and #hunk.header_lines > 0 @@ -450,46 +430,16 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) hl_group = 'DiffsClear', priority = p.clear, }) - - if pw > 1 then - local hline = hunk.header_lines[i + 1] - if hline:match('^index ') then - pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, qw, { - end_col = qw + 5, - hl_group = '@keyword.diff', - priority = p.syntax, - }) - local dot_pos = hline:find('%.%.', 1, false) - if dot_pos then - local rest = hline:sub(dot_pos + 2) - local hash = rest:match('^(%x+)') - if hash then - pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, qw + dot_pos + 1, { - end_col = qw + dot_pos + 1 + #hash, - hl_group = '@constant.diff', - priority = p.syntax, - }) - end - end - end - end end end - if (qw > 0 or pw > 1) and at_raw_line then + if qw > 0 and at_raw_line then local at_buf_line = hunk.start_line - 1 pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, at_buf_line, 0, { end_col = #at_raw_line, hl_group = 'DiffsClear', priority = p.clear, }) - if pw > 1 and opts.highlights.treesitter.enabled then - pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, at_buf_line, qw, { - end_col = #at_raw_line, - hl_group = '@attribute.diff', - priority = p.syntax, - }) - end end if use_ts and hunk.header_context and hunk.header_context_col then @@ -520,82 +470,20 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) or content:match('^|||||||') end - if not opts.syntax_only then - if opts.hide_prefix then - local virt_hl = (opts.highlights.background and line_hl) or nil - pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 0, { - virt_text = { { string.rep(' ', pw + qw), virt_hl } }, - virt_text_pos = 'overlay', - }) - end + if opts.hide_prefix then + local virt_hl = (opts.highlights.background and line_hl) or nil + pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 0, { + virt_text = { { string.rep(' ', pw + qw), virt_hl } }, + virt_text_pos = 'overlay', + }) + end - if qw > 0 then - pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 0, { - end_col = pw + qw, - hl_group = 'DiffsClear', - priority = p.clear, - }) - elseif pw > 1 then - pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 0, { - end_col = pw, - hl_group = 'DiffsClear', - priority = p.clear, - }) - end - - if pw > 1 then - for ci = 0, pw - 1 do - local ch = line:sub(ci + 1, ci + 1) - if ch == '+' or ch == '-' then - pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, ci + qw, { - end_col = ci + qw + 1, - hl_group = ch == '+' and '@diff.plus' or '@diff.minus', - priority = p.syntax, - }) - end - end - end - - if opts.highlights.background and is_diff_line then - pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 0, { - line_hl_group = line_hl, - number_hl_group = opts.highlights.gutter and number_hl or nil, - priority = p.line_bg, - }) - end - - if is_marker and line_len > pw then - pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, pw + qw, { - end_col = line_len + qw, - hl_group = 'DiffsConflictMarker', - priority = p.char_bg, - }) - end - - if char_spans_by_line[i] then - local char_hl = has_add and 'DiffsAddText' or 'DiffsDeleteText' - for _, span in ipairs(char_spans_by_line[i]) do - dbg( - 'char extmark: line=%d buf_line=%d col=%d..%d hl=%s text="%s"', - i, - buf_line, - span.col_start, - span.col_end, - char_hl, - line:sub(span.col_start + 1, span.col_end) - ) - local ok, err = - pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, span.col_start + qw, { - end_col = span.col_end + qw, - hl_group = char_hl, - priority = p.char_bg, - }) - if not ok then - dbg('char extmark FAILED: %s', err) - end - extmark_count = extmark_count + 1 - end - end + if qw > 0 then + pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 0, { + end_col = pw + qw, + hl_group = 'DiffsClear', + priority = p.clear, + }) end if line_len > pw and covered_lines[buf_line] then @@ -605,6 +493,53 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) priority = p.clear, }) end + + if opts.highlights.background and is_diff_line then + pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 0, { + end_row = buf_line + 1, + hl_group = line_hl, + hl_eol = true, + priority = p.line_bg, + }) + if opts.highlights.gutter then + pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 0, { + number_hl_group = number_hl, + priority = p.line_bg, + }) + end + end + + if is_marker and line_len > pw then + pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, pw + qw, { + end_col = line_len + qw, + hl_group = 'DiffsConflictMarker', + priority = p.char_bg, + }) + end + + if char_spans_by_line[i] then + local char_hl = has_add and 'DiffsAddText' or 'DiffsDeleteText' + for _, span in ipairs(char_spans_by_line[i]) do + dbg( + 'char extmark: line=%d buf_line=%d col=%d..%d hl=%s text="%s"', + i, + buf_line, + span.col_start, + span.col_end, + char_hl, + line:sub(span.col_start + 1, span.col_end) + ) + local ok, err = pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, span.col_start + qw, { + end_col = span.col_end + qw, + hl_group = char_hl, + priority = p.char_bg, + }) + if not ok then + dbg('char extmark FAILED: %s', err) + end + extmark_count = extmark_count + 1 + end + end end dbg('hunk %s:%d applied %d extmarks', hunk.filename, hunk.start_line, extmark_count) diff --git a/lua/diffs/init.lua b/lua/diffs/init.lua index 242c247..dcf05b6 100644 --- a/lua/diffs/init.lua +++ b/lua/diffs/init.lua @@ -765,7 +765,7 @@ local function init() if not entry.highlighted[i] then local hunk = entry.hunks[i] local clear_start = hunk.start_line - 1 - local clear_end = hunk.start_line + #hunk.lines + local clear_end = clear_start + #hunk.lines if hunk.header_start_line then clear_start = hunk.header_start_line - 1 end @@ -789,21 +789,22 @@ local function init() end local cur = hunk_cache[bufnr] if not cur or cur.tick ~= tick then - dbg( - 'deferred syntax stale: cur.tick=%s captured=%d', - cur and tostring(cur.tick) or 'nil', - tick - ) + dbg('deferred syntax stale: cur.tick=%s captured=%d', cur and tostring(cur.tick) or 'nil', tick) return end local t1 = config.debug and vim.uv.hrtime() or nil - local syntax_opts = { + local full_opts = { hide_prefix = config.hide_prefix, highlights = config.highlights, - syntax_only = true, } for _, hunk in ipairs(deferred_syntax) do - highlight.highlight_hunk(bufnr, ns, hunk, syntax_opts) + local start_row = hunk.start_line - 1 + local end_row = start_row + #hunk.lines + if hunk.header_start_line then + start_row = hunk.header_start_line - 1 + end + vim.api.nvim_buf_clear_namespace(bufnr, ns, start_row, end_row) + highlight.highlight_hunk(bufnr, ns, hunk, full_opts) end if t1 then dbg('deferred pass: %d hunks in %.2fms', #deferred_syntax, (vim.uv.hrtime() - t1) / 1e6) @@ -952,7 +953,6 @@ M._test = { invalidate_cache = invalidate_cache, hunks_eq = hunks_eq, process_pending_clear = process_pending_clear, - ft_retry_pending = ft_retry_pending, } return M diff --git a/lua/diffs/parser.lua b/lua/diffs/parser.lua index 2ef3030..fa1bcc0 100644 --- a/lua/diffs/parser.lua +++ b/lua/diffs/parser.lua @@ -13,7 +13,6 @@ ---@field file_new_start integer? ---@field file_new_count integer? ---@field prefix_width integer ----@field quote_width integer ---@field repo_root string? local M = {} @@ -61,15 +60,6 @@ local function get_ft_from_filename(filename, repo_root) end local ft = vim.filetype.match({ filename = filename }) - if not ft and vim.fn.did_filetype() ~= 0 then - dbg('retrying filetype match for %s (clearing did_filetype)', filename) - local saved = rawget(vim.fn, 'did_filetype') - rawset(vim.fn, 'did_filetype', function() - return 0 - end) - ft = vim.filetype.match({ filename = filename }) - rawset(vim.fn, 'did_filetype', saved) - end if ft then dbg('filetype from filename: %s', ft) return ft @@ -135,18 +125,6 @@ end function M.parse_buffer(bufnr) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) local repo_root = get_repo_root(bufnr) - - local quote_prefix = nil - local quote_width = 0 - for _, l in ipairs(lines) do - local qp = l:match('^(>+ )diff ') or l:match('^(>+ )@@') - if qp then - quote_prefix = qp - quote_width = #qp - break - end - end - ---@type diffs.Hunk[] local hunks = {} @@ -185,7 +163,6 @@ function M.parse_buffer(bufnr) ---@type integer? local new_remaining = nil local is_unified_diff = false - local current_quote_width = 0 local function flush_hunk() if hunk_start and #hunk_lines > 0 then @@ -198,7 +175,6 @@ function M.parse_buffer(bufnr) header_context_col = hunk_header_context_col, lines = hunk_lines, prefix_width = hunk_prefix_width, - quote_width = current_quote_width, file_old_start = file_old_start, file_old_count = file_old_count, file_new_start = file_new_start, @@ -224,33 +200,18 @@ function M.parse_buffer(bufnr) end for i, line in ipairs(lines) do - local logical = line - if quote_prefix then - if line:sub(1, quote_width) == quote_prefix then - logical = line:sub(quote_width + 1) - elseif line:match('^>+$') then - logical = '' - end - end - - local diff_git_file = logical:match('^diff %-%-git a/.+ b/(.+)$') - or logical:match('^diff %-%-combined (.+)$') - or logical:match('^diff %-%-cc (.+)$') - local neogit_file = logical:match('^modified%s+(.+)$') - or (not logical:match('^new file mode') and logical:match('^new file%s+(.+)$')) - or (not logical:match('^deleted file mode') and logical:match('^deleted%s+(.+)$')) - or logical:match('^renamed%s+(.+)$') - or logical:match('^copied%s+(.+)$') - local bare_file = not hunk_start and logical:match('^([^%s]+%.[^%s]+)$') - local filename = logical:match('^[MADRCU%?!]%s+(.+)$') - or diff_git_file - or neogit_file - or bare_file + local diff_git_file = line:match('^diff %-%-git a/.+ b/(.+)$') + local neogit_file = line:match('^modified%s+(.+)$') + or (not line:match('^new file mode') and line:match('^new file%s+(.+)$')) + or (not line:match('^deleted file mode') and line:match('^deleted%s+(.+)$')) + or line:match('^renamed%s+(.+)$') + or line:match('^copied%s+(.+)$') + local bare_file = not hunk_start and line:match('^([^%s]+%.[^%s]+)$') + local filename = line:match('^[MADRCU%?!]%s+(.+)$') or diff_git_file or neogit_file or bare_file if filename then is_unified_diff = diff_git_file ~= nil flush_hunk() current_filename = filename - current_quote_width = (logical ~= line) and quote_width or 0 local cache_key = (repo_root or '') .. '\0' .. filename local cached = ft_lang_cache[cache_key] if cached then @@ -272,13 +233,13 @@ function M.parse_buffer(bufnr) hunk_prefix_width = 1 header_start = i header_lines = {} - elseif logical:match('^@@+') then + elseif line:match('^@@+') then flush_hunk() hunk_start = i - local at_prefix = logical:match('^(@@+)') + local at_prefix = line:match('^(@@+)') hunk_prefix_width = #at_prefix - 1 if #at_prefix == 2 then - local hs, hc, hs2, hc2 = logical:match('^@@ %-(%d+),?(%d*) %+(%d+),?(%d*) @@') + local hs, hc, hs2, hc2 = line:match('^@@ %-(%d+),?(%d*) %+(%d+),?(%d*) @@') if hs then file_old_start = tonumber(hs) file_old_count = tonumber(hc) or 1 @@ -288,31 +249,24 @@ function M.parse_buffer(bufnr) new_remaining = file_new_count end else - local hs, hc = logical:match('%-(%d+),?(%d*)') - if hs then - file_old_start = tonumber(hs) - file_old_count = tonumber(hc) or 1 - old_remaining = file_old_count - end - local hs2, hc2 = logical:match('%+(%d+),?(%d*) @@') + local hs2, hc2 = line:match('%+(%d+),?(%d*) @@') if hs2 then file_new_start = tonumber(hs2) file_new_count = tonumber(hc2) or 1 - new_remaining = file_new_count end end - local at_end, context = logical:match('^(@@+.-@@+%s*)(.*)') + local at_end, context = line:match('^(@@+.-@@+%s*)(.*)') if context and context ~= '' then hunk_header_context = context - hunk_header_context_col = #at_end + current_quote_width + hunk_header_context_col = #at_end end if hunk_count then hunk_count = hunk_count + 1 end elseif hunk_start then - local prefix = logical:sub(1, 1) + local prefix = line:sub(1, 1) if prefix == ' ' or prefix == '+' or prefix == '-' then - table.insert(hunk_lines, logical) + table.insert(hunk_lines, line) if old_remaining and (prefix == ' ' or prefix == '-') then old_remaining = old_remaining - 1 end @@ -320,21 +274,22 @@ function M.parse_buffer(bufnr) new_remaining = new_remaining - 1 end elseif - logical == '' + line == '' + and is_unified_diff and old_remaining and old_remaining > 0 and new_remaining and new_remaining > 0 then - table.insert(hunk_lines, string.rep(' ', hunk_prefix_width)) + table.insert(hunk_lines, ' ') old_remaining = old_remaining - 1 new_remaining = new_remaining - 1 elseif - logical == '' - or logical:match('^[MADRC%?!]%s+') - or logical:match('^diff ') - or logical:match('^index ') - or logical:match('^Binary ') + line == '' + or line:match('^[MADRC%?!]%s+') + or line:match('^diff ') + or line:match('^index ') + or line:match('^Binary ') then flush_hunk() current_filename = nil @@ -344,7 +299,7 @@ function M.parse_buffer(bufnr) end end if header_start and not hunk_start then - table.insert(header_lines, logical) + table.insert(header_lines, line) end end diff --git a/spec/email_quote_spec.lua b/spec/email_quote_spec.lua deleted file mode 100644 index 3f2bd6e..0000000 --- a/spec/email_quote_spec.lua +++ /dev/null @@ -1,237 +0,0 @@ -require('spec.helpers') -local highlight = require('diffs.highlight') -local parser = require('diffs.parser') - -local function create_buffer(lines) - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - return bufnr -end - -local function delete_buffer(bufnr) - if vim.api.nvim_buf_is_valid(bufnr) then - vim.api.nvim_buf_delete(bufnr, { force = true }) - end -end - -local function get_extmarks(bufnr, ns) - return vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true }) -end - -local function highlight_opts() - return { - hide_prefix = false, - highlights = { - background = true, - gutter = false, - context = { enabled = false, lines = 0 }, - treesitter = { enabled = true, max_lines = 500 }, - vim = { enabled = false, max_lines = 200 }, - intra = { enabled = false, algorithm = 'default', max_lines = 500 }, - priorities = { clear = 198, syntax = 199, line_bg = 200, char_bg = 201 }, - }, - } -end - -describe('parser email-quoted diffs', function() - it('parses a fully email-quoted unified diff', function() - local bufnr = create_buffer({ - '> diff --git a/foo.py b/foo.py', - '> index abc1234..def5678 100644', - '> --- a/foo.py', - '> +++ b/foo.py', - '> @@ -0,0 +1,3 @@', - '> +from typing import Annotated, final', - '> +', - '> +class Foo:', - }) - local hunks = parser.parse_buffer(bufnr) - - assert.are.equal(1, #hunks) - assert.are.equal('foo.py', hunks[1].filename) - assert.are.equal(3, #hunks[1].lines) - assert.are.equal('+from typing import Annotated, final', hunks[1].lines[1]) - assert.are.equal(2, hunks[1].quote_width) - delete_buffer(bufnr) - end) - - it('parses a quoted diff embedded in an email reply', function() - local bufnr = create_buffer({ - 'Looks good, one nit:', - '', - '> diff --git a/foo.py b/foo.py', - '> @@ -0,0 +1,3 @@', - '> +from typing import Annotated, final', - '> +', - '> +class Foo:', - '', - 'Maybe rename Foo to Bar?', - }) - local hunks = parser.parse_buffer(bufnr) - - assert.are.equal(1, #hunks) - assert.are.equal('foo.py', hunks[1].filename) - assert.are.equal(3, #hunks[1].lines) - assert.are.equal(2, hunks[1].quote_width) - delete_buffer(bufnr) - end) - - it('sets quote_width = 0 on normal (unquoted) diffs', function() - local bufnr = create_buffer({ - 'diff --git a/bar.lua b/bar.lua', - '@@ -1,2 +1,2 @@', - '-old_line', - '+new_line', - }) - local hunks = parser.parse_buffer(bufnr) - - assert.are.equal(1, #hunks) - assert.are.equal(0, hunks[1].quote_width) - delete_buffer(bufnr) - end) - - it('treats bare > lines as empty quoted lines', function() - local bufnr = create_buffer({ - '> diff --git a/foo.py b/foo.py', - '> @@ -1,3 +1,3 @@', - '> -old', - '>', - '> +new', - }) - local hunks = parser.parse_buffer(bufnr) - - assert.are.equal(1, #hunks) - assert.are.equal(3, #hunks[1].lines) - assert.are.equal('-old', hunks[1].lines[1]) - assert.are.equal(' ', hunks[1].lines[2]) - assert.are.equal('+new', hunks[1].lines[3]) - delete_buffer(bufnr) - end) - - it('adjusts header_context_col for quote width', function() - local bufnr = create_buffer({ - '> diff --git a/foo.py b/foo.py', - '> @@ -1,2 +1,2 @@ def hello():', - '> -old', - '> +new', - }) - local hunks = parser.parse_buffer(bufnr) - - assert.are.equal(1, #hunks) - assert.are.equal('def hello():', hunks[1].header_context) - assert.are.equal(#'@@ -1,2 +1,2 @@ ' + 2, hunks[1].header_context_col) - delete_buffer(bufnr) - end) - - it('handles deeply nested quotes', function() - local bufnr = create_buffer({ - '>> diff --git a/foo.py b/foo.py', - '>> @@ -0,0 +1,2 @@', - '>> +line1', - '>> +line2', - }) - local hunks = parser.parse_buffer(bufnr) - - assert.are.equal(1, #hunks) - assert.are.equal(3, hunks[1].quote_width) - assert.are.equal('+line1', hunks[1].lines[1]) - delete_buffer(bufnr) - end) -end) - -describe('email-quoted header highlight suppression', function() - before_each(function() - vim.api.nvim_set_hl(0, 'DiffsClear', { fg = 0xc0c0c0, bg = 0x1e1e2e }) - vim.api.nvim_set_hl(0, 'DiffsAdd', { bg = 0x2e4a3a }) - vim.api.nvim_set_hl(0, 'DiffsDelete', { bg = 0x4a2e3a }) - end) - - it('applies DiffsClear to header lines when quote_width > 0', function() - local bufnr = create_buffer({ - '> diff --git a/foo.py b/foo.py', - '> index abc1234..def5678 100644', - '> --- a/foo.py', - '> +++ b/foo.py', - '> @@ -0,0 +1,2 @@', - '> +line1', - '> +line2', - }) - local hunks = parser.parse_buffer(bufnr) - assert.are.equal(1, #hunks) - - local ns = vim.api.nvim_create_namespace('diffs_email_clear_test') - highlight.highlight_hunk(bufnr, ns, hunks[1], highlight_opts()) - - local extmarks = get_extmarks(bufnr, ns) - local clear_lines = {} - for _, mark in ipairs(extmarks) do - local d = mark[4] - if d and d.hl_group == 'DiffsClear' and mark[3] == 0 then - clear_lines[mark[2]] = true - end - end - assert.is_true(clear_lines[0] ~= nil, 'expected DiffsClear on diff --git line') - assert.is_true(clear_lines[1] ~= nil, 'expected DiffsClear on index line') - assert.is_true(clear_lines[2] ~= nil, 'expected DiffsClear on --- line') - assert.is_true(clear_lines[3] ~= nil, 'expected DiffsClear on +++ line') - delete_buffer(bufnr) - end) - - it('applies DiffsClear and diff treesitter to @@ line when quote_width > 0', function() - local bufnr = create_buffer({ - '> diff --git a/foo.py b/foo.py', - '> @@ -0,0 +1,2 @@', - '> +line1', - '> +line2', - }) - local hunks = parser.parse_buffer(bufnr) - assert.are.equal(1, #hunks) - - local ns = vim.api.nvim_create_namespace('diffs_email_at_test') - highlight.highlight_hunk(bufnr, ns, hunks[1], highlight_opts()) - - local extmarks = get_extmarks(bufnr, ns) - local has_at_clear = false - local has_at_ts = false - for _, mark in ipairs(extmarks) do - local d = mark[4] - if mark[2] == 1 and d then - if d.hl_group == 'DiffsClear' and mark[3] == 0 then - has_at_clear = true - end - if d.hl_group and d.hl_group:match('^@.*%.diff$') and d.priority == 199 then - has_at_ts = true - end - end - end - assert.is_true(has_at_clear, 'expected DiffsClear on @@ line') - assert.is_true(has_at_ts, 'expected diff treesitter capture on @@ line') - delete_buffer(bufnr) - end) - - it('does not apply DiffsClear to header lines when quote_width = 0', function() - local bufnr = create_buffer({ - 'diff --git a/foo.py b/foo.py', - 'index abc1234..def5678 100644', - '--- a/foo.py', - '+++ b/foo.py', - '@@ -0,0 +1,2 @@', - '+line1', - '+line2', - }) - local hunks = parser.parse_buffer(bufnr) - assert.are.equal(1, #hunks) - - local ns = vim.api.nvim_create_namespace('diffs_email_noclear_test') - highlight.highlight_hunk(bufnr, ns, hunks[1], highlight_opts()) - - local extmarks = get_extmarks(bufnr, ns) - for _, mark in ipairs(extmarks) do - local d = mark[4] - if d and d.hl_group == 'DiffsClear' and mark[3] == 0 and mark[2] < 5 then - error('unexpected DiffsClear at col 0 on header line ' .. mark[2] .. ' with quote_width=0') - end - end - delete_buffer(bufnr) - end) -end) diff --git a/spec/highlight_spec.lua b/spec/highlight_spec.lua index e3ee12d..00a0774 100644 --- a/spec/highlight_spec.lua +++ b/spec/highlight_spec.lua @@ -287,7 +287,7 @@ describe('highlight', function() local extmarks = get_extmarks(bufnr) local has_diff_add = false for _, mark in ipairs(extmarks) do - if mark[4] and mark[4].line_hl_group == 'DiffsAdd' then + if mark[4] and mark[4].hl_group == 'DiffsAdd' then has_diff_add = true break end @@ -320,7 +320,7 @@ describe('highlight', function() local extmarks = get_extmarks(bufnr) local has_diff_delete = false for _, mark in ipairs(extmarks) do - if mark[4] and mark[4].line_hl_group == 'DiffsDelete' then + if mark[4] and mark[4].hl_group == 'DiffsDelete' then has_diff_delete = true break end @@ -362,124 +362,6 @@ describe('highlight', function() delete_buffer(bufnr) end) - it('line bg uses line_hl_group not hl_group with end_row', function() - local bufnr = create_buffer({ - '@@ -1,1 +1,2 @@', - ' local x = 1', - '+local y = 2', - }) - - local hunk = { - filename = 'test.lua', - lang = 'lua', - start_line = 1, - lines = { ' local x = 1', '+local y = 2' }, - } - - highlight.highlight_hunk( - bufnr, - ns, - hunk, - default_opts({ highlights = { background = true } }) - ) - - local extmarks = get_extmarks(bufnr) - for _, mark in ipairs(extmarks) do - local d = mark[4] - assert.is_not_equal('DiffsAdd', d and d.hl_group) - assert.is_not_equal('DiffsDelete', d and d.hl_group) - end - delete_buffer(bufnr) - end) - - it('line bg extmark survives adjacent clear_namespace starting at next row', function() - local bufnr = create_buffer({ - 'diff --git a/foo.py b/foo.py', - '@@ -1,2 +1,2 @@', - '-old', - '+new', - }) - - local hunk = { - filename = 'foo.py', - header_start_line = 1, - start_line = 2, - lines = { '-old', '+new' }, - prefix_width = 1, - quote_width = 0, - } - - highlight.highlight_hunk( - bufnr, - ns, - hunk, - default_opts({ highlights = { background = true, treesitter = { enabled = false } } }) - ) - - local last_body_row = hunk.start_line + #hunk.lines - 1 - vim.api.nvim_buf_clear_namespace(bufnr, ns, last_body_row + 1, last_body_row + 10) - - local marks = vim.api.nvim_buf_get_extmarks( - bufnr, - ns, - { last_body_row, 0 }, - { last_body_row, -1 }, - { details = true } - ) - local has_line_bg = false - for _, mark in ipairs(marks) do - if mark[4] and mark[4].line_hl_group == 'DiffsAdd' then - has_line_bg = true - end - end - assert.is_true(has_line_bg) - delete_buffer(bufnr) - end) - - it('clear range covers last body line of hunk with header', function() - local bufnr = create_buffer({ - 'diff --git a/foo.py b/foo.py', - 'index abc..def 100644', - '--- a/foo.py', - '+++ b/foo.py', - '@@ -1,3 +1,3 @@', - ' ctx', - '-old', - '+new', - }) - - local hunk = { - filename = 'foo.py', - header_start_line = 1, - start_line = 5, - lines = { ' ctx', '-old', '+new' }, - prefix_width = 1, - quote_width = 0, - } - - highlight.highlight_hunk( - bufnr, - ns, - hunk, - default_opts({ highlights = { background = true, treesitter = { enabled = false } } }) - ) - - local last_body_row = hunk.start_line + #hunk.lines - 1 - local clear_start = hunk.header_start_line - 1 - local clear_end = hunk.start_line + #hunk.lines - vim.api.nvim_buf_clear_namespace(bufnr, ns, clear_start, clear_end) - - local marks = vim.api.nvim_buf_get_extmarks( - bufnr, - ns, - { last_body_row, 0 }, - { last_body_row, -1 }, - { details = false } - ) - assert.are.equal(0, #marks) - delete_buffer(bufnr) - end) - it('still applies background when treesitter disabled', function() local bufnr = create_buffer({ '@@ -1,1 +1,2 @@', @@ -504,7 +386,7 @@ describe('highlight', function() local extmarks = get_extmarks(bufnr) local has_diff_add = false for _, mark in ipairs(extmarks) do - if mark[4] and mark[4].line_hl_group == 'DiffsAdd' then + if mark[4] and mark[4].hl_group == 'DiffsAdd' then has_diff_add = true break end @@ -618,7 +500,7 @@ describe('highlight', function() local extmarks = get_extmarks(bufnr) local has_diff_add = false for _, mark in ipairs(extmarks) do - if mark[4] and mark[4].line_hl_group == 'DiffsAdd' then + if mark[4] and mark[4].hl_group == 'DiffsAdd' then has_diff_add = true break end @@ -703,7 +585,7 @@ describe('highlight', function() local found = false for _, mark in ipairs(extmarks) do local d = mark[4] - if d and (d.line_hl_group == 'DiffsAdd' or d.line_hl_group == 'DiffsDelete') then + if d and (d.hl_group == 'DiffsAdd' or d.hl_group == 'DiffsDelete') and d.hl_eol then found = true end end @@ -859,7 +741,7 @@ describe('highlight', function() if d then if d.hl_group == 'DiffsClear' then table.insert(priorities.clear, d.priority) - elseif d.line_hl_group == 'DiffsAdd' or d.line_hl_group == 'DiffsDelete' then + elseif (d.hl_group == 'DiffsAdd' or d.hl_group == 'DiffsDelete') and d.hl_eol then table.insert(priorities.line_bg, d.priority) elseif d.hl_group == 'DiffsAddText' or d.hl_group == 'DiffsDeleteText' then table.insert(priorities.char_bg, d.priority) @@ -960,8 +842,8 @@ describe('highlight', function() local line_bgs = {} for _, mark in ipairs(extmarks) do local d = mark[4] - if d and (d.line_hl_group == 'DiffsAdd' or d.line_hl_group == 'DiffsDelete') then - line_bgs[mark[2]] = d.line_hl_group + if d and (d.hl_group == 'DiffsAdd' or d.hl_group == 'DiffsDelete') and d.hl_eol then + line_bgs[mark[2]] = d.hl_group end end assert.is_nil(line_bgs[1]) @@ -1064,16 +946,11 @@ describe('highlight', function() highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) local extmarks = get_extmarks(bufnr) - local content_clear_count = 0 for _, mark in ipairs(extmarks) do if mark[4] and mark[4].hl_group == 'DiffsClear' then - assert.is_true(mark[3] == 0 or mark[3] == 2, 'DiffsClear at unexpected col ' .. mark[3]) - if mark[3] == 2 then - content_clear_count = content_clear_count + 1 - end + assert.are.equal(2, mark[3]) end end - assert.are.equal(2, content_clear_count) delete_buffer(bufnr) end) @@ -1157,8 +1034,8 @@ describe('highlight', function() local marker_text = {} for _, mark in ipairs(extmarks) do local d = mark[4] - if d and (d.line_hl_group == 'DiffsAdd' or d.line_hl_group == 'DiffsDelete') then - line_bgs[mark[2]] = d.line_hl_group + if d and (d.hl_group == 'DiffsAdd' or d.hl_group == 'DiffsDelete') and d.hl_eol then + line_bgs[mark[2]] = d.hl_group end if d and d.number_hl_group then gutter_hls[mark[2]] = d.number_hl_group @@ -1251,123 +1128,6 @@ describe('highlight', function() end delete_buffer(bufnr) end) - - it('two-pass rendering produces no duplicate extmarks', function() - vim.api.nvim_set_hl(0, 'DiffsAddText', { bg = 0x00FF00 }) - vim.api.nvim_set_hl(0, 'DiffsDeleteText', { bg = 0xFF0000 }) - vim.api.nvim_set_hl(0, 'DiffsAddNr', { fg = 0x80c080, bg = 0x2e4a3a }) - vim.api.nvim_set_hl(0, 'DiffsDeleteNr', { fg = 0xc08080, bg = 0x4a2e3a }) - - local bufnr = create_buffer({ - '@@ -1,2 +1,2 @@', - '-local x = 1', - '+local x = 2', - }) - - local hunk = { - filename = 'test.lua', - lang = 'lua', - start_line = 1, - lines = { '-local x = 1', '+local x = 2' }, - } - - local fast = default_opts({ - highlights = { - treesitter = { enabled = false }, - background = true, - gutter = true, - intra = { enabled = true, algorithm = 'default', max_lines = 500 }, - }, - }) - - local syntax = default_opts({ - highlights = { - treesitter = { enabled = true }, - background = true, - gutter = true, - intra = { enabled = true, algorithm = 'default', max_lines = 500 }, - }, - }) - syntax.syntax_only = true - - highlight.highlight_hunk(bufnr, ns, hunk, fast) - highlight.highlight_hunk(bufnr, ns, hunk, syntax) - - local extmarks = get_extmarks(bufnr) - for row = 1, 2 do - local line_hl_count = 0 - local number_hl_count = 0 - local intra_count = 0 - for _, mark in ipairs(extmarks) do - if mark[2] == row then - local d = mark[4] - if d.line_hl_group then - line_hl_count = line_hl_count + 1 - end - if d.number_hl_group then - number_hl_count = number_hl_count + 1 - end - if d.hl_group == 'DiffsAddText' or d.hl_group == 'DiffsDeleteText' then - intra_count = intra_count + 1 - end - end - end - assert.are.equal(1, line_hl_count, 'row ' .. row .. ' has duplicate line_hl_group') - assert.are.equal(1, number_hl_count, 'row ' .. row .. ' has duplicate number_hl_group') - assert.is_true(intra_count <= 1, 'row ' .. row .. ' has duplicate intra extmarks') - end - delete_buffer(bufnr) - end) - - it('syntax_only pass adds treesitter without duplicating backgrounds', function() - local bufnr = create_buffer({ - '@@ -1,2 +1,3 @@', - ' local x = 1', - '+local y = 2', - ' return x', - }) - - local hunk = { - filename = 'test.lua', - lang = 'lua', - start_line = 1, - lines = { ' local x = 1', '+local y = 2', ' return x' }, - } - - local fast = default_opts({ - highlights = { - treesitter = { enabled = false }, - background = true, - }, - }) - - local syntax = default_opts({ - highlights = { - treesitter = { enabled = true }, - background = true, - }, - }) - syntax.syntax_only = true - - highlight.highlight_hunk(bufnr, ns, hunk, fast) - highlight.highlight_hunk(bufnr, ns, hunk, syntax) - - local extmarks = get_extmarks(bufnr) - local has_ts = false - local line_hl_count = 0 - for _, mark in ipairs(extmarks) do - local d = mark[4] - if d and d.hl_group and d.hl_group:match('^@.*%.lua$') then - has_ts = true - end - if d and d.line_hl_group then - line_hl_count = line_hl_count + 1 - end - end - assert.is_true(has_ts) - assert.are.equal(1, line_hl_count) - delete_buffer(bufnr) - end) end) describe('diff header highlighting', function() @@ -1485,467 +1245,6 @@ describe('highlight', function() assert.are.equal(0, header_extmarks) delete_buffer(bufnr) end) - - it('does not apply DiffsClear to header lines for non-quoted diffs', function() - local bufnr = create_buffer({ - 'diff --git a/parser.lua b/parser.lua', - 'index 3e8afa0..018159c 100644', - '--- a/parser.lua', - '+++ b/parser.lua', - '@@ -1,2 +1,3 @@', - ' local M = {}', - '+local x = 1', - }) - - local hunk = { - filename = 'parser.lua', - lang = 'lua', - start_line = 5, - lines = { ' local M = {}', '+local x = 1' }, - header_start_line = 1, - header_lines = { - 'diff --git a/parser.lua b/parser.lua', - 'index 3e8afa0..018159c 100644', - '--- a/parser.lua', - '+++ b/parser.lua', - }, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - for _, mark in ipairs(extmarks) do - local d = mark[4] - if d and d.hl_group == 'DiffsClear' and mark[3] == 0 and mark[2] < 4 then - error('unexpected DiffsClear on header row ' .. mark[2] .. ' for non-quoted diff') - end - end - delete_buffer(bufnr) - end) - - it('preserves diff grammar treesitter on headers for non-quoted diffs', function() - local bufnr = create_buffer({ - 'diff --git a/parser.lua b/parser.lua', - '--- a/parser.lua', - '+++ b/parser.lua', - '@@ -1,2 +1,3 @@', - ' local M = {}', - '+local x = 1', - }) - - local hunk = { - filename = 'parser.lua', - lang = 'lua', - start_line = 4, - lines = { ' local M = {}', '+local x = 1' }, - header_start_line = 1, - header_lines = { - 'diff --git a/parser.lua b/parser.lua', - '--- a/parser.lua', - '+++ b/parser.lua', - }, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - local header_ts_count = 0 - for _, mark in ipairs(extmarks) do - local d = mark[4] - if mark[2] < 3 and d and d.hl_group and d.hl_group:match('^@.*%.diff$') then - header_ts_count = header_ts_count + 1 - end - end - assert.is_true(header_ts_count > 0, 'expected diff grammar treesitter on header lines') - delete_buffer(bufnr) - end) - - it('applies syntax extmarks to combined diff body lines', function() - local bufnr = create_buffer({ - '@@@ -1,2 -1,2 +1,3 @@@', - ' local M = {}', - '+ local x = 1', - ' -local y = 2', - }) - - local hunk = { - filename = 'test.lua', - lang = 'lua', - prefix_width = 2, - start_line = 1, - lines = { ' local M = {}', '+ local x = 1', ' -local y = 2' }, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - local syntax_on_body = 0 - for _, mark in ipairs(extmarks) do - local d = mark[4] - if mark[2] >= 1 and d and d.hl_group and d.hl_group:match('^@.*%.lua$') then - syntax_on_body = syntax_on_body + 1 - end - end - assert.is_true(syntax_on_body > 0, 'expected lua treesitter syntax on combined diff body') - delete_buffer(bufnr) - end) - - it('applies DiffsClear and per-char diff fg to combined diff body prefixes', function() - local bufnr = create_buffer({ - '@@@', - ' unchanged', - '+ added', - ' -removed', - '++both', - }) - - local hunk = { - filename = 'test.lua', - lang = 'lua', - prefix_width = 2, - start_line = 1, - lines = { ' unchanged', '+ added', ' -removed', '++both' }, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - local prefix_clears = {} - local plus_marks = {} - local minus_marks = {} - for _, mark in ipairs(extmarks) do - local d = mark[4] - if mark[2] >= 1 and d then - if d.hl_group == 'DiffsClear' and mark[3] == 0 and d.end_col == 2 then - prefix_clears[mark[2]] = true - end - if d.hl_group == '@diff.plus' and d.priority == 199 then - if not plus_marks[mark[2]] then - plus_marks[mark[2]] = {} - end - table.insert(plus_marks[mark[2]], mark[3]) - end - if d.hl_group == '@diff.minus' and d.priority == 199 then - if not minus_marks[mark[2]] then - minus_marks[mark[2]] = {} - end - table.insert(minus_marks[mark[2]], mark[3]) - end - end - end - - assert.is_true(prefix_clears[1] ~= nil, 'DiffsClear on context prefix') - assert.is_true(prefix_clears[2] ~= nil, 'DiffsClear on add prefix') - assert.is_true(prefix_clears[3] ~= nil, 'DiffsClear on del prefix') - assert.is_true(prefix_clears[4] ~= nil, 'DiffsClear on both-add prefix') - - assert.is_true(plus_marks[2] ~= nil, '@diff.plus on + in "+ added"') - assert.are.equal(0, plus_marks[2][1]) - - assert.is_true(minus_marks[3] ~= nil, '@diff.minus on - in " -removed"') - assert.are.equal(1, minus_marks[3][1]) - - assert.is_true(plus_marks[4] ~= nil, '@diff.plus on ++ in "++both"') - assert.are.equal(2, #plus_marks[4]) - - assert.is_nil(plus_marks[1], 'no @diff.plus on context " unchanged"') - assert.is_nil(minus_marks[1], 'no @diff.minus on context " unchanged"') - delete_buffer(bufnr) - end) - - it('applies DiffsClear to headers for combined diffs', function() - local bufnr = create_buffer({ - 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', - '--- a/lua/merge/target.lua', - '+++ b/lua/merge/target.lua', - '@@@ -1,2 -1,2 +1,3 @@@', - ' local M = {}', - '+ local x = 1', - }) - - local hunk = { - filename = 'lua/merge/target.lua', - lang = 'lua', - prefix_width = 2, - start_line = 5, - lines = { ' local M = {}', '+ local x = 1' }, - header_start_line = 1, - header_lines = { - 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', - '--- a/lua/merge/target.lua', - '+++ b/lua/merge/target.lua', - }, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - local clear_lines = {} - for _, mark in ipairs(extmarks) do - local d = mark[4] - if d and d.hl_group == 'DiffsClear' and mark[3] == 0 and mark[2] < 4 then - clear_lines[mark[2]] = true - end - end - assert.is_true(clear_lines[0] ~= nil, 'DiffsClear on diff --combined line') - assert.is_true(clear_lines[1] ~= nil, 'DiffsClear on index line') - assert.is_true(clear_lines[2] ~= nil, 'DiffsClear on --- line') - assert.is_true(clear_lines[3] ~= nil, 'DiffsClear on +++ line') - delete_buffer(bufnr) - end) - - it('applies @attribute.diff at syntax priority to @@@ line for combined diffs', function() - local bufnr = create_buffer({ - '@@@ -1,2 -1,2 +1,3 @@@', - ' local M = {}', - '+ local x = 1', - }) - - local hunk = { - filename = 'test.lua', - lang = 'lua', - prefix_width = 2, - start_line = 1, - lines = { ' local M = {}', '+ local x = 1' }, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - local has_attr = false - for _, mark in ipairs(extmarks) do - local d = mark[4] - if mark[2] == 0 and d and d.hl_group == '@attribute.diff' and (d.priority or 0) >= 199 then - has_attr = true - end - end - assert.is_true(has_attr, '@attribute.diff at p>=199 on @@@ line') - delete_buffer(bufnr) - end) - - it('applies DiffsClear to @@@ line for combined diffs', function() - local bufnr = create_buffer({ - '@@@ -1,2 -1,2 +1,3 @@@', - ' local M = {}', - '+ local x = 1', - }) - - local hunk = { - filename = 'test.lua', - lang = 'lua', - prefix_width = 2, - start_line = 1, - lines = { ' local M = {}', '+ local x = 1' }, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - local has_at_clear = false - for _, mark in ipairs(extmarks) do - local d = mark[4] - if mark[2] == 0 and d and d.hl_group == 'DiffsClear' and mark[3] == 0 then - has_at_clear = true - end - end - assert.is_true(has_at_clear, 'DiffsClear on @@@ line') - delete_buffer(bufnr) - end) - - it('applies header diff grammar at syntax priority for combined diffs', function() - local bufnr = create_buffer({ - 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', - '--- a/lua/merge/target.lua', - '+++ b/lua/merge/target.lua', - '@@@ -1,2 -1,2 +1,3 @@@', - ' local M = {}', - '+ local x = 1', - }) - - local hunk = { - filename = 'lua/merge/target.lua', - lang = 'lua', - prefix_width = 2, - start_line = 5, - lines = { ' local M = {}', '+ local x = 1' }, - header_start_line = 1, - header_lines = { - 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', - '--- a/lua/merge/target.lua', - '+++ b/lua/merge/target.lua', - }, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - local high_prio_diff = {} - for _, mark in ipairs(extmarks) do - local d = mark[4] - if - mark[2] < 4 - and d - and d.hl_group - and d.hl_group:match('^@.*%.diff$') - and (d.priority or 0) >= 199 - then - high_prio_diff[mark[2]] = true - end - end - assert.is_true(high_prio_diff[2] ~= nil, 'diff grammar at p>=199 on --- line') - assert.is_true(high_prio_diff[3] ~= nil, 'diff grammar at p>=199 on +++ line') - delete_buffer(bufnr) - end) - - it('@diff.minus wins over @punctuation.special on combined diff headers', function() - local bufnr = create_buffer({ - 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', - '--- a/lua/merge/target.lua', - '+++ b/lua/merge/target.lua', - '@@@ -1,2 -1,2 +1,3 @@@', - ' local M = {}', - '+ local x = 1', - }) - - local hunk = { - filename = 'lua/merge/target.lua', - lang = 'lua', - prefix_width = 2, - start_line = 5, - lines = { ' local M = {}', '+ local x = 1' }, - header_start_line = 1, - header_lines = { - 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', - '--- a/lua/merge/target.lua', - '+++ b/lua/merge/target.lua', - }, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - local minus_prio, punct_prio_minus = 0, 0 - local plus_prio, punct_prio_plus = 0, 0 - for _, mark in ipairs(extmarks) do - local d = mark[4] - if d and d.hl_group then - if mark[2] == 2 then - if d.hl_group == '@diff.minus.diff' then - minus_prio = math.max(minus_prio, d.priority or 0) - elseif d.hl_group == '@punctuation.special.diff' then - punct_prio_minus = math.max(punct_prio_minus, d.priority or 0) - end - elseif mark[2] == 3 then - if d.hl_group == '@diff.plus.diff' then - plus_prio = math.max(plus_prio, d.priority or 0) - elseif d.hl_group == '@punctuation.special.diff' then - punct_prio_plus = math.max(punct_prio_plus, d.priority or 0) - end - end - end - end - assert.is_true( - minus_prio > punct_prio_minus, - '@diff.minus.diff should beat @punctuation.special.diff on --- line' - ) - assert.is_true( - plus_prio > punct_prio_plus, - '@diff.plus.diff should beat @punctuation.special.diff on +++ line' - ) - delete_buffer(bufnr) - end) - - it('applies @keyword.diff on index word for combined diffs', function() - local bufnr = create_buffer({ - 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', - '--- a/lua/merge/target.lua', - '+++ b/lua/merge/target.lua', - '@@@ -1,2 -1,2 +1,3 @@@', - ' local M = {}', - '+ local x = 1', - }) - - local hunk = { - filename = 'lua/merge/target.lua', - lang = 'lua', - prefix_width = 2, - start_line = 5, - lines = { ' local M = {}', '+ local x = 1' }, - header_start_line = 1, - header_lines = { - 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', - '--- a/lua/merge/target.lua', - '+++ b/lua/merge/target.lua', - }, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - local has_keyword = false - for _, mark in ipairs(extmarks) do - local d = mark[4] - if - mark[2] == 1 - and d - and d.hl_group == '@keyword.diff' - and mark[3] == 0 - and (d.end_col or 0) == 5 - then - has_keyword = true - end - end - assert.is_true(has_keyword, '@keyword.diff at row 1, cols 0-5') - delete_buffer(bufnr) - end) - - it('applies @constant.diff on result hash for combined diffs', function() - local bufnr = create_buffer({ - 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', - '--- a/lua/merge/target.lua', - '+++ b/lua/merge/target.lua', - '@@@ -1,2 -1,2 +1,3 @@@', - ' local M = {}', - '+ local x = 1', - }) - - local hunk = { - filename = 'lua/merge/target.lua', - lang = 'lua', - prefix_width = 2, - start_line = 5, - lines = { ' local M = {}', '+ local x = 1' }, - header_start_line = 1, - header_lines = { - 'diff --combined lua/merge/target.lua', - 'index abc1234,def5678..ghi9012', - '--- a/lua/merge/target.lua', - '+++ b/lua/merge/target.lua', - }, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - local has_constant = false - for _, mark in ipairs(extmarks) do - local d = mark[4] - if mark[2] == 1 and d and d.hl_group == '@constant.diff' and (d.priority or 0) >= 199 then - has_constant = true - end - end - assert.is_true(has_constant, '@constant.diff on result hash') - delete_buffer(bufnr) - end) end) describe('extmark priority', function() diff --git a/spec/integration_spec.lua b/spec/integration_spec.lua index 23e870b..7468e8f 100644 --- a/spec/integration_spec.lua +++ b/spec/integration_spec.lua @@ -127,110 +127,6 @@ describe('integration', function() end) end) - describe('ft_retry_pending', function() - before_each(function() - rawset(vim.fn, 'did_filetype', function() - return 1 - end) - require('diffs.parser')._test.ft_lang_cache = {} - end) - - after_each(function() - rawset(vim.fn, 'did_filetype', nil) - end) - - it('sets ft_retry_pending when nil-ft hunks detected under did_filetype', function() - local bufnr = create_buffer({ - 'diff --git a/app.conf b/app.conf', - '@@ -1,2 +1,2 @@', - ' server {', - '- listen 80;', - '+ listen 8080;', - }) - diffs.attach(bufnr) - local entry = diffs._test.hunk_cache[bufnr] - assert.is_not_nil(entry) - assert.is_nil(entry.hunks[1].ft) - assert.is_true(diffs._test.ft_retry_pending[bufnr] == true) - delete_buffer(bufnr) - end) - - it('clears ft_retry_pending after scheduled callback fires', function() - local bufnr = create_buffer({ - 'diff --git a/app.conf b/app.conf', - '@@ -1,2 +1,2 @@', - ' server {', - '- listen 80;', - '+ listen 8080;', - }) - diffs.attach(bufnr) - assert.is_true(diffs._test.ft_retry_pending[bufnr] == true) - - local done = false - vim.schedule(function() - done = true - end) - vim.wait(1000, function() - return done - end) - - assert.is_nil(diffs._test.ft_retry_pending[bufnr]) - delete_buffer(bufnr) - end) - - it('invalidates cache after scheduled callback fires', function() - local bufnr = create_buffer({ - 'diff --git a/app.conf b/app.conf', - '@@ -1,2 +1,2 @@', - ' server {', - '- listen 80;', - '+ listen 8080;', - }) - diffs.attach(bufnr) - local tick_after_attach = diffs._test.hunk_cache[bufnr].tick - assert.is_true(tick_after_attach >= 0) - - local done = false - vim.schedule(function() - done = true - end) - vim.wait(1000, function() - return done - end) - - local entry = diffs._test.hunk_cache[bufnr] - assert.are.equal(-1, entry.tick) - assert.is_true(entry.pending_clear) - delete_buffer(bufnr) - end) - - it('does not set ft_retry_pending when did_filetype() is zero', function() - rawset(vim.fn, 'did_filetype', nil) - local bufnr = create_buffer({ - 'diff --git a/test.sh b/test.sh', - '@@ -1,2 +1,3 @@', - ' #!/usr/bin/env bash', - '-old line', - '+new line', - }) - diffs.attach(bufnr) - assert.is_falsy(diffs._test.ft_retry_pending[bufnr]) - delete_buffer(bufnr) - end) - - it('does not set ft_retry_pending for files with resolvable ft', function() - local bufnr = create_buffer({ - 'M test.lua', - '@@ -1,1 +1,2 @@', - ' local x = 1', - '+local y = 2', - }) - diffs.attach(bufnr) - assert.is_falsy(diffs._test.ft_retry_pending[bufnr]) - delete_buffer(bufnr) - end) - end) - describe('extmarks from highlight pipeline', function() it('DiffsAdd background applied to + lines', function() local bufnr = create_buffer({ @@ -249,7 +145,7 @@ describe('integration', function() local extmarks = get_extmarks(bufnr, ns) local has_diff_add = false for _, mark in ipairs(extmarks) do - if mark[4] and mark[4].line_hl_group == 'DiffsAdd' then + if mark[4] and mark[4].hl_group == 'DiffsAdd' then has_diff_add = true break end @@ -275,7 +171,7 @@ describe('integration', function() local extmarks = get_extmarks(bufnr, ns) local has_diff_delete = false for _, mark in ipairs(extmarks) do - if mark[4] and mark[4].line_hl_group == 'DiffsDelete' then + if mark[4] and mark[4].hl_group == 'DiffsDelete' then has_diff_delete = true break end @@ -302,10 +198,10 @@ describe('integration', function() local has_add = false local has_delete = false for _, mark in ipairs(extmarks) do - if mark[4] and mark[4].line_hl_group == 'DiffsAdd' then + if mark[4] and mark[4].hl_group == 'DiffsAdd' then has_add = true end - if mark[4] and mark[4].line_hl_group == 'DiffsDelete' then + if mark[4] and mark[4].hl_group == 'DiffsDelete' then has_delete = true end end @@ -334,8 +230,8 @@ describe('integration', function() local line_bgs = {} for _, mark in ipairs(extmarks) do local d = mark[4] - if d and (d.line_hl_group == 'DiffsAdd' or d.line_hl_group == 'DiffsDelete') then - line_bgs[mark[2]] = d.line_hl_group + if d and (d.hl_group == 'DiffsAdd' or d.hl_group == 'DiffsDelete') and d.hl_eol then + line_bgs[mark[2]] = d.hl_group end end assert.is_nil(line_bgs[1]) @@ -417,10 +313,10 @@ describe('integration', function() local del_lines = {} for _, mark in ipairs(extmarks) do local d = mark[4] - if d and d.line_hl_group == 'DiffsAdd' then + if d and d.hl_group == 'DiffsAdd' and d.hl_eol then add_lines[mark[2]] = true end - if d and d.line_hl_group == 'DiffsDelete' then + if d and d.hl_group == 'DiffsDelete' and d.hl_eol then del_lines[mark[2]] = true end end diff --git a/spec/parser_spec.lua b/spec/parser_spec.lua index adbbd37..5613784 100644 --- a/spec/parser_spec.lua +++ b/spec/parser_spec.lua @@ -163,10 +163,10 @@ describe('parser', function() end end) - it('stops hunk at blank line when remaining counts exhausted', function() + it('stops hunk at blank line', function() local bufnr = create_buffer({ 'M test.lua', - '@@ -1,1 +1,2 @@', + '@@ -1,2 +1,3 @@', ' local x = 1', '+local y = 2', '', @@ -391,29 +391,6 @@ describe('parser', function() vim.fn.delete(repo_root, 'rf') end) - it('detects filetype for .sh files when did_filetype() is non-zero', function() - rawset(vim.fn, 'did_filetype', function() - return 1 - end) - - parser._test.ft_lang_cache = {} - local bufnr = create_buffer({ - 'diff --git a/test.sh b/test.sh', - '@@ -1,3 +1,4 @@', - ' #!/usr/bin/env bash', - ' set -euo pipefail', - '-echo "running tests..."', - '+echo "running tests with coverage..."', - }) - local hunks = parser.parse_buffer(bufnr) - - assert.are.equal(1, #hunks) - assert.are.equal('test.sh', hunks[1].filename) - assert.are.equal('sh', hunks[1].ft) - delete_buffer(bufnr) - rawset(vim.fn, 'did_filetype', nil) - end) - it('extracts file line numbers from @@ header', function() local bufnr = create_buffer({ 'M lua/test.lua', @@ -529,8 +506,7 @@ describe('parser', function() assert.are.equal(1, #hunks) assert.are.equal(1, hunks[1].file_new_start) assert.are.equal(9, hunks[1].file_new_count) - assert.are.equal(1, hunks[1].file_old_start) - assert.are.equal(3, hunks[1].file_old_count) + assert.is_nil(hunks[1].file_old_start) delete_buffer(bufnr) end)