mirror of
https://github.com/zoriya/jj.nvim.git
synced 2026-08-15 23:53:18 +00:00
fix(status): Make restoring files available in visual mode (#144)
This commit is contained in:
@@ -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.
|
||||

|
||||
|
||||
### Restore a changed file
|
||||
|
||||
Press `<S-x>` on a file from the `status` output and that's it, it's restored.
|
||||
|
||||

|
||||
|
||||
### Status picker (Snacks.nvim)
|
||||
|
||||
+1
-5
@@ -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
@@ -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" },
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user