fix(status): Make restoring files available in visual mode (#144)

This commit is contained in:
Nicolas GB
2026-07-24 17:47:24 +02:00
committed by GitHub
parent cf11a4ffec
commit 3d10ae14be
5 changed files with 85 additions and 35 deletions
+9 -6
View File
@@ -372,15 +372,18 @@ Open the current buffer's file in your browser on the hosted remote (GitHub/GitL
In Visual mode, select lines and run `:Jbrowse` to open a range.
### Open a changed file
### Open or restore changed files
The `status` buffer supports these default keybindings:
- `<CR>` — Open the file under the cursor in the current window.
- `<S-x>` — Restore the file under the cursor.
- Visual mode + `<S-x>` — Restore every changed file in the selected status lines at once.
Restoring multiple files runs one `jj restore` command, refreshes the status buffer when it succeeds, and reports every restored path. Renamed files are handled correctly: both the old and new paths are restored.
Just press enter to open a file from the `status` output in your current window.
![Open-status](https://github.com/NicolasGB/jj.nvim/raw/main/assets/enter-status.gif)
### Restore a changed file
Press `<S-x>` on a file from the `status` output and that's it, it's restored.
![Restore-status](https://github.com/NicolasGB/jj.nvim/raw/main/assets/x-status.gif)
### Status picker (Snacks.nvim)
+1 -5
View File
@@ -188,11 +188,7 @@ local function get_highlight_marks()
if mode == "v" or mode == "V" then
-- Visual mode: get selected lines
local selected_start_line = vim.fn.line("v")
local selected_end_line = vim.fn.line(".")
if selected_start_line > selected_end_line then
selected_start_line, selected_end_line = selected_end_line, selected_start_line
end
local _, selected_start_line, selected_end_line = utils.get_visual_selection(buf)
-- Get the lines based on revision content
local start_line = get_prev_revision_line(selected_start_line) or selected_start_line
+46 -23
View File
@@ -10,35 +10,54 @@ local jj_args = require("jj.core.args")
--- Handle restoring a file from the jj status buffer
--- Supports both renamed and non-renamed files
function M.handle_status_restore()
local file_info = parser.parse_file_info_from_status_line(vim.api.nvim_get_current_line())
if not file_info then
local lines = utils.get_visual_selection(terminal.state.buf)
if not lines then
lines = { vim.api.nvim_get_current_line() }
end
--- @type jj.core.parser.status_file[]
local files = {}
for _, line in ipairs(lines) do
local file_info = parser.parse_file_info_from_status_line(line)
if file_info then
table.insert(files, file_info)
end
end
-- Return early if no files were found in the selection
if #files == 0 then
utils.notify("No files selected to restore", vim.log.levels.WARN)
return
end
if file_info.is_rename then
local restore_cmd = {
"jj",
"restore",
"--from",
"@-",
jj_args.fileset(file_info.old_path),
jj_args.fileset(file_info.new_path),
}
local _, restore_success = runner.execute(restore_cmd, "Failed to restore original file")
if restore_success then
utils.notify("Reverted rename: " .. file_info.new_path .. " -> " .. file_info.old_path, vim.log.levels.INFO)
require("jj.cmd").status()
-- For each file found restore it
local cmd = { "jj", "restore" }
for _, file in ipairs(files) do
if file.is_rename then
-- If it's a rename add both old and new paths to the restore command
vim.list_extend(cmd, { jj_args.fileset(file.old_path), jj_args.fileset(file.new_path) })
else
table.insert(cmd, jj_args.fileset(file.old_path))
end
else
-- For non-renamed files, use regular restore
local restore_cmd = { "jj", "restore", jj_args.fileset(file_info.old_path) }
end
local _, success = runner.execute(restore_cmd, "Failed to restore")
if success then
utils.notify("Restored: `" .. file_info.old_path .. "`", vim.log.levels.INFO)
require("jj.cmd").status()
local _, restore_success = runner.execute(cmd, "Failed to restore original file")
if restore_success then
local notif_msg = "Restored file:\n"
if #files > 1 then
notif_msg = "Restored files:\n"
end
for _, file in ipairs(files) do
if file.is_rename then
notif_msg = notif_msg .. "- `" .. file.old_path .. "` -> `" .. file.new_path .. "`\n"
else
notif_msg = notif_msg .. "- `" .. file.old_path .. "`\n"
end
end
utils.notify(notif_msg, vim.log.levels.INFO)
M.status() -- Refresh the status buffer after restoring files
end
end
@@ -73,14 +92,18 @@ end
function M.status_keymaps()
local cmd = require("jj.cmd")
local cfg = cmd.config.keymaps.status or {}
--- @type jj.cmd.keymap_specs
local specs = {
open_file = {
desc = "Open file under cursor",
handler = M.handle_status_enter,
modes = { "n" },
},
restore_file = {
desc = "Restore file under cursor",
handler = M.handle_status_restore,
modes = { "n", "v" },
},
}
+7 -1
View File
@@ -1,4 +1,10 @@
--- @class jj.core.parser
--- @class jj.core.parser.status_file
--- @field old_path string The original path of the file
--- @field new_path string The new path of the file (if renamed)
--- @field is_rename boolean Whether the file was renamed
local M = {}
--- Checks if the given value is a list of strings.
@@ -67,7 +73,7 @@ end
--- Parse the current line in the jj status buffer to extract file information.
--- Handles renamed files and regular status lines.
--- @return {old_path : string, new_path : string, is_rename : boolean}|nil A table with , or nil if parsing fails
--- @return jj.core.parser.status_file|nil A table with , or nil if parsing fails
function M.parse_file_info_from_status_line(line)
if not line then
return nil
+22
View File
@@ -1064,4 +1064,26 @@ function M.open_first_conflicted_file(revset)
end
end
--- Extract selected lines from visual mode, and the marks for the start and end lines.
--- @param bufnr integer The buffer number to get the selection from
--- @return string[]|nil The selected lines, or nil if not in visual mode
--- @return integer|nil The start line of the selection (1-based)
--- @return integer|nil The end line of the selection (1-based)
function M.get_visual_selection(bufnr)
local mode = vim.fn.mode()
if mode ~= "v" and mode ~= "V" and mode ~= "\22" then
return nil, nil, nil
end
local selected_start_line = vim.fn.line("v")
local selected_end_line = vim.fn.line(".")
if selected_start_line > selected_end_line then
selected_start_line, selected_end_line = selected_end_line, selected_start_line
end
local lines = vim.api.nvim_buf_get_lines(bufnr, selected_start_line - 1, selected_end_line, false)
return lines, selected_start_line, selected_end_line
end
return M