Files
Nicolas GBandGitHub 7f2786ff16 refactor!: Migrate runners cmd from string to argv (#134)
Migrate command execution to structured argv lists across the plugin, replacing shell-string command construction with `vim.system` argv execution. The runner API now exposes argv-based execution as the default path:

   - `runner.execute_command` -> `runner.execute`
   - `runner.execute_command_async` -> `runner.execute_async`
   - `runner.execute_command_raw` -> `runner.execute_raw`
   - `runner.execute_command_raw_async` -> `runner.execute_raw_async`
   - `runner.execute_command_sync` was removed; use `runner.execute` and handle the result directly

BREAKING CHANGES:
   - `cmd.new({ args = ... })` now requires `args` as `string[]` argv tokens, not a single string.
   - `cmd.log({ ... })` now uses `raw_flags` as `string[]` for raw passthrough flags; `raw = "..."` is no longer supported.
   - Direct users of `jj.core.runner` must migrate from the old `execute_command*` string helpers to the new argv-based `execute*` helpers.
2026-07-07 18:25:30 +02:00

172 lines
5.4 KiB
Lua

local utils = require("jj.utils")
local buffer = require("jj.core.buffer")
local runner = require("jj.core.runner")
local diff = require("jj.diff")
local file = require("jj.file")
--- Open a writable buffer for a specific revision of a file.
--- @param rev string The revision
--- @param path string The file path (absolute or repo-relative)
--- @param enc? jj.file.enc Encoding settings for the file content
local function open_revision(rev, path, enc)
local cmd = {
"jj",
"log",
"--no-graph",
"-r",
rev,
"-T",
'change_id ++ "\n"',
"--quiet",
}
local raw_ids, ok = runner.execute(cmd, "jj: failed to resolve revision")
if not ok or not raw_ids then
return
end
local ids = vim.split(vim.trim(raw_ids), "\n", { trimempty = true })
if #ids ~= 1 then
utils.notify(string.format("Revision '%s' is ambiguous", rev), vim.log.levels.ERROR)
return
end
local change_id = ids[1]
local rel_path, err = utils.normalize_relative_path(path)
if not rel_path then
utils.notify(err or "Could not resolve path", vim.log.levels.ERROR)
return
end
-- A file added since `rev` has no content there; show it as empty so it
-- diffs as fully added rather than aborting the diff.
local lines, had_eol, ok_read, used_enc, absent = file.get_file_content(change_id, rel_path, enc)
if not ok_read and not absent 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)
local buf_name = string.format("jj://%s/%s", change_id, rel_path)
file.set_buf_encoding(buf, used_enc)
vim.api.nvim_buf_set_name(buf, buf_name)
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
vim.bo[buf].eol = had_eol
local ft = vim.filetype.match({ filename = rel_path })
if ft then
vim.bo[buf].filetype = ft
end
vim.bo[buf].buftype = "acwrite"
vim.bo[buf].bufhidden = "wipe"
vim.bo[buf].readonly = false
vim.bo[buf].swapfile = false
vim.bo[buf].modifiable = true
vim.api.nvim_create_autocmd("BufWriteCmd", {
buffer = buf,
callback = function()
file.write_revision_file(buf, change_id, rel_path, vim.v.cmdbang == 1)
end,
})
vim.bo[buf].modified = false
vim.api.nvim_win_set_buf(0, buf)
end
-----------------------------------------------------------------------
-- Native Backend
-----------------------------------------------------------------------
diff.register_backend("native", {
--- Opens a side-by-side diff of the current buffer against a revision.
--- Creates a split with the revision content on the left and the current buffer on the right.
--- Closing either side will clean up both and restore the original cursor position.
diff_current = function(opts)
if not utils.ensure_jj() then
return
end
-- Save current state to restore after diff is closed
local prev_buf = vim.api.nvim_get_current_buf()
local prev_cur_pos = buffer.get_cursor(prev_buf) or { 1, 0 }
local buf_name = vim.api.nvim_buf_get_name(0)
local change_id, jj_path = utils.parse_jj_uri(buf_name)
local rev = opts.rev or (change_id and (change_id .. "-")) or "@-"
local path = opts.path or jj_path or buf_name
local layout = opts.layout or "vertical"
-- Borrow the current buffer's encoding settings so the revision
-- side is decoded in the same way as the current file.
-- Assumes the encoding didn't change between revisions.
local enc = file.get_buf_encoding(prev_buf)
local split_fun = layout == "horizontal" and vim.cmd.split or vim.cmd.vsplit
local orig_win = vim.api.nvim_get_current_win()
-- Use better diff algorithm for code moves and indentation
local saved_diffopt = vim.o.diffopt
vim.opt.diffopt:append("algorithm:patience,indent-heuristic")
-- Set up diff: current buffer on right, revision on left
vim.cmd.diffthis()
split_fun({ mods = { split = "aboveleft" } })
open_revision(rev, path, enc)
vim.cmd.diffthis()
local rev_buf = vim.api.nvim_get_current_buf()
local augroup = vim.api.nvim_create_augroup("JJDiffCleanup" .. rev_buf, { clear = true })
-- Cleanup closes both sides, exits diff mode, and restores cursor.
local function cleanup()
vim.api.nvim_del_augroup_by_id(augroup)
vim.schedule(function()
if vim.api.nvim_buf_is_valid(rev_buf) then
vim.api.nvim_buf_delete(rev_buf, { force = true })
end
if vim.api.nvim_win_is_valid(orig_win) then
vim.api.nvim_set_current_win(orig_win)
vim.cmd.diffoff()
end
buffer.set_cursor(prev_buf, prev_cur_pos)
vim.o.diffopt = saved_diffopt
end)
end
-- Trigger cleanup when either the revision buffer or original window is closed
vim.api.nvim_create_autocmd({ "BufWipeout", "BufHidden" }, {
group = augroup,
buffer = rev_buf,
once = true,
callback = cleanup,
})
vim.api.nvim_create_autocmd("WinClosed", {
group = augroup,
pattern = tostring(orig_win),
once = true,
callback = cleanup,
})
end,
show_revision = function(opts)
local terminal = require("jj.ui.terminal")
local cmd = { "jj", "show", "-r", opts.rev, "--quiet", "--no-pager" }
terminal.run_floating(cmd)
end,
diff_revisions = function(opts)
local terminal = require("jj.ui.terminal")
local cmd = { "jj", "diff", "-f", opts.left, "-t", opts.right, "--quiet", "--no-pager" }
terminal.run_floating(cmd)
end,
diff_history_revisions = function(_)
utils.notify(
"Diffing revisions with history mode is not supported on the `native` backend.",
vim.log.levels.WARN
)
end,
})