diff --git a/lua/jj/cmd.lua b/lua/jj/cmd.lua index 377276d..e6f93cf 100644 --- a/lua/jj/cmd.lua +++ b/lua/jj/cmd.lua @@ -2,6 +2,7 @@ local M = {} local utils = require("jj.utils") +local diff = require("jj.diff") -- Config for cmd module M.config = { @@ -551,7 +552,7 @@ local function execute_describe(description) return end - -- Use --stdin to properly handle multi-line and special characters + -- Use --stdin to properly handle multi-line and special characters local _, success = utils.execute_command("jj describe --stdin", "Failed to describe", description) if success then utils.notify("Description set.", vim.log.levels.INFO) @@ -896,7 +897,7 @@ function M.j(args) end if #args == 0 then - -- Use the user's default command and do not try to parse anythng else + -- Use the user's default command and do not try to parse anything else run("jj") return end @@ -940,7 +941,7 @@ local function handle_j_command(opts) M.j(args) end ---- Register the J command +--- Register the J and Jdiff commands function M.register_command() vim.api.nvim_create_user_command("J", handle_j_command, { nargs = "*", @@ -973,6 +974,19 @@ function M.register_command() end, desc = "Execute jj commands with subcommand support", }) + + vim.api.nvim_create_user_command("Jdiff", diff.open_vdiff, { + nargs = "?", + desc = "Diff against jj revision", + }) + vim.api.nvim_create_user_command("Jhdiff", diff.open_hdiff, { + nargs = "?", + desc = "Horizontal diff against jj revision", + }) + vim.api.nvim_create_user_command("Jvdiff", diff.open_vdiff, { + nargs = "?", + desc = "Vertical diff against jj revision", + }) end return M diff --git a/lua/jj/diff.lua b/lua/jj/diff.lua new file mode 100644 index 0000000..95610fe --- /dev/null +++ b/lua/jj/diff.lua @@ -0,0 +1,68 @@ +---@class jj.diff +local M = {} + +local utils = require("jj.utils") + +--- Get the content of a file at a specific revision +--- @param rev string The revision +--- @param path string The file path +--- @return table lines The file content +local function get_file_content(rev, path) + local cmd = string.format("jj file show -r %s %s", rev, vim.fn.shellescape(path)) + local content = vim.fn.system(cmd) + local success = vim.v.shell_error == 0 + if success then + return vim.split(content, "\n", { trimempty = true }) + else + -- File does not exist at revision + return {} + end +end + +--- Open a read-only buffer for a specific revision of a file +--- @param rev string The revision +--- @param path string The file path +function M.open_revision(rev, path) + local lines = get_file_content(rev, path) + + local buf = vim.api.nvim_create_buf(false, true) + + local buf_name = string.format("jj://%s/%s", rev, path) + vim.api.nvim_buf_set_name(buf, buf_name) + vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) + + vim.bo[buf].buftype = "nofile" + vim.bo[buf].bufhidden = "wipe" + vim.bo[buf].readonly = true + vim.bo[buf].swapfile = false + vim.bo[buf].modifiable = true + + vim.api.nvim_win_set_buf(0, buf) +end + +--- Open a diff split for a specific revision of the current file +--- @param split_fun function Split function for the diff +--- @param args table Any passed arguments +function M.open_diff(split_fun, args) + if not utils.ensure_jj() then + return + end + + local rev = args[1] or "@-" + local path = vim.api.nvim_buf_get_name(0) + + vim.cmd.diffthis() + split_fun({ mods = { split = "aboveleft" } }) + M.open_revision(rev, path) + vim.cmd.diffthis() +end + +function M.open_vdiff(opts) + M.open_diff(vim.cmd.vsplit, opts.fargs) +end + +function M.open_hdiff(opts) + M.open_diff(vim.cmd.split, opts.fargs) +end + +return M