From ba48ed08b5c08a7192b1a47a689e0c9f949fe5a4 Mon Sep 17 00:00:00 2001 From: NicolasGB Date: Mon, 5 Jan 2026 15:58:23 +0100 Subject: [PATCH] feat(log): add immutable-ignoring rebase keymaps Changes add support for rebasing while ignoring immutability constraints with new keymaps (, , , ) and update documentation. Refactors rebase handler to accept optional ignore_immut parameter and includes minor comment improvements in buffer and command modules. --- README.md | 12 +++++++++--- lua/jj/cmd/init.lua | 10 ++++++++-- lua/jj/cmd/log.lua | 43 ++++++++++++++++++++++++++++++++++++++---- lua/jj/core/buffer.lua | 6 +++--- 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3f5f6a1..42a71ba 100644 --- a/README.md +++ b/README.md @@ -134,8 +134,11 @@ Once in rebase mode, the interface highlights your selection and the current reb From rebase mode, choose how to rebase: - `` or `o` - Rebase onto (`-o`) the revision under cursor -- `a` or `A` - Rebase after (`-A`) the revision under cursor -- `b` or `B` - Rebase before (`-B`) the revision under cursor +- `a` - Rebase after (`-A`) the revision under cursor +- `b` - Rebase before (`-B`) the revision under cursor +- `` or `` - Rebase onto (`-o`) ignoring immutability +- `` - Rebase after (`-A`) ignoring immutability +- `` - Rebase before (`-B`) ignoring immutability - `` or `` - Exit rebase mode without making changes **Visual mode selection:** Select multiple revisions in visual mode before pressing `r` to rebase them all at once. The plugin extracts each selected revision and rebases them together. @@ -300,9 +303,12 @@ The plugin also provides `:Jdiff`, `:Jvdiff`, and `:Jhdiff` commands for diffing open_pr_list = "", -- Open PR/MR by selecting from all bookmarks rebase = "r", -- Enter rebase mode targeting revision under cursor or selected revisions rebase_mode = { - onto = { "", "o" }, -- Select revision under cursor as rebase destination + onto = { "", "o" }, -- Select revision under cursor as rebase onto destination after = { "a", "A" }, -- Rebase after revision under cursor before = { "b", "B" }, -- Rebase before revision under cursor + onto_immutable = { "", "" }, -- Select revision as a rebase onto destination (ignore immutability) + after_immutable = "", -- Rebase after revision under cursor (ignore immutability) + before_immutable = "", -- Rebase before revision under cursor (ignore immutability) exit_mode = { "", "" }, -- Exit rebase mode }, }, diff --git a/lua/jj/cmd/init.lua b/lua/jj/cmd/init.lua index 3f42200..96b40c0 100644 --- a/lua/jj/cmd/init.lua +++ b/lua/jj/cmd/init.lua @@ -52,6 +52,9 @@ local status_module = require("jj.cmd.status") --- @field onto? string|string[] --- @field after? string|string[] --- @field before? string|string[] +--- @field onto_immutable? string|string[] +--- @field after_immutable? string|string[] +--- @field before_immutable? string|string[] --- @field exit_mode? string|string[] --- @class jj.cmd.bookmark @@ -128,8 +131,11 @@ M.config = { rebase = "r", rebase_mode = { onto = { "", "o" }, - after = { "a", "A" }, - before = { "b", "B" }, + after = "a", + before = "b", + onto_immutable = { "", "" }, + after_immutable = "", + before_immutable = "", exit_mode = { "", "" }, }, }, diff --git a/lua/jj/cmd/log.lua b/lua/jj/cmd/log.lua index 6c6a020..29287d0 100644 --- a/lua/jj/cmd/log.lua +++ b/lua/jj/cmd/log.lua @@ -127,7 +127,7 @@ local function apply_target_highlight(buf, revset_line, hl_group) log_rebase_target_ns_id, revset_line, 0, - { end_line = revset_line + HIGHLIGHT_RANGE, hl_group = hl_group } + { end_line = revset_line + HIGHLIGHT_RANGE, end_col = 0, hl_group = hl_group } ) last_rebase_target_line = revset_line end @@ -176,6 +176,9 @@ function M.log(opts) -- If a log was already being displayed before this command we will want to maintain the cursor position if terminal.state.buf_cmd == "log" then terminal.store_cursor_position() + -- Make sure to clear highlights before rerunning since the previous log buffer might have some + vim.api.nvim_buf_clear_namespace(terminal.state.buf, log_selected_ns_id, 0, -1) + vim.api.nvim_buf_clear_namespace(terminal.state.buf, log_rebase_target_ns_id, 0, -1) end local jj_cmd = "jj log" @@ -636,7 +639,7 @@ function M.log_keymaps() modes = { "n" }, }, new_after_immutable = { - desc = "Create new change after revision under cursor (ignore immutable)", + desc = "Create new change after revision under cursor (ignores immutability)", handler = M.handle_log_new, args = { "after", true }, modes = { "n" }, @@ -726,6 +729,24 @@ function M.rebase_keymaps() args = { "before" }, modes = { "n" }, }, + onto_immutable = { + desc = "Rebase onto (-O) the revision under cursor (ignores immutability)", + handler = M.handle_rebase_execute, + args = { "onto", true }, + modes = { "n" }, + }, + after_immutable = { + desc = "Rebase revset(s) after (-A) the revision under cursor (ignores immutability)", + handler = M.handle_rebase_execute, + args = { "after", true }, + modes = { "n" }, + }, + before_immutable = { + desc = "Rebase revset(s) before (-B) the revision under cursor (ignores immutability)", + handler = M.handle_rebase_execute, + args = { "before", true }, + modes = { "n" }, + }, exit_mode = { desc = "Exit rebase to normal mode", handler = M.handle_rebase_mode_exit, @@ -790,13 +811,15 @@ function M.handle_rebase_mode_exit() -- Clear highlights local buf = terminal.state.buf or 0 vim.api.nvim_buf_clear_namespace(buf, log_selected_ns_id, 0, -1) + vim.api.nvim_buf_clear_namespace(buf, log_rebase_target_ns_id, 0, -1) - utils.notify("Rebase operation `canceled`", vim.log.levels.INFO, 500) + utils.notify("Rebase `canceled`", vim.log.levels.INFO, 500) end --- Handle rebase execution with mode --- @param mode "onto" | "after" | "before" Rebase mode -function M.handle_rebase_execute(mode) +--- @param ignore_immut boolean? Wether or not to ignore immutability +function M.handle_rebase_execute(mode, ignore_immut) -- Get all revsets in the format "xx xy xz" local revsets = vim.b.jj_rebase_revsets local destination_revset = get_revset() @@ -813,6 +836,13 @@ function M.handle_rebase_execute(mode) utils.notify(string.format("Rebasing...", revsets, mode, destination_revset), vim.log.levels.INFO, 500) local cmd = string.format("jj rebase -r '%s' %s %s", revsets, mode_flat, destination_revset) + + -- If ignore_immut is true, add the flag + -- This is not currently exposed in keymaps but could be in the future + if ignore_immut then + cmd = cmd .. " --ignore-immutable" + end + runner.execute_command_async(cmd, function() utils.notify( string.format("Rebased `%s` %s `%s` successfully", revsets, mode, destination_revset), @@ -820,6 +850,11 @@ function M.handle_rebase_execute(mode) ) vim.b.jj_rebase_revsets = nil + -- Clear all highlighting before transitioning + local buf = terminal.state.buf or 0 + vim.api.nvim_buf_clear_namespace(buf, log_selected_ns_id, 0, -1) + vim.api.nvim_buf_clear_namespace(buf, log_rebase_target_ns_id, 0, -1) + M.transition_mode("normal") -- Refresh log M.log({}) diff --git a/lua/jj/core/buffer.lua b/lua/jj/core/buffer.lua index 828cb0b..030b5cc 100644 --- a/lua/jj/core/buffer.lua +++ b/lua/jj/core/buffer.lua @@ -447,9 +447,9 @@ function M.set_cursor(buf, pos, opts) return end - -- For terminal buffers, delay to allow rendering to complete - -- Must validate position INSIDE the deferred function for terminal buffers - -- because the buffer content may not be stable yet + -- For terminal buffers, we delay cursor positioning to allow async rendering to complete. + -- The position is validated inside the deferred function because buffer content + -- may not be stable at function call time if vim.bo[buf].buftype == "terminal" or delay > 0 then vim.defer_fn(function() if not vim.api.nvim_buf_is_valid(buf) or not vim.api.nvim_win_is_valid(winid) then