diff --git a/lua/diffs/highlight.lua b/lua/diffs/highlight.lua index 3190641..836a9f4 100644 --- a/lua/diffs/highlight.lua +++ b/lua/diffs/highlight.lua @@ -245,7 +245,7 @@ local function highlight_vim_syntax( pcall(vim.api.nvim_buf_delete, scratch, { force = true }) local hunk_line_count = #hunk.lines - local col_off = (hunk.prefix_width or 1) + (hunk.quote_width or 0) - 1 + local col_off = (hunk.prefix_width or 1) - 1 local extmark_count = 0 for _, span in ipairs(spans) do local adj = span.line - leading_offset @@ -273,7 +273,6 @@ end function M.highlight_hunk(bufnr, ns, hunk, opts) local p = opts.highlights.priorities local pw = hunk.prefix_width or 1 - local qw = hunk.quote_width or 0 local use_ts = hunk.lang and opts.highlights.treesitter.enabled local use_vim = not use_ts and hunk.ft and opts.highlights.vim.enabled @@ -330,9 +329,9 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) end extmark_count = - highlight_treesitter(bufnr, ns, new_code, hunk.lang, new_map, pw + qw, covered_lines, p) + highlight_treesitter(bufnr, ns, new_code, hunk.lang, new_map, pw, covered_lines, p) extmark_count = extmark_count - + highlight_treesitter(bufnr, ns, old_code, hunk.lang, old_map, pw + qw, covered_lines, p) + + highlight_treesitter(bufnr, ns, old_code, hunk.lang, old_map, pw, covered_lines, p) if hunk.header_context and hunk.header_context_col then local header_extmarks = highlight_text( @@ -371,21 +370,11 @@ 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, 0, nil, p, pw > 1) end local at_raw_line - if (qw > 0 or pw > 1) and opts.highlights.treesitter.enabled then + if pw > 1 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] end @@ -431,7 +420,7 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) end if - (qw > 0 or pw > 1) + pw > 1 and hunk.header_start_line and hunk.header_lines and #hunk.header_lines > 0 @@ -440,37 +429,35 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) for i = 0, #hunk.header_lines - 1 do local buf_line = hunk.header_start_line - 1 + i pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 0, { - end_col = #hunk.header_lines[i + 1] + qw, + end_col = #hunk.header_lines[i + 1], 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 = 5 + qw, - 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, dot_pos + 1 + qw, { - end_col = dot_pos + 1 + #hash + qw, - hl_group = '@constant.diff', - priority = p.syntax, - }) - end + local hline = hunk.header_lines[i + 1] + if hline:match('^index ') then + pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 0, { + end_col = 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, dot_pos + 1, { + end_col = dot_pos + 1 + #hash, + hl_group = '@constant.diff', + priority = p.syntax, + }) end end end end end - if (qw > 0 or pw > 1) and at_raw_line then + if pw > 1 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, @@ -478,7 +465,7 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) priority = p.clear, }) if opts.highlights.treesitter.enabled then - pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, at_buf_line, qw, { + pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, at_buf_line, 0, { end_col = #at_raw_line, hl_group = '@attribute.diff', priority = p.syntax, @@ -495,16 +482,9 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) }) end - local raw_body_lines - if qw > 0 then - raw_body_lines = - vim.api.nvim_buf_get_lines(bufnr, hunk.start_line, hunk.start_line + #hunk.lines, false) - end - for i, line in ipairs(hunk.lines) do local buf_line = hunk.start_line + i - 1 local line_len = #line - local raw_len = raw_body_lines and #raw_body_lines[i] or nil local prefix = line:sub(1, pw) local has_add = prefix:find('+', 1, true) ~= nil local has_del = prefix:find('-', 1, true) ~= nil @@ -525,30 +505,22 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) 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 = { { string.rep(' ', pw), virt_hl } }, virt_text_pos = 'overlay', }) end - if qw > 0 or pw > 1 then - local prefix_end = pw + qw - if raw_len and prefix_end > raw_len then - prefix_end = raw_len - end + if pw > 1 then pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, 0, { - end_col = prefix_end, + end_col = pw, hl_group = 'DiffsClear', priority = p.clear, }) for ci = 0, pw - 1 do local ch = line:sub(ci + 1, ci + 1) if ch == '+' or ch == '-' then - local char_col = ci + qw - if raw_len and char_col >= raw_len then - break - end - pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, char_col, { - end_col = char_col + 1, + pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, ci, { + end_col = ci + 1, hl_group = ch == '+' and '@diff.plus' or '@diff.minus', priority = p.syntax, }) @@ -565,8 +537,8 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) 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, + pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, pw, { + end_col = line_len, hl_group = 'DiffsConflictMarker', priority = p.char_bg, }) @@ -584,12 +556,11 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) 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, - }) + local ok, err = pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, span.col_start, { + end_col = span.col_end, + hl_group = char_hl, + priority = p.char_bg, + }) if not ok then dbg('char extmark FAILED: %s', err) end @@ -599,8 +570,8 @@ function M.highlight_hunk(bufnr, ns, hunk, opts) end if line_len > pw and covered_lines[buf_line] then - pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, pw + qw, { - end_col = line_len + qw, + pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, buf_line, pw, { + end_col = line_len, hl_group = 'DiffsClear', priority = p.clear, }) diff --git a/lua/diffs/parser.lua b/lua/diffs/parser.lua index 3df3d4c..a32e563 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 = {} @@ -136,17 +135,6 @@ 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 = {} @@ -184,7 +172,6 @@ function M.parse_buffer(bufnr) local old_remaining = nil ---@type integer? local new_remaining = nil - local current_quote_width = 0 local function flush_hunk() if hunk_start and #hunk_lines > 0 then @@ -197,7 +184,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, @@ -223,32 +209,19 @@ 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/(.+)$') + or line:match('^diff %-%-combined (.+)$') + or line:match('^diff %-%-cc (.+)$') + 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 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 @@ -270,13 +243,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 @@ -286,31 +259,31 @@ function M.parse_buffer(bufnr) new_remaining = file_new_count end else - local hs, hc = logical:match('%-(%d+),?(%d*)') + local hs, hc = line: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 @@ -318,7 +291,7 @@ function M.parse_buffer(bufnr) new_remaining = new_remaining - 1 end elseif - logical == '' + line == '' and old_remaining and old_remaining > 0 and new_remaining @@ -328,11 +301,11 @@ function M.parse_buffer(bufnr) 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 @@ -342,7 +315,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 e1744a1..0000000 --- a/spec/email_quote_spec.lua +++ /dev/null @@ -1,477 +0,0 @@ -require('spec.helpers') -local highlight = require('diffs.highlight') -local parser = require('diffs.parser') - -describe('email-quoted diffs', function() - 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 - - describe('parser', 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('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) - - 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('does not false-positive on prose containing > diff', function() - local bufnr = create_buffer({ - '> diff between approaches is small', - '> I think we should go with option A', - }) - local hunks = parser.parse_buffer(bufnr) - - assert.are.equal(0, #hunks) - delete_buffer(bufnr) - end) - - it('stores header lines stripped of quote prefix', function() - local bufnr = create_buffer({ - '> diff --git a/foo.lua b/foo.lua', - '> index abc1234..def5678 100644', - '> --- a/foo.lua', - '> +++ b/foo.lua', - '> @@ -1,1 +1,1 @@', - '> -old', - '> +new', - }) - local hunks = parser.parse_buffer(bufnr) - - assert.are.equal(1, #hunks) - assert.is_not_nil(hunks[1].header_lines) - for _, hline in ipairs(hunks[1].header_lines) do - assert.is_nil(hline:match('^> ')) - end - delete_buffer(bufnr) - end) - end) - - describe('highlight', function() - local ns - - before_each(function() - ns = vim.api.nvim_create_namespace('diffs_email_test') - vim.api.nvim_set_hl(0, 'DiffsClear', { fg = 0xc0c0c0, bg = 0x1e1e1e }) - vim.api.nvim_set_hl(0, 'DiffsAdd', { bg = 0x1a3a1a }) - vim.api.nvim_set_hl(0, 'DiffsDelete', { bg = 0x3a1a1a }) - vim.api.nvim_set_hl(0, 'DiffsConflictMarker', { fg = 0x808080, bold = true }) - end) - - local function get_extmarks(bufnr) - return vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true }) - end - - local function default_opts(overrides) - local opts = { - 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, - }, - }, - } - if overrides then - if overrides.highlights then - opts.highlights = vim.tbl_deep_extend('force', opts.highlights, overrides.highlights) - end - for k, v in pairs(overrides) do - if k ~= 'highlights' then - opts[k] = v - end - end - end - return opts - end - - it('applies DiffsClear on email-quoted header lines covering full buffer width', function() - local buf_lines = { - '> diff --git a/foo.lua b/foo.lua', - '> index abc1234..def5678 100644', - '> --- a/foo.lua', - '> +++ b/foo.lua', - '> @@ -1,1 +1,1 @@', - '> -old', - '> +new', - } - local bufnr = create_buffer(buf_lines) - - local hunk = { - filename = 'foo.lua', - lang = 'lua', - ft = 'lua', - start_line = 5, - lines = { '-old', '+new' }, - prefix_width = 1, - quote_width = 2, - header_start_line = 1, - header_lines = { - 'diff --git a/foo.lua b/foo.lua', - 'index abc1234..def5678 100644', - '--- a/foo.lua', - '+++ b/foo.lua', - }, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - local header_clears = {} - for _, mark in ipairs(extmarks) do - local d = mark[4] - if d and d.hl_group == 'DiffsClear' and mark[2] < 4 then - table.insert(header_clears, { row = mark[2], col = mark[3], end_col = d.end_col }) - end - end - assert.is_true(#header_clears > 0) - for _, c in ipairs(header_clears) do - assert.are.equal(0, c.col) - local buf_line_len = #buf_lines[c.row + 1] - assert.are.equal(buf_line_len, c.end_col) - end - - delete_buffer(bufnr) - end) - - it('applies body prefix DiffsClear covering [0, pw+qw)', function() - local bufnr = create_buffer({ - '> @@ -1,1 +1,1 @@', - '> -old', - '> +new', - }) - - local hunk = { - filename = 'foo.lua', - lang = 'lua', - ft = 'lua', - start_line = 1, - lines = { '-old', '+new' }, - prefix_width = 1, - quote_width = 2, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - local prefix_clears = {} - for _, mark in ipairs(extmarks) do - local d = mark[4] - if d and d.hl_group == 'DiffsClear' and d.end_col == 3 and mark[3] == 0 then - table.insert(prefix_clears, { row = mark[2] }) - end - end - assert.are.equal(2, #prefix_clears) - - delete_buffer(bufnr) - end) - - it('clamps body prefix DiffsClear on bare > lines (1-byte buffer line)', function() - local bufnr = create_buffer({ - '> @@ -1,3 +1,3 @@', - '> -old', - '>', - '> +new', - }) - - local hunk = { - filename = 'foo.lua', - ft = 'lua', - lang = 'lua', - start_line = 1, - lines = { '-old', ' ', '+new' }, - prefix_width = 1, - quote_width = 2, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - local bare_line_row = 2 - local bare_clears = {} - for _, mark in ipairs(extmarks) do - local d = mark[4] - if d and d.hl_group == 'DiffsClear' and mark[2] == bare_line_row and mark[3] == 0 then - table.insert(bare_clears, { end_col = d.end_col }) - end - end - assert.are.equal(1, #bare_clears) - assert.are.equal(1, bare_clears[1].end_col) - - delete_buffer(bufnr) - end) - - it('applies per-char @diff.plus/@diff.minus at ci + qw', function() - local bufnr = create_buffer({ - '> @@ -1,1 +1,1 @@', - '> -old', - '> +new', - }) - - local hunk = { - filename = 'foo.lua', - lang = 'lua', - ft = 'lua', - start_line = 1, - lines = { '-old', '+new' }, - prefix_width = 1, - quote_width = 2, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - local diff_marks = {} - for _, mark in ipairs(extmarks) do - local d = mark[4] - if d and (d.hl_group == '@diff.plus' or d.hl_group == '@diff.minus') then - table.insert( - diff_marks, - { row = mark[2], col = mark[3], end_col = d.end_col, hl = d.hl_group } - ) - end - end - assert.is_true(#diff_marks >= 2) - for _, dm in ipairs(diff_marks) do - assert.are.equal(2, dm.col) - assert.are.equal(3, dm.end_col) - end - - delete_buffer(bufnr) - end) - - it('offsets treesitter extmarks by pw + qw', function() - local bufnr = create_buffer({ - '> @@ -1,1 +1,2 @@', - '> local x = 1', - '> +local y = 2', - }) - - local hunk = { - filename = 'test.lua', - lang = 'lua', - ft = 'lua', - start_line = 1, - lines = { ' local x = 1', '+local y = 2' }, - prefix_width = 1, - quote_width = 2, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - - local extmarks = get_extmarks(bufnr) - local ts_marks = {} - for _, mark in ipairs(extmarks) do - local d = mark[4] - if d and d.hl_group and d.hl_group:match('^@.*%.lua$') then - table.insert(ts_marks, { row = mark[2], col = mark[3] }) - end - end - assert.is_true(#ts_marks > 0) - for _, tm in ipairs(ts_marks) do - assert.is_true(tm.col >= 3) - end - - delete_buffer(bufnr) - end) - - it('offsets intra-line char span extmarks by qw', function() - local bufnr = create_buffer({ - '> @@ -1,1 +1,1 @@', - '> -hello world', - '> +hello earth', - }) - - local hunk = { - filename = 'test.txt', - ft = nil, - lang = nil, - start_line = 1, - lines = { '-hello world', '+hello earth' }, - prefix_width = 1, - quote_width = 2, - } - - highlight.highlight_hunk( - bufnr, - ns, - hunk, - default_opts({ - highlights = { intra = { enabled = true, algorithm = 'default', max_lines = 500 } }, - }) - ) - - local extmarks = get_extmarks(bufnr) - local char_marks = {} - for _, mark in ipairs(extmarks) do - local d = mark[4] - if d and (d.hl_group == 'DiffsAddText' or d.hl_group == 'DiffsDeleteText') then - table.insert(char_marks, { row = mark[2], col = mark[3], end_col = d.end_col }) - end - end - if #char_marks > 0 then - for _, cm in ipairs(char_marks) do - assert.is_true(cm.col >= 2) - end - end - - delete_buffer(bufnr) - end) - - it('does not produce duplicate extmarks with syntax_only + qw', function() - local bufnr = create_buffer({ - '> @@ -1,1 +1,2 @@', - '> local x = 1', - '> +local y = 2', - }) - - local hunk = { - filename = 'test.lua', - lang = 'lua', - ft = 'lua', - start_line = 1, - lines = { ' local x = 1', '+local y = 2' }, - prefix_width = 1, - quote_width = 2, - } - - highlight.highlight_hunk(bufnr, ns, hunk, default_opts()) - highlight.highlight_hunk(bufnr, ns, hunk, default_opts({ syntax_only = true })) - - local extmarks = get_extmarks(bufnr) - local line_bg_count = 0 - for _, mark in ipairs(extmarks) do - local d = mark[4] - if d and d.line_hl_group == 'DiffsAdd' then - line_bg_count = line_bg_count + 1 - end - end - assert.are.equal(1, line_bg_count) - - delete_buffer(bufnr) - end) - end) -end)