diff --git a/CHANGELOG.md b/CHANGELOG.md index d23cf3c..f8f0106 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Fixed + +- The diff drew its cursor line over the text even with `cursorlineopt=number`, and the panel, history and PR checks lists lost their cursor row under that setting. The diff now follows `cursorlineopt`, and the lists always highlight the row + +## [0.1.43] — 2026-09-08 + ### Added - The PR overview can now be commented on without leaving it. `ga` posts a comment on the pull request, and `gp`/`gq` answer what's under the cursor: into that code thread, or as a new PR comment where GitHub offers no threading. `gq` opens with the comment quoted, and in visual mode quotes just the selection diff --git a/lua/differ/history/init.lua b/lua/differ/history/init.lua index 5b0e38a..53011cc 100644 --- a/lua/differ/history/init.lua +++ b/lua/differ/history/init.lua @@ -749,6 +749,7 @@ function History:_setup_window() set_wo(win, "foldcolumn", "0") set_wo(win, "wrap", false) set_wo(win, "cursorline", true) + set_wo(win, "cursorlineopt", "line") if self.position == "left" or self.position == "right" then set_wo(win, "winfixwidth", true) else diff --git a/lua/differ/panel/init.lua b/lua/differ/panel/init.lua index 4d8d80c..87e627c 100644 --- a/lua/differ/panel/init.lua +++ b/lua/differ/panel/init.lua @@ -1216,6 +1216,7 @@ function Panel:_setup_window() set_wo(win, "foldcolumn", "0") set_wo(win, "wrap", false) set_wo(win, "cursorline", true) + set_wo(win, "cursorlineopt", "line") if self.progress then -- a `%!` expression so the file-position meter tracks the cursor on each redraw set_wo(win, "winbar", '%!v:lua.require("differ.ui.winbar").panel()') diff --git a/lua/differ/pr/checks.lua b/lua/differ/pr/checks.lua index 202008d..613f760 100644 --- a/lua/differ/pr/checks.lua +++ b/lua/differ/pr/checks.lua @@ -124,6 +124,7 @@ local function render(checks) title_pos = "center", }) set_wo(win, "cursorline", true) + set_wo(win, "cursorlineopt", "line") local function close() if vim.api.nvim_win_is_valid(win) then diff --git a/lua/differ/view.lua b/lua/differ/view.lua index 8268fb2..b2d70aa 100644 --- a/lua/differ/view.lua +++ b/lua/differ/view.lua @@ -372,6 +372,26 @@ function View:_paint_staged() end end +-- 'cursorlineopt' values that highlight the line text; screenline still gets the +-- whole line, an extmark can't target one screen row +local CURSORLINE_TEXT = { line = true, screenline = true, both = true } + +-- reads the window's effective values, so a :setlocal inside the diff wins over the global +---@param win integer +---@return boolean +local function highlights_cursor_text(win) + if not vim.api.nvim_get_option_value("cursorline", { scope = "local", win = win }) then + return false + end + local opt = vim.api.nvim_get_option_value("cursorlineopt", { scope = "local", win = win }) + for value in opt:gmatch("[^,]+") do + if CURSORLINE_TEXT[value] then + return true + end + end + return false +end + -- repaint our own cursor line above the diff backgrounds, since a no-foreground -- CursorLine is low-priority and gets buried by them. mirrors the diff line bg -- (a char-level fill with hl_eol so it spans the whole row past EOL) but at a higher @@ -391,9 +411,7 @@ function View:_paint_cursorline() if not (win and vim.api.nvim_win_is_valid(win)) then return end - -- read the window's effective value, so a :setlocal cursorline inside the diff - -- still paints while a global nocursorline leaves the rows clean - if not vim.api.nvim_get_option_value("cursorline", { scope = "local", win = win }) then + if not highlights_cursor_text(win) then return end local row = vim.api.nvim_win_get_cursor(win)[1] - 1 @@ -632,7 +650,7 @@ function View:_setup_window(winid, bufnr) -- can't also be buffer-scoped: skip the fire unless this view holds focus vim.api.nvim_create_autocmd("OptionSet", { group = group, - pattern = "cursorline", + pattern = { "cursorline", "cursorlineopt" }, callback = function() if vim.api.nvim_get_current_buf() == bufnr then self:_paint_cursorline() diff --git a/test/nvim/panel_spec.lua b/test/nvim/panel_spec.lua index 966fa4e..97e06f4 100644 --- a/test/nvim/panel_spec.lua +++ b/test/nvim/panel_spec.lua @@ -288,6 +288,19 @@ describe("panel navigation", function() assert.is_truthy(vim.tbl_contains(texts, "-3")) p:close() end) + + it("highlights the cursor row even when cursorlineopt is number only", function() + local had = vim.o.cursorlineopt + vim.o.cursorlineopt = "number" + local p = panel({ fe("a.lua") }) + p:open() + local opt = + vim.api.nvim_get_option_value("cursorlineopt", { scope = "local", win = p.winid }) + assert.are.equal("line", opt) + assert.are.equal("number", vim.go.cursorlineopt) + p:close() + vim.o.cursorlineopt = had + end) end) -- the panel window's position relative to the origin window it was split from diff --git a/test/nvim/view_spec.lua b/test/nvim/view_spec.lua index f51aad9..7cd34b7 100644 --- a/test/nvim/view_spec.lua +++ b/test/nvim/view_spec.lua @@ -1032,13 +1032,14 @@ describe("view cursor-line overlay", function() local cursor_ns = vim.api.nvim_create_namespace("differ.cursorline") -- the overlay follows the window's cursorline, and nvim ships it off - local had_cursorline + local had_cursorline, had_cursorlineopt before_each(function() - had_cursorline = vim.o.cursorline + had_cursorline, had_cursorlineopt = vim.o.cursorline, vim.o.cursorlineopt vim.o.cursorline = true + vim.o.cursorlineopt = "both" end) after_each(function() - vim.o.cursorline = had_cursorline + vim.o.cursorline, vim.o.cursorlineopt = had_cursorline, had_cursorlineopt end) local CURSORLINE = { @@ -1201,6 +1202,61 @@ describe("view cursor-line overlay", function() assert.are.same({}, overlay_rows(buf)) v:close() end) + + it("paints no overlay when cursorlineopt only highlights the number", function() + vim.cmd("silent! only") + vim.o.cursorlineopt = "number" + local v = View.new(model("a\nb\nc\n", "a\nB\nc\n"), { + layout = "stacked", + context = math.huge, + deep_diff = { enabled = true }, + }) + v:open() + local buf, win = v.columns[1].bufnr, v.columns[1].winid + assert.are.same({}, overlay_rows(buf)) + + vim.api.nvim_win_set_cursor(win, { 1, 0 }) + vim.api.nvim_exec_autocmds("CursorMoved", { buffer = buf }) + assert.are.same({}, overlay_rows(buf)) + v:close() + end) + + it("paints the overlay for every cursorlineopt that highlights the line", function() + vim.cmd("silent! only") + local v = View.new(model("a\nb\nc\n", "a\nB\nc\n"), { + layout = "stacked", + context = math.huge, + deep_diff = { enabled = true }, + }) + v:open() + local buf, win = v.columns[1].bufnr, v.columns[1].winid + local cur = vim.api.nvim_win_get_cursor(win)[1] - 1 + for _, opt in ipairs({ "line", "screenline", "line,number", "number,screenline" }) do + vim.api.nvim_set_option_value("cursorlineopt", opt, { scope = "local", win = win }) + assert.are.same({ cur }, overlay_rows(buf), opt) + end + v:close() + end) + + it("repaints when cursorlineopt changes on the diff window mid-session", function() + vim.cmd("silent! only") + local v = View.new(model("a\nb\nc\n", "a\nB\nc\n"), { + layout = "stacked", + context = math.huge, + deep_diff = { enabled = true }, + }) + v:open() + local buf, win = v.columns[1].bufnr, v.columns[1].winid + local cur = vim.api.nvim_win_get_cursor(win)[1] - 1 + assert.are.same({ cur }, overlay_rows(buf)) + + vim.api.nvim_set_option_value("cursorlineopt", "number", { scope = "local", win = win }) + assert.are.same({}, overlay_rows(buf)) + + vim.api.nvim_set_option_value("cursorlineopt", "both", { scope = "local", win = win }) + assert.are.same({ cur }, overlay_rows(buf)) + v:close() + end) end) describe("view close guard", function()