mirror of
https://github.com/zoriya/jj.nvim.git
synced 2026-08-05 02:36:07 +00:00
- 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.
210 lines
6.4 KiB
Lua
210 lines
6.4 KiB
Lua
--- @class jj.ui.editor
|
|
local M = {}
|
|
|
|
local buffer = require("jj.core.buffer")
|
|
|
|
--- @class jj.ui.editor.opts
|
|
--- @field auto_insert? boolean Smart insert: enter insert mode when description is empty, stay in normal mode when one exists
|
|
--- @field highlights? jj.ui.editor.highlights
|
|
--- @field window? jj.ui.editor.window
|
|
|
|
--- @class jj.ui.editor.highlights
|
|
---@field added? table Highlight settings for added lines
|
|
---@field modified? table Highlight settings for modified lines
|
|
---@field deleted? table Highlight settings for deleted lines
|
|
---@field renamed? table Highlight settings for renamed lines
|
|
|
|
--- @class jj.ui.editor.window
|
|
--- @field type? "hsplit"|"vsplit"|"floating"|"tab" Type of window the terminal is displayed in
|
|
--- @field split_size? number Size % of the split window, either height (hsplit) or width (vsplit) (between 0.1 and 1.0)
|
|
--- @field floating_width? number Width % of the floating window (between 0.1 and 1.0)
|
|
--- @field floating_height? number Height % of the floating window (between 0.1 and 1.0)
|
|
|
|
--- @type jj.ui.editor.opts
|
|
M.opts = {
|
|
highlights = {
|
|
-- Only init this one by default since it's not handled natively by neovim
|
|
renamed = { fg = "#d29922", ctermfg = "Yellow" },
|
|
},
|
|
auto_insert = true,
|
|
}
|
|
|
|
--- Private module state to track highlights initialization
|
|
local highlights_initialized = false
|
|
|
|
-- Initialize highlight groups once
|
|
local function init_highlights()
|
|
if highlights_initialized then
|
|
return
|
|
end
|
|
|
|
-- Override the highlight groups if user provided custom settings
|
|
if M.opts.highlights.added then
|
|
vim.api.nvim_set_hl(0, "Added", M.opts.highlights.added)
|
|
end
|
|
|
|
if M.opts.highlights.modified then
|
|
vim.api.nvim_set_hl(0, "Changed", M.opts.highlights.modified)
|
|
end
|
|
|
|
if M.opts.highlights.deleted then
|
|
vim.api.nvim_set_hl(0, "Removed", M.opts.highlights.deleted)
|
|
end
|
|
|
|
-- 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
|
|
|
|
highlights_initialized = true
|
|
end
|
|
|
|
--- Setup function to configure highlights and other options
|
|
---@param user_opts? jj.ui.editor.opts Configuration options
|
|
function M.setup(user_opts)
|
|
M.opts = vim.tbl_deep_extend("force", M.opts, user_opts or {})
|
|
|
|
-- Reset highlights flag to force re-initialization with new highlights
|
|
if highlights_initialized then
|
|
highlights_initialized = false
|
|
init_highlights()
|
|
end
|
|
end
|
|
|
|
---@param initial_text string[] Lines to initialize the buffer with
|
|
---@param on_write fun(buf: string[])? Optional callback called with user text on buffer write
|
|
---@param on_unload? fun(buf: string[])? Optional callback with user text called when the buffer is closed
|
|
---@param keymaps? jj.core.buffer.keymap[] Optional keymaps for the buffer
|
|
function M.open_editor(initial_text, on_write, on_unload, keymaps)
|
|
-- Initialize highlight groups once
|
|
init_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")
|
|
|
|
-- Apply explicit highlight for renamed entries in JJ header lines.
|
|
-- Example line: "JJ: R path/to/file"
|
|
local function apply_highlights(buf)
|
|
if not vim.api.nvim_buf_is_valid(buf) then
|
|
return
|
|
end
|
|
|
|
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 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
|
|
|
|
-- Create buffer
|
|
local buf = M.create_buffer()
|
|
-- Set keymaps before setting content to avoid triggering them during setup
|
|
buffer.set_keymaps(buf, keymaps or {})
|
|
|
|
-- Set buffer content
|
|
vim.api.nvim_buf_set_lines(buf, 0, -1, false, initial_text)
|
|
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()
|
|
if not vim.api.nvim_buf_is_valid(buf) then
|
|
return
|
|
end
|
|
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
|
local desc = require("jj.utils").extract_description_from_describe(lines)
|
|
if not desc or desc == "" then
|
|
vim.cmd("startinsert")
|
|
end
|
|
end)
|
|
end
|
|
|
|
-- Handle :w and :wq commands
|
|
vim.api.nvim_create_autocmd("BufWriteCmd", {
|
|
buffer = buf,
|
|
callback = function()
|
|
-- Get current buffer lines
|
|
local buf_lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
|
-- Update the last written lines variable
|
|
vim.b[buf].jj_last_written_lines = buf_lines
|
|
vim.bo[buf].modified = false
|
|
-- Call the on_write callback if provided
|
|
if on_write then
|
|
on_write(buf_lines)
|
|
end
|
|
end,
|
|
})
|
|
|
|
-- Register the on_close callback
|
|
if on_unload then
|
|
vim.api.nvim_create_autocmd("BufWipeout", {
|
|
buffer = buf,
|
|
callback = function()
|
|
local last_written = vim.b[buf].jj_last_written_lines
|
|
on_unload(last_written)
|
|
end,
|
|
})
|
|
end
|
|
end
|
|
|
|
--- Create the buffer based on the module options.
|
|
--- @return number buf Buffer number of the created buffer
|
|
function M.create_buffer()
|
|
if M.opts.window.type == "floating" then
|
|
local buf, _ = buffer.create_float({
|
|
title = "JJ editor",
|
|
title_pos = "center",
|
|
filetype = "jjdescription",
|
|
buftype = "acwrite",
|
|
bufhidden = "wipe",
|
|
enter = true,
|
|
modifiable = true,
|
|
height = math.floor(vim.o.lines * M.opts.window.floating_height),
|
|
width = math.floor(vim.o.columns * M.opts.window.floating_width),
|
|
win_options = {
|
|
wrap = true,
|
|
number = false,
|
|
relativenumber = false,
|
|
cursorline = false,
|
|
signcolumn = "no",
|
|
winfixbuf = true,
|
|
},
|
|
})
|
|
|
|
return buf
|
|
else
|
|
-- Get the lines/columns based on the direction of the split
|
|
local full_size = M.opts.window.type == "hsplit" and vim.o.lines or vim.o.columns
|
|
|
|
return buffer.create({
|
|
name = "jj:///DESCRIBE_EDITMSG",
|
|
filetype = "jjdescription",
|
|
buftype = "acwrite",
|
|
bufhidden = "wipe",
|
|
modifiable = true,
|
|
split = buffer.resolve_split(M.opts.window.type),
|
|
size = math.floor(full_size * M.opts.window.split_size),
|
|
})
|
|
end
|
|
end
|
|
|
|
return M
|