mirror of
https://github.com/zoriya/jj.nvim.git
synced 2026-08-05 02:36:07 +00:00
feat(status,highlights): Handle renamed files in restore and picker flows
- Improve status-line parsing to support top-level and nested rename
syntaxes, so actions no longer include trailing braces in paths.
- Update the Snacks status picker integration to pass structured
rename/status metadata and use git_status formatting, preserving
icons/highlighting while showing rename pairs correctly.
- Also tighten renamed-line highlighting in the editor buffer for consistent jjRenamed behavior.
This commit is contained in:
+28
-31
@@ -55,50 +55,47 @@ end
|
||||
--- 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
|
||||
function M.parse_file_info_from_status_line(line)
|
||||
-- Handle renamed files: "R path/{old_name => new_name}" or "R old_path => new_path"
|
||||
local rename_pattern_curly = "^R (.*)/{(.*) => ([^}]+)}"
|
||||
local dir_path, old_name, new_name = line:match(rename_pattern_curly)
|
||||
if not line then
|
||||
return nil
|
||||
end
|
||||
|
||||
line = vim.trim(line)
|
||||
|
||||
-- Handle renamed files in nested path form:
|
||||
-- R dir/{old_name => new_name}
|
||||
local dir_path, old_name, new_name = line:match("^R%s+(.*)/{(.*)%s=>%s([^}]+)}$")
|
||||
if dir_path and old_name and new_name then
|
||||
return {
|
||||
old_path = dir_path .. "/" .. old_name,
|
||||
new_path = dir_path .. "/" .. new_name,
|
||||
is_rename = true,
|
||||
}
|
||||
else
|
||||
-- Try simple rename pattern: "R old_path => new_path"
|
||||
local rename_pattern_simple = "^R (.*) => (.+)$"
|
||||
local old_path, new_path = line:match(rename_pattern_simple)
|
||||
if old_path and new_path then
|
||||
return {
|
||||
old_path = old_path,
|
||||
new_path = new_path,
|
||||
is_rename = true,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- Not a rename, try regular status patterns
|
||||
local filepath
|
||||
-- Handle renamed files: "R path/{old_name => new_name}" or "R old_path => new_path"
|
||||
local rename_pattern_curly_new = "^R (.*)/{.* => ([^}]+)}"
|
||||
local dir_path_new, renamed_file = line:match(rename_pattern_curly_new)
|
||||
|
||||
if dir_path_new and renamed_file then
|
||||
filepath = dir_path_new .. "/" .. renamed_file
|
||||
else
|
||||
-- Try simple rename pattern: "R old_path => new_path"
|
||||
local rename_pattern_simple_new = "^R .* => (.+)$"
|
||||
filepath = line:match(rename_pattern_simple_new)
|
||||
-- Handle renamed files in top-level brace form:
|
||||
-- R {old_name => new_name}
|
||||
local old_top, new_top = line:match("^R%s+{(.-)%s=>%s([^}]+)}$")
|
||||
if old_top and new_top then
|
||||
return {
|
||||
old_path = old_top,
|
||||
new_path = new_top,
|
||||
is_rename = true,
|
||||
}
|
||||
end
|
||||
|
||||
if not filepath then
|
||||
-- jj status format: "M filename" or "A filename"
|
||||
-- Match lines that start with status letter followed by space and filename
|
||||
local pattern = "^[MAD?!] (.+)$"
|
||||
filepath = line:match(pattern)
|
||||
-- Handle simple rename form:
|
||||
-- R old_path => new_path
|
||||
local old_path, new_path = line:match("^R%s+(.+)%s=>%s(.+)$")
|
||||
if old_path and new_path then
|
||||
return {
|
||||
old_path = vim.trim(old_path),
|
||||
new_path = vim.trim(new_path),
|
||||
is_rename = true,
|
||||
}
|
||||
end
|
||||
|
||||
-- Regular status lines (M/A/D/?/!)
|
||||
local filepath = line:match("^[MAD?!]%s+(.+)$")
|
||||
if filepath then
|
||||
return {
|
||||
old_path = filepath,
|
||||
|
||||
+21
-9
@@ -1,5 +1,6 @@
|
||||
local utils = require("jj.utils")
|
||||
local runner = require("jj.core.runner")
|
||||
local parser = require("jj.core.parser")
|
||||
|
||||
--- @class jj.picker
|
||||
|
||||
@@ -7,8 +8,9 @@ local runner = require("jj.core.runner")
|
||||
--- @field snacks table|boolean The snacks config
|
||||
|
||||
--- @class jj.picker.file
|
||||
--- @field file string The path of the file
|
||||
--- @field change string The type of change in the file
|
||||
--- @field file string The current path of the file
|
||||
--- @field status string JJ-style status code (e.g. "M ", "R ") for picker formatting
|
||||
--- @field rename? string Previous path when this item is a rename
|
||||
--- @field diff_cmd string The command to get the diff of the file
|
||||
|
||||
--- @class jj.picker.log_line
|
||||
@@ -51,14 +53,24 @@ local function get_files()
|
||||
local lines = vim.split(diff_ouptut, "\n", { trimempty = true })
|
||||
|
||||
for _, line in ipairs(lines) do
|
||||
local change, file_path = line:match("^(%a)%s(.+)$")
|
||||
local change = line:match("^(%a)%s")
|
||||
local file_info = parser.parse_file_info_from_status_line(line)
|
||||
|
||||
table.insert(files, {
|
||||
text = file_path,
|
||||
file = file_path,
|
||||
change = change,
|
||||
diff_cmd = string.format("jj diff %s", file_path),
|
||||
})
|
||||
if change and file_info and file_info.new_path then
|
||||
local file_path = file_info.new_path
|
||||
local item = {
|
||||
text = line:sub(3),
|
||||
file = file_path,
|
||||
status = change .. " ",
|
||||
diff_cmd = string.format("jj diff %s", vim.fn.shellescape(file_path)),
|
||||
}
|
||||
|
||||
if change == "R" and file_info.old_path and file_info.old_path ~= file_info.new_path then
|
||||
item.rename = file_info.old_path
|
||||
end
|
||||
|
||||
table.insert(files, item)
|
||||
end
|
||||
end
|
||||
|
||||
return files
|
||||
|
||||
@@ -28,6 +28,7 @@ function M.status(opts, files)
|
||||
source = "jj",
|
||||
items = files,
|
||||
title = "JJ Status",
|
||||
format = "git_status",
|
||||
actions = {
|
||||
open_and_diff = function(picker, item)
|
||||
picker:close()
|
||||
|
||||
+25
-38
@@ -51,7 +51,8 @@ local function init_highlights()
|
||||
vim.api.nvim_set_hl(0, "Removed", M.opts.highlights.deleted)
|
||||
end
|
||||
|
||||
-- this one will always be executed since the default nvim highlight group does not exist for renames
|
||||
-- Neovim's jj syntax uses jjRenamed for renamed files.
|
||||
-- Also define Renamed for backward compatibility with existing user configs.
|
||||
if M.opts.highlights.renamed then
|
||||
vim.api.nvim_set_hl(0, "jjRenamed", M.opts.highlights.renamed)
|
||||
end
|
||||
@@ -79,40 +80,28 @@ function M.open_editor(initial_text, on_write, on_unload, keymaps)
|
||||
-- Initialize highlight groups once
|
||||
init_highlights()
|
||||
|
||||
-- Create a namespace for our highlights
|
||||
-- Create a namespace for explicit renamed-line highlighting (independent of syntax file support)
|
||||
local ns_id = vim.api.nvim_create_namespace("jj_describe_highlights")
|
||||
|
||||
-- Function to apply highlights to the buffer
|
||||
-- Apply explicit highlight for renamed entries in JJ header lines.
|
||||
-- Example line: "JJ: R path/to/file"
|
||||
local function apply_highlights(buf)
|
||||
-- Clear existing highlights
|
||||
vim.api.nvim_buf_clear_namespace(buf, ns_id, 0, -1)
|
||||
if not vim.api.nvim_buf_is_valid(buf) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Get all lines
|
||||
vim.api.nvim_buf_clear_namespace(buf, ns_id, 0, -1)
|
||||
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||
|
||||
for i, line in ipairs(lines) do
|
||||
local line_idx = i - 1 -- 0-indexed
|
||||
|
||||
-- First, check if line starts with JJ: and highlight it as comment
|
||||
if line:match("^JJ:") then
|
||||
-- Then check for rename status indicator
|
||||
local status_pos = line:find("[R] ", 4) -- Find status after "JJ:"
|
||||
if status_pos then
|
||||
local status = line:sub(status_pos, status_pos) -- Get the status character
|
||||
local hl_group = nil
|
||||
|
||||
if status == "R" then
|
||||
hl_group = "jjRenamed"
|
||||
end
|
||||
|
||||
if hl_group then
|
||||
-- Highlight from the status character to the end of the line
|
||||
vim.api.nvim_buf_set_extmark(buf, ns_id, line_idx, status_pos - 1, {
|
||||
end_col = #line,
|
||||
hl_group = hl_group,
|
||||
})
|
||||
end
|
||||
end
|
||||
local status_pos = line:match("^JJ:%s+()R%s")
|
||||
if status_pos then
|
||||
-- Highlight only the status letter (R), like A/M/D syntax groups do.
|
||||
vim.api.nvim_buf_set_extmark(buf, ns_id, i - 1, status_pos - 1, {
|
||||
end_col = status_pos,
|
||||
hl_group = "jjRenamed",
|
||||
priority = 200,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -124,10 +113,16 @@ function M.open_editor(initial_text, on_write, on_unload, keymaps)
|
||||
|
||||
-- Set buffer content
|
||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, initial_text)
|
||||
|
||||
-- Apply highlights initially
|
||||
apply_highlights(buf)
|
||||
|
||||
-- Keep explicit highlights up-to-date while editing
|
||||
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI", "BufEnter" }, {
|
||||
buffer = buf,
|
||||
callback = function()
|
||||
apply_highlights(buf)
|
||||
end,
|
||||
})
|
||||
|
||||
-- Smart insert mode: insert when description is empty, normal mode otherwise
|
||||
if M.opts.auto_insert then
|
||||
vim.schedule(function()
|
||||
@@ -142,14 +137,6 @@ function M.open_editor(initial_text, on_write, on_unload, keymaps)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Reapply highlights when text changes
|
||||
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, {
|
||||
buffer = buf,
|
||||
callback = function()
|
||||
apply_highlights(buf)
|
||||
end,
|
||||
})
|
||||
|
||||
-- Handle :w and :wq commands
|
||||
vim.api.nvim_create_autocmd("BufWriteCmd", {
|
||||
buffer = buf,
|
||||
|
||||
Reference in New Issue
Block a user