---@class jjannotate.Config ---@field view jjannotate.Config.View ---@field mappings jjannotate.Config.Mappings ---@class jjannotate.Config.View ---@field collapse_repeating_lines boolean Whether to collapse repeating lines into a single line ---@field highlight_changed_lines boolean Whether to highlight all lines changed by cursor's change id ---@field highlight_current_change "none" | "bold" How to highlight lines related to current change ---@field palette jjannotate.Config.Palette Controls the view's color palette ---@field loading_delay number? Delay in ms before showing a "Loading..." placeholder while annotating ---@class jjannotate.Config.Mappings ---@field close string | boolean Close the annotate window ---@field show string | boolean Show revision under the cursor in a new tab ---@field follow string | boolean Follow revision under the cursor and re-run annotate in a new tab ---@field prev_change string | boolean Jump to the previous change ---@field next_change string | boolean Jump to the next change ---@alias jjannotate.Config.Palette "auto" | jjannotate.Config.PaletteAuto | jjannotate.Config.ResolvedPalette --- * `auto`: Pick palette based on vim.o.background --- * `PaletteAuto`: Pick palette based on vim.o.background, with a color multiplier --- * `ResolvedPalette`: Manually specify the palette ---@class jjannotate.Config.PaletteAuto ---@field auto boolean Pick palette based on vim.o.background ---@field multiplier jjannotate.Config.Palette.Color Color multiplier applied to the auto-selected palette ---@class jjannotate.Config.ResolvedPalette ---@field fg jjannotate.Config.Palette.Color ---@field fg_bold jjannotate.Config.Palette.Color ---@field bg jjannotate.Config.Palette.Color ---@class jjannotate.Config.Palette.Color ---@field sat number ---@field light number local M = {} local Window = require("jjannotate.window") local backend = require("jjannotate.backend") local cache = require("jjannotate.cache") local util = require("jjannotate.util") ---@param buf integer ---@return jjannotate.RevisionRef? local function buf_revision(buf) local bufname = vim.api.nvim_buf_get_name(buf) return util.parse_uri("annotate", bufname) end ---@param info jjannotate.RepoInfo ---@param revset string? ---@return string? local function resolve_revision_sync(info, revset) if revset then return info.backend.resolve_revision(info.repo, revset) end local revision = buf_revision(0) return revision and revision.change_id or info.backend.resolve_revision(info.repo) end --- Open an annotate window for `path` (or the current buffer). --- If already open, update and focus it. ---@param path? string defaults to current buffer ---@param revset? string defaults to "@" function M.open(path, revset) local info = path and backend.path_info(path) or backend.buf_info(0) if not info then local display_path = path or vim.api.nvim_buf_get_name(0) if display_path ~= "" then display_path = vim.fs.relpath(vim.uv.cwd(), display_path) or display_path end util.notify( string.format("`%s` is not in a jj or git repo", display_path), vim.log.levels.ERROR ) return end info.backend.resolve_revision(info.repo, revset, function(change_id) vim.schedule(function() ---@type jjannotate.Revision local revision = vim.tbl_extend("force", info, { change_id = change_id }) local existing = cache:get_by_revision(revision) if existing then vim.api.nvim_create_autocmd("User", { pattern = "JjAnnotateUpdate:" .. existing.id, once = true, callback = function(args) local w = cache:get_by_id(args.data.id) if w then vim.api.nvim_set_current_win(w.annotate.win) end end, }) existing:update() return end Window.open(revision) end) end) end --- Check if any annotate window is open ---@param path? string if provided, check if path's annotate window is open ---@param revset? string defaults to "@", only used if `path` is provided ---@return boolean function M.is_open(path, revset) if path then local info = backend.path_info(path) if not info then return false end local change_id = resolve_revision_sync(info, revset) if not change_id then return false end local w = cache:get_by_revision({ repo = info.repo, path = info.path, change_id = change_id }) return w and w:is_open() or false end return vim.iter(vim.tbl_values(cache.wins)):any(function(w) return w:is_open() end) end --- Close annotate window ---@param path? string defaults to current buffer ---@param revset? string defaults to "@" ---@return boolean whether any window(s) closed function M.close(path, revset) local info = path and backend.path_info(path) or backend.buf_info(0) if not info then return false end local change_id = resolve_revision_sync(info, revset) if not change_id then return false end local w = cache:get_by_revision({ repo = info.repo, path = info.path, change_id = change_id }) if w then return w:close() end return false end --- Close all annotate windows ---@return number number of windows closed function M.close_all() local count = 0 for _, w in pairs(cache.wins) do if w:close() then count = count + 1 end end return count end --- Toggle annotate window ---@param path? string defaults to current buffer ---@param revset? string defaults to "@" function M.toggle(path, revset) if not M.close(path, revset) then M.open(path, revset) end end ---@param win_id integer ---@return jjannotate.Window? function M.get_by_id(win_id) return cache:get_by_id(win_id) end ---@param revision jjannotate.RevisionRef ---@return jjannotate.Window? function M.get_by_revision(revision) return cache:get_by_revision(revision) end require("jjannotate.attach").setup() return M