jujutsu file annotations for neovim (like blame)
4

Configure Feed

Select the types of activity you want to include in your feed.

always annotate current buf

Signed-off-by: Ron Shavit <me@ronshavit.com>

Ron Shavit (May 29, 2026, 1:05 AM +0300) 2bd80244 1b42d3d5

+41 -60
+41 -60
lua/jjannotate.lua
··· 53 53 ---@return integer 54 54 local function cursor_row(win) return vim.api.nvim_win_get_cursor(win or 0)[1] end 55 55 56 - ---@param path string 57 - ---@return { win: integer, buf: integer} | nil 58 - local function find_visible_buf_with_path(path) 59 - local target = vim.fn.fnamemodify(path, ":p") 60 - for _, win in ipairs(vim.api.nvim_list_wins()) do 61 - local buf = vim.api.nvim_win_get_buf(win) 62 - if vim.api.nvim_buf_get_name(buf) == target then return { win = win, buf = buf } end 63 - end 64 - end 65 - 66 56 ---@param win integer 67 57 ---@param longest_line integer 68 58 local function win_set_width(win, longest_line) ··· 174 164 175 165 ---@param state jjannotate.State 176 166 local function setup_autocmds(state) 177 - if state.original then 178 - vim.api.nvim_create_autocmd({ "BufWritePost", "FileChangedShellPost" }, { 179 - desc = "Update jjannotate on file change", 180 - group = augroup, 181 - buffer = state.original.buf, 182 - callback = function() update(state) end, 183 - }) 184 - end 167 + vim.api.nvim_create_autocmd({ "BufWritePost", "FileChangedShellPost" }, { 168 + desc = "Update jjannotate on file change", 169 + group = augroup, 170 + buffer = state.original.buf, 171 + callback = function() update(state) end, 172 + }) 185 173 186 174 if state.opts.view.highlight_lines then 187 175 local callback = vim.schedule_wrap(function() highlight_lines(state) end) ··· 193 181 callback = callback, 194 182 }) 195 183 196 - if state.original then 197 - vim.api.nvim_create_autocmd("CursorMoved", { 198 - desc = "Highlight lines in original buffer", 199 - group = augroup, 200 - buffer = state.original.buf, 201 - callback = callback, 202 - }) 203 - end 184 + vim.api.nvim_create_autocmd("CursorMoved", { 185 + desc = "Highlight lines in original buffer", 186 + group = augroup, 187 + buffer = state.original.buf, 188 + callback = callback, 189 + }) 204 190 end 205 191 206 192 vim.api.nvim_create_autocmd({ "BufHidden", "BufUnload" }, { ··· 212 198 vim.api.nvim_clear_autocmds({ group = augroup, buffer = state.buf }) 213 199 vim.api.nvim_buf_clear_namespace(state.buf, ns_hl, 0, -1) 214 200 215 - if state.original then 216 - if vim.api.nvim_buf_is_valid(state.original.buf) then 217 - vim.api.nvim_clear_autocmds({ group = augroup, buffer = state.original.buf }) 218 - vim.api.nvim_buf_clear_namespace(state.original.buf, ns_hl, 0, -1) 219 - end 201 + if vim.api.nvim_buf_is_valid(state.original.buf) then 202 + vim.api.nvim_clear_autocmds({ group = augroup, buffer = state.original.buf }) 203 + vim.api.nvim_buf_clear_namespace(state.original.buf, ns_hl, 0, -1) 204 + end 220 205 221 - if vim.api.nvim_win_is_valid(state.original.win) then 222 - for opt, value in pairs(state.original_opts) do 223 - vim.api.nvim_set_option_value(opt, value, { win = state.original.win }) 224 - end 206 + if vim.api.nvim_win_is_valid(state.original.win) then 207 + for opt, value in pairs(state.original_opts) do 208 + vim.api.nvim_set_option_value(opt, value, { win = state.original.win }) 225 209 end 226 210 end 227 211 ··· 264 248 ---@param opts jjannotate.Opts 265 249 ---@return jjannotate.State 266 250 local function open(path, result, opts) 251 + local buf = vim.api.nvim_create_buf(false, true) 252 + 267 253 ---@type jjannotate.State 268 254 local state = { 269 255 path = path, 270 - win = nil, 271 - buf = vim.api.nvim_create_buf(false, true), 272 - original = nil, 256 + buf = buf, 257 + win = vim.api.nvim_open_win(buf, false, { split = "left" }), 258 + original = { 259 + win = vim.api.nvim_get_current_win(), 260 + buf = vim.api.nvim_get_current_buf(), 261 + }, 273 262 original_opts = {}, 274 263 result = result, 275 264 opts = opts, ··· 281 270 vim.api.nvim_set_option_value("bufhidden", "wipe", { buf = state.buf }) 282 271 vim.api.nvim_set_option_value("swapfile", false, { buf = state.buf }) 283 272 284 - local longest_line = render_lines(state) 285 - 286 - state.original = find_visible_buf_with_path(path) 287 - local row = cursor_row(state.original.win) 288 - 289 - state.win = vim.api.nvim_open_win(state.buf, true, { split = "left" }) 290 273 vim.api.nvim_set_option_value("list", false, { win = state.win }) 291 274 vim.api.nvim_set_option_value("spell", false, { win = state.win }) 292 275 vim.api.nvim_set_option_value("signcolumn", "no", { win = state.win }) 293 276 vim.api.nvim_set_option_value("foldcolumn", "0", { win = state.win }) 294 - vim.api.nvim_set_option_value("number", true, { win = state.win }) 295 - vim.api.nvim_set_option_value("relativenumber", true, { win = state.win }) 277 + vim.api.nvim_set_option_value("number", false, { win = state.win }) 278 + vim.api.nvim_set_option_value("relativenumber", false, { win = state.win }) 296 279 vim.api.nvim_set_option_value("sidescrolloff", 0, { win = state.win }) 297 280 vim.api.nvim_set_option_value("winfixwidth", true, { win = state.win }) 281 + 282 + local longest_line = render_lines(state) 298 283 win_set_width(state.win, longest_line) 299 284 300 - if state.original then vim.api.nvim_win_set_cursor(state.win, { row, 0 }) end 285 + vim.api.nvim_win_set_cursor(state.win, { cursor_row(state.original.win), 0 }) 301 286 302 287 setup_autocmds(state) 303 288 setup_keymaps(state) 304 289 305 - if state.original then 306 - for opt, value in pairs(enabled_opts) do 307 - state.original_opts[opt] = vim.api.nvim_get_option_value(opt, { win = state.original.win }) 308 - vim.api.nvim_set_option_value(opt, value, { win = state.original.win }) 309 - vim.api.nvim_set_option_value(opt, value, { win = state.win }) 310 - end 290 + for opt, value in pairs(enabled_opts) do 291 + state.original_opts[opt] = vim.api.nvim_get_option_value(opt, { win = state.original.win }) 292 + vim.api.nvim_set_option_value(opt, value, { win = state.original.win }) 293 + vim.api.nvim_set_option_value(opt, value, { win = state.win }) 311 294 end 312 295 313 296 vim.api.nvim_win_call(state.original.win, function() vim.cmd.syncbind() end) ··· 329 312 330 313 local M = {} 331 314 332 - ---@param path? string 333 - function M.open(path) 334 - path = path or vim.fn.expand("%") 315 + function M.open() 316 + local path = vim.fn.expand("%") 335 317 336 318 local state = states[path] 337 319 if state then ··· 381 363 return closed > 0 382 364 end 383 365 384 - ---@param path? string 385 - function M.toggle(path) 386 - path = path or vim.b.jjannotate_path or vim.fn.expand("%") 387 - if not M.close(path) then M.open(path) end 366 + function M.toggle() 367 + local path = vim.b.jjannotate_path or vim.fn.expand("%") 368 + if not M.close(path) then M.open() end 388 369 end 389 370 390 371 return M