feat(bookmark): add bookmark management from log buffer and bookmark_move API (#51)

This commit is contained in:
Nicolas GB
2025-11-27 22:07:22 +01:00
committed by GitHub
parent b32ba1aa5e
commit d63761e1bf
4 changed files with 169 additions and 14 deletions
+35 -3
View File
@@ -83,10 +83,16 @@ You can fetch and push directly from the log buffer:
- `<S-p>` - Push all changes to remote
- `p` - Push bookmark of revision under cursor to remote
### Manage bookmarks from the log buffer
- `b` - Create a new bookmark or move an existing one to the revision under cursor
- Select from existing bookmarks to move them
- Or create a new bookmark at that revision
### Open a PR/MR from the log buffer
- `o` - Open a PR/MR for the revision under cursor
- `<S-o>` - Select a bookmark from all available bookmarks and open a PR/MR
- `<S-o>` - Select a remote from all available bookmarks and open a PR/MR
The plugin automatically:
@@ -142,6 +148,7 @@ The plugin provides a `:J` command that accepts jj subcommands:
:J fetch " Fetch from remote
:J open_pr " Open PR for current change's bookmark
:J open_pr --list " Select bookmark from all and open PR
:J bookmark create/move/delete
:J # This will use your defined default command
:J <your-alias>
```
@@ -220,6 +227,7 @@ The plugin also provides `:Jdiff`, `:Jvdiff`, and `:Jhdiff` commands for diffing
undo = "<S-u>", -- Undo last operation
redo = "<S-r>", -- Redo last undone operation
abandon = "a", -- Abandon revision under cursor
bookmark = "b", -- Create or move bookmark to revision under cursor
fetch = "f", -- Fetch from remote
push = "p", -- Push bookmark of revision under cursor
push_all = "<S-p>", -- Push all changes to remote
@@ -359,6 +367,29 @@ cmd.push({ bookmark = "main" }) -- Push only main bookmark
cmd.push({ bookmark = "feature" }) -- Push only feature bookmark
```
### Bookmark Management Command Options
The `bookmark_create` function creates a new bookmark:
```lua
local cmd = require("jj.cmd")
cmd.bookmark_create() -- Prompts for bookmark name, then prompts the revision
```
The `bookmark_move` function moves an existing bookmark to a new revision:
```lua
local cmd = require("jj.cmd")
cmd.bookmark_move() -- Select bookmark, then specify new revset
```
The `bookmark_delete` function deletes a bookmark:
```lua
local cmd = require("jj.cmd")
cmd.bookmark_delete() -- Select bookmark to delete
```
### Open PR/MR Command Options
The `open_pr` function accepts an options table:
@@ -445,8 +476,9 @@ diff.open_hsplit({ rev = "@-2" }) -- Horizontal split against @-2
vim.keymap.set("n", "<leader>ju", cmd.undo, { desc = "JJ undo" })
vim.keymap.set("n", "<leader>jy", cmd.redo, { desc = "JJ redo" })
vim.keymap.set("n", "<leader>jr", cmd.rebase, { desc = "JJ rebase" })
vim.keymap.set("n", "<leader>jb", cmd.bookmark_create, { desc = "JJ bookmark create" })
vim.keymap.set("n", "<leader>jB", cmd.bookmark_delete, { desc = "JJ bookmark delete" })
vim.keymap.set("n", "<leader>jbc", cmd.bookmark_create, { desc = "JJ bookmark create" })
vim.keymap.set("n", "<leader>jbd", cmd.bookmark_delete, { desc = "JJ bookmark delete" })
vim.keymap.set("n", "<leader>jbm", cmd.bookmark_move, { desc = "JJ bookmark move" })
vim.keymap.set("n", "<leader>ja", cmd.abandon, { desc = "JJ abandon" })
vim.keymap.set("n", "<leader>jf", cmd.fetch, { desc = "JJ fetch" })
vim.keymap.set("n", "<leader>jp", cmd.push, { desc = "JJ push" })
+67 -5
View File
@@ -41,6 +41,7 @@ local status_module = require("jj.cmd.status")
--- @field push? string|string[]
--- @field open_pr? string|string[]
--- @field open_pr_list? string|string[]
--- @field bookmark? string|string[]
--- @class jj.cmd.status.keymaps
--- @field open_file? string|string[] Keymaps for the status command buffer, setting a keymap to nil will disable it
@@ -104,6 +105,7 @@ M.config = {
push = "p",
open_pr = "o",
open_pr_list = "<S-o>",
bookmark = "b",
},
status = {
open_file = "<CR>",
@@ -356,11 +358,60 @@ function M.bookmark_create()
prompt = "Bookmark name: ",
}, function(input)
if input then
local cmd = string.format("jj b c %s", input)
runner.execute_command_async(cmd, function()
utils.notify(string.format("Bookmark `%s` created successfully for @", input), vim.log.levels.INFO)
M.log({})
end, "Error creating bookmark")
-- Get the revset
vim.ui.input({
prompt = "Revset (default: @): ",
default = "@",
}, function(revset)
revset = revset or "@"
local cmd = string.format("jj b c %s %s", input, revset)
runner.execute_command_async(cmd, function()
utils.notify(
string.format("Bookmark `%s` created successfully for %s", input, revset),
vim.log.levels.INFO
)
M.log({})
end, "Error creating bookmark")
end)
else
terminal.close_terminal_buffer()
end
end)
end
-- Jujutsu bookmark move
function M.bookmark_move()
if not utils.ensure_jj() then
return
end
M.log({})
local bookmarks = utils.get_all_bookmarks()
if #bookmarks == 0 then
utils.notify("No bookmarks found to move", vim.log.levels.ERROR)
return
end
vim.ui.select(bookmarks, {
prompt = "Select bookmark to move: ",
}, function(choice)
if choice then
vim.ui.input({
prompt = "New revset for bookmark '" .. choice .. "': ",
default = "@",
}, function(revset)
if revset then
local cmd = string.format("jj b m %s --to %s", choice, revset)
runner.execute_command_async(cmd, function()
utils.notify(
string.format("Bookmark `%s` moved successfully to %s", choice, revset),
vim.log.levels.INFO
)
M.log({})
end, "Error moving bookmark")
else
terminal.close_terminal_buffer()
end
end)
else
terminal.close_terminal_buffer()
end
@@ -665,6 +716,17 @@ function M.j(args)
M.open_pr()
end
end,
bookmark = function()
if remaining_args[1] == "create" or remaining_args[1] == "c" then
M.bookmark_create()
elseif remaining_args[1] == "move" or remaining_args[1] == "m" then
M.bookmark_move()
elseif remaining_args[1] == "delete" or remaining_args[1] == "d" then
M.bookmark_delete()
else
terminal.run(cmd, M.terminal_keymaps())
end
end,
}
if handlers[subcommand] then
+60 -6
View File
@@ -235,9 +235,13 @@ end
function M.handle_log_push_all()
local cmd = "jj git push"
utils.notify("Pushing `ALL` bookmarks", vim.log.levels.INFO)
runner.execute_command_async(cmd, function()
utils.notify("Successfully pushed all to remote", vim.log.levels.INFO)
M.log({})
runner.execute_command_async(cmd, function(output)
if output and string.find(output, "Nothing changed%.") then
utils.notify("Nothing changed.", vim.log.levels.INFO)
else
utils.notify("Successfully pushed all to remote", vim.log.levels.INFO)
M.log({})
end
end, "Error pushing to remote")
end
@@ -270,9 +274,13 @@ function M.handle_log_push_bookmark()
-- Push the bookmark from the revset found
local cmd = string.format("jj git push --bookmark %s -N", bookmark)
utils.notify(string.format("Pushing bookmark `%s`...", bookmark), vim.log.levels.INFO)
runner.execute_command_async(cmd, function()
utils.notify(string.format("Successfully pushed bookmark for `%s`", revset), vim.log.levels.INFO)
M.log({})
runner.execute_command_async(cmd, function(output)
if output and string.find(output, "Nothing changed%.") then
utils.notify("Nothing changed.", vim.log.levels.INFO)
else
utils.notify(string.format("Successfully pushed bookmark for `%s`", revset), vim.log.levels.INFO)
M.log({})
end
end, string.format("Error pushing bookmark for `%s`", revset))
end
@@ -331,6 +339,48 @@ function M.handle_log_open_pr(list_bookmarks)
utils.open_pr_for_bookmark(bookmark)
end
-- Create or move bookmark at revision under cursor in `jj log` buffer
local function handle_log_bookmark()
local line = vim.api.nvim_get_current_line()
local revset = parser.get_rev_from_log_line(line)
if not revset or revset == "" then
return
end
-- Get all bookmarks
local bookmarks = utils.get_all_bookmarks()
table.insert(bookmarks, 1, "[Create new]")
-- Prompt to select or create a bookmark
vim.ui.select(bookmarks, {
prompt = "Select a bookmark to move or create a new: ",
}, function(choice)
if choice then
if choice == "[Create new]" then
-- Prompt for new bookmark name
vim.ui.input({ prompt = "Enter new bookmark name: " }, function(input)
if input and input ~= "" then
local cmd = string.format("jj bookmark create %s -r %s", input, revset)
runner.execute_command_async(cmd, function()
utils.notify(
string.format("Created bookmark `%s` at `%s`", input, revset),
vim.log.levels.INFO
)
M.log({})
end, "Error creating bookmark")
end
end)
else
-- Move existing bookmark to the revision
local cmd = string.format("jj bookmark move %s --to %s", choice, revset)
runner.execute_command_async(cmd, function()
utils.notify(string.format("Moved bookmark `%s` to `%s`", choice, revset), vim.log.levels.INFO)
M.log({})
end, "Error moving bookmark")
end
end
end)
end
--- Resolve log keymaps from config, filtering out nil values
--- @return jj.core.buffer.keymap[]
function M.log_keymaps()
@@ -414,6 +464,10 @@ function M.log_keymaps()
handler = M.handle_log_open_pr,
args = { true },
},
bookmark = {
desc = "Create or move bookmark at revision under cursor",
handler = handle_log_bookmark,
},
}
return cmd.merge_keymaps(cmd.resolve_keymaps_from_specs(keymaps, specs), cmd.terminal_keymaps())
+7
View File
@@ -46,6 +46,13 @@ function M.execute_command_async(cmd, on_success, error_prefix, input, silent)
end
end
end,
on_stderr = function(_, data)
for _, line in ipairs(data) do
if line ~= "" then
table.insert(output_lines, line)
end
end
end,
on_exit = function(_, exit_code)
local output = table.concat(output_lines, "\n")
if exit_code == 0 then