mirror of
https://github.com/zoriya/jj.nvim.git
synced 2026-08-05 02:36:07 +00:00
feat(diff): Make both codediff/diffview work with the new Jedit
command to allow diffing agianst arbitrary revisions in buffer
This commit is contained in:
+168
-2
@@ -1,8 +1,111 @@
|
||||
local utils = require("jj.utils")
|
||||
local file = require("jj.file")
|
||||
|
||||
---@type jj.diff
|
||||
local diff = require("jj.diff")
|
||||
|
||||
---@type {dirs: string[], files: string[]}[]
|
||||
local pending_launches = {}
|
||||
---@type table<number, {dirs: string[], files: string[]}>
|
||||
local artifacts_by_tab = {}
|
||||
local temp_cleanup_registered = false
|
||||
|
||||
local function cleanup_artifacts(artifacts)
|
||||
if not artifacts then
|
||||
return
|
||||
end
|
||||
|
||||
for _, file_path in ipairs(artifacts.files or {}) do
|
||||
local bufnr = vim.fn.bufnr(file_path)
|
||||
if bufnr ~= -1 and vim.api.nvim_buf_is_valid(bufnr) then
|
||||
pcall(vim.api.nvim_buf_delete, bufnr, { force = true })
|
||||
end
|
||||
end
|
||||
|
||||
for _, dir_path in ipairs(artifacts.dirs or {}) do
|
||||
vim.fn.delete(dir_path, "rf")
|
||||
end
|
||||
end
|
||||
|
||||
local function register_cleanup_hooks()
|
||||
if temp_cleanup_registered then
|
||||
return
|
||||
end
|
||||
temp_cleanup_registered = true
|
||||
|
||||
local group = vim.api.nvim_create_augroup("JJCodediffTempCleanup", { clear = true })
|
||||
|
||||
vim.api.nvim_create_autocmd("User", {
|
||||
group = group,
|
||||
pattern = "CodeDiffOpen",
|
||||
callback = function(args)
|
||||
local artifacts = table.remove(pending_launches, 1)
|
||||
if not artifacts then
|
||||
return
|
||||
end
|
||||
local tabpage = (args.data and args.data.tabpage) or vim.api.nvim_get_current_tabpage()
|
||||
artifacts_by_tab[tabpage] = artifacts
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("User", {
|
||||
group = group,
|
||||
pattern = "CodeDiffClose",
|
||||
callback = function(args)
|
||||
local tabpage = (args.data and args.data.tabpage) or vim.api.nvim_get_current_tabpage()
|
||||
local artifacts = artifacts_by_tab[tabpage]
|
||||
if artifacts then
|
||||
cleanup_artifacts(artifacts)
|
||||
artifacts_by_tab[tabpage] = nil
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("VimLeavePre", {
|
||||
group = group,
|
||||
callback = function()
|
||||
for _, artifacts in pairs(artifacts_by_tab) do
|
||||
cleanup_artifacts(artifacts)
|
||||
end
|
||||
for _, artifacts in ipairs(pending_launches) do
|
||||
cleanup_artifacts(artifacts)
|
||||
end
|
||||
artifacts_by_tab = {}
|
||||
pending_launches = {}
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
--- Write text lines to a temp file while preserving filename/extension for filetype detection.
|
||||
--- @param original_path string
|
||||
--- @param lines string[]
|
||||
--- @param had_eol boolean
|
||||
--- @return string|nil file_path
|
||||
--- @return string|nil dir_path
|
||||
local function write_temp_file_like(original_path, lines, had_eol)
|
||||
local dir = vim.fn.tempname()
|
||||
vim.fn.mkdir(dir, "p")
|
||||
|
||||
local basename = vim.fs.basename(original_path or "")
|
||||
if not basename or basename == "" then
|
||||
basename = "jj-codediff.tmp"
|
||||
end
|
||||
|
||||
local path = dir .. "/" .. basename
|
||||
local fh = io.open(path, "w")
|
||||
if not fh then
|
||||
vim.fn.delete(dir, "rf")
|
||||
return nil, nil
|
||||
end
|
||||
fh:write(table.concat(lines, "\n"))
|
||||
if had_eol then
|
||||
fh:write("\n")
|
||||
end
|
||||
fh:close()
|
||||
|
||||
return path, dir
|
||||
end
|
||||
|
||||
--- Givewn two changes, show their diff using codediff
|
||||
--- @param left string
|
||||
--- @param right string
|
||||
@@ -53,8 +156,71 @@ diff.register_backend("codediff", {
|
||||
return
|
||||
end
|
||||
|
||||
-- Extract the commit id from opts.rev
|
||||
local revset = opts.rev or "@-"
|
||||
local buf_name = vim.api.nvim_buf_get_name(0)
|
||||
local change_id, jj_path = utils.parse_jj_uri(buf_name)
|
||||
local revset = opts.rev or (change_id and (change_id .. "-")) or "@-"
|
||||
|
||||
-- For jj:// buffers, compare the current in-memory buffer against <revset> for the same file.
|
||||
if change_id and not opts.path then
|
||||
local path = jj_path
|
||||
if not path or path == "" then
|
||||
utils.notify("Invalid jj:// buffer path", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
local base_lines, base_had_eol, ok_read = file.get_file_content(revset, path)
|
||||
if not ok_read then
|
||||
utils.notify(string.format("Could not read `%s` from `%s` for CodeDiff", path, revset), vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
local cur_lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
|
||||
local cur_had_eol = vim.bo[0].eol
|
||||
|
||||
local base_file, base_dir = write_temp_file_like(path, base_lines, base_had_eol)
|
||||
local cur_file, cur_dir = write_temp_file_like(path, cur_lines, cur_had_eol)
|
||||
if not base_file or not cur_file or not base_dir or not cur_dir then
|
||||
if base_dir then
|
||||
vim.fn.delete(base_dir, "rf")
|
||||
end
|
||||
if cur_dir then
|
||||
vim.fn.delete(cur_dir, "rf")
|
||||
end
|
||||
utils.notify("Failed to create temporary files for CodeDiff", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
register_cleanup_hooks()
|
||||
local artifacts = {
|
||||
dirs = { base_dir, cur_dir },
|
||||
files = { base_file, cur_file },
|
||||
}
|
||||
table.insert(pending_launches, artifacts)
|
||||
|
||||
local ok_cmd, err = pcall(function()
|
||||
vim.cmd(
|
||||
string.format("CodeDiff file %s %s", vim.fn.fnameescape(base_file), vim.fn.fnameescape(cur_file))
|
||||
)
|
||||
end)
|
||||
|
||||
if not ok_cmd then
|
||||
-- remove the just-enqueued launch so the next CodeDiffOpen doesn't consume it
|
||||
if pending_launches[#pending_launches] == artifacts then
|
||||
table.remove(pending_launches)
|
||||
else
|
||||
for i = #pending_launches, 1, -1 do
|
||||
if pending_launches[i] == artifacts then
|
||||
table.remove(pending_launches, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cleanup_artifacts(artifacts)
|
||||
utils.notify(err or "Could not launch CodeDiff", vim.log.levels.ERROR)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local commit_id = utils.get_commit_id(revset)
|
||||
if not commit_id then
|
||||
|
||||
@@ -46,15 +46,23 @@ diff.register_backend("diffview", {
|
||||
return
|
||||
end
|
||||
|
||||
-- Extract the commit id from opts.rev
|
||||
local revset = opts.rev or "@-"
|
||||
local buf_name = vim.api.nvim_buf_get_name(0)
|
||||
local change_id, jj_path = utils.parse_jj_uri(buf_name)
|
||||
local revset = opts.rev or (change_id and (change_id .. "-")) or "@-"
|
||||
|
||||
local commit_id = utils.get_commit_id(revset)
|
||||
if not commit_id then
|
||||
return
|
||||
end
|
||||
|
||||
vim.cmd(string.format("DiffviewOpen %s -- %%", commit_id))
|
||||
local raw_path = opts.path or jj_path or "%"
|
||||
local path, err = utils.normalize_repo_path(raw_path)
|
||||
if not path then
|
||||
utils.notify(err or "Could not resolve file path for Diffview", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
vim.cmd(string.format("DiffviewOpen %s -- %s", commit_id, vim.fn.fnameescape(path)))
|
||||
vim.cmd("DiffviewToggleFiles")
|
||||
end,
|
||||
show_revision = function(opts)
|
||||
|
||||
@@ -27,7 +27,11 @@ local function open_revision(rev, path)
|
||||
return
|
||||
end
|
||||
|
||||
local lines, had_eol = file.get_file_content(change_id, rel_path)
|
||||
local lines, had_eol, ok_read = file.get_file_content(change_id, rel_path)
|
||||
if not ok_read then
|
||||
utils.notify(string.format("Could not read `%s` from `%s`", rel_path, change_id), vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
local buf = vim.api.nvim_create_buf(false, true)
|
||||
|
||||
|
||||
+13
-4
@@ -21,6 +21,7 @@ local parser = require("jj.core.parser")
|
||||
--- @param path string Repository-relative path
|
||||
--- @return string[] lines
|
||||
--- @return boolean had_eol Whether the content had a trailing newline
|
||||
--- @return boolean ok Whether the command succeeded
|
||||
local function get_file_content(rev, path)
|
||||
local content, ok = runner.execute_command(
|
||||
string.format("jj file show -r %s %s", vim.fn.shellescape(rev), vim.fn.shellescape(path)),
|
||||
@@ -29,14 +30,14 @@ local function get_file_content(rev, path)
|
||||
true
|
||||
)
|
||||
if not ok or not content then
|
||||
return {}, false
|
||||
return {}, false, false
|
||||
end
|
||||
local lines = vim.split(content, "\n", { plain = true, trimempty = false })
|
||||
local had_eol = #lines > 0 and lines[#lines] == ""
|
||||
if had_eol then
|
||||
table.remove(lines, #lines)
|
||||
end
|
||||
return lines, had_eol
|
||||
return lines, had_eol, true
|
||||
end
|
||||
M.get_file_content = get_file_content
|
||||
|
||||
@@ -146,7 +147,11 @@ function M.open_target(opts)
|
||||
end
|
||||
local change_id = ids[1]
|
||||
|
||||
local lines, had_eol = get_file_content(change_id, path)
|
||||
local lines, had_eol, ok_read = get_file_content(change_id, path)
|
||||
if not ok_read then
|
||||
utils.notify(string.format("Could not read `%s` from `%s`", path, change_id), vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
local ft = vim.filetype.match({ filename = path })
|
||||
|
||||
local buf, _ = buffer.create({
|
||||
@@ -219,7 +224,11 @@ function M.register_command()
|
||||
local name = vim.api.nvim_buf_get_name(0)
|
||||
local change_id, path = utils.parse_jj_uri(name)
|
||||
if not change_id then return end
|
||||
local lines, had_eol = get_file_content(change_id, path)
|
||||
local lines, had_eol, ok_read = get_file_content(change_id, path)
|
||||
if not ok_read then
|
||||
utils.notify(string.format("Could not read `%s` from `%s`", path, change_id), vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
local buf = vim.api.nvim_get_current_buf()
|
||||
vim.bo[buf].modifiable = true
|
||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
||||
|
||||
Reference in New Issue
Block a user