From 3f6049b17c506b1103ea13786d2773446fdebfbc Mon Sep 17 00:00:00 2001 From: Sivansh Gupta <76725678+sivansh11@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:33:06 +0530 Subject: [PATCH] feat: `D` to describe a change in log buffer (#23) --- lua/jj/cmd.lua | 69 +++++++++++++++++++++++++++++++++++------------- lua/jj/utils.lua | 20 ++++++++------ 2 files changed, 63 insertions(+), 26 deletions(-) diff --git a/lua/jj/cmd.lua b/lua/jj/cmd.lua index 560dba7..67e93e5 100644 --- a/lua/jj/cmd.lua +++ b/lua/jj/cmd.lua @@ -36,7 +36,9 @@ local state = { --- Close the current terminal buffer if it exists local function close_terminal_buffer() - if state.buf and vim.api.nvim_buf_is_valid(state.buf) then + if not state.buf then + return + elseif state.buf and vim.api.nvim_buf_is_valid(state.buf) then vim.cmd("bwipeout! " .. state.buf) else vim.cmd("close") @@ -421,6 +423,17 @@ local function handle_log_diff() end end +--- Handle describign a log line +local function handle_log_describe() + local line = vim.api.nvim_get_current_line() + local revset = get_rev_from_log_line(line) + if revset then + M.describe(nil, revset) + else + utils.notify("No valid revision found in the log line", vim.log.levels.ERROR) + end +end + --- Run a command and show it's output in a terminal buffer --- If a previous command already existed it smartly reuses the buffer cleaning the previous output ---@param cmd string @@ -594,6 +607,7 @@ local function run(cmd) register_command_keymap({ "n" }, "r", function() M.redo() end, { desc = "Redo last operation" }) + register_command_keymap({ "n" }, "D", handle_log_describe, { desc = "Describe change under cursor" }) end if #new_command_keymaps > 0 then @@ -627,17 +641,25 @@ end --- Execute jj describe command with the given description ---@param description string The description text -local function execute_describe(description) +local function execute_describe(description, revset) if not description or description == "" then utils.notify("Description cannot be empty", vim.log.levels.ERROR) return end - - -- Use --stdin to properly handle multi-line and special characters - local _, success = utils.execute_command("jj describe --stdin", "Failed to describe", description) - if success then - utils.notify("Description set.", vim.log.levels.INFO) - end + if revset == nil then + -- Use --stdin to properly handle multi-line and special characters + local _, success = utils.execute_command("jj describe --stdin", "Failed to describe", description) + if success then + utils.notify("Description set.", vim.log.levels.INFO) + end + else + -- Use --stdin to properly handle multi-line and special characters + local cmd = "jj describe -r " .. revset .. " --stdin" + local _, success = utils.execute_command(cmd, "Failed to describe", description) + if success then + utils.notify("Description set.", vim.log.levels.INFO) + end + end end --- @class jj.cmd.describe_opts @@ -651,28 +673,38 @@ local default_describe_opts = { --- Jujutsu describe ---@param description? string Optional description text ---@param opts? jj.cmd.describe_opts Optional command options -function M.describe(description, opts) +function M.describe(description, revset, opts) if not utils.ensure_jj() then return - end + end -- Check if a description was provided otherwise require for input if description then -- Description provided directly - execute_describe(description) + execute_describe(description, revset) else -- Use buffer editor mode if M.config.describe_editor == "buffer" then -- Build initial lines - local status_files = utils.get_status_files() - local text = { "JJ: This commit contains the following changes:" } + if not revset then + revset = '@' + end + local cmd = "jj log -r " .. revset .. " --no-graph -T 'coalesce(description, \"(no description set)\n\")'" + local old_description_raw, success = utils.execute_command(cmd, "Failed to get old description") + if not old_description_raw or not success then + return + end + local old_description = vim.trim(old_description_raw) + local status_files = utils.get_status_files(revset) + local text = { old_description } + table.insert(text, "") -- Empty line to separate from user input + table.insert(text, "JJ: Change ID: " .. revset) + table.insert(text, "JJ: This commit contains the following changes:") for _, item in ipairs(status_files) do table.insert(text, string.format("JJ: %s %s", item.status, item.file)) end table.insert(text, "JJ:") -- blank line - table.insert(text, 'JJ: Lines starting with "JJ:" (like this one) will be ignored when finalizing') - table.insert(text, "") -- Empty line to separate from user input - table.insert(text, "") -- Another empty line where user can start typing + table.insert(text, 'JJ: Lines starting with "JJ:" (like this one) will be removed') utils.open_ephemeral_buffer(text, function(buf_lines) local user_lines = {} @@ -683,8 +715,9 @@ function M.describe(description, opts) end -- Join lines and trim leading/trailing whitespace local trimmed_description = table.concat(user_lines, "\n"):gsub("^%s+", ""):gsub("%s+$", "") - execute_describe(trimmed_description) + execute_describe(trimmed_description, revset) end) + close_terminal_buffer() else local merged_opts = vim.tbl_deep_extend("force", default_describe_opts, opts or {}) if merged_opts.with_status then @@ -698,7 +731,7 @@ function M.describe(description, opts) }, function(input) -- If the user inputed something, execute the describe command if input then - execute_describe(input) + execute_describe(input, revset) end -- Close the current terminal when finished close_terminal_buffer() diff --git a/lua/jj/utils.lua b/lua/jj/utils.lua index d42ee9a..90a8308 100644 --- a/lua/jj/utils.lua +++ b/lua/jj/utils.lua @@ -158,12 +158,17 @@ end --- Get a list of files with their status in the current jj repository. --- @return table[] A list of tables with {status = string, file = string} -function M.get_status_files() +function M.get_status_files(revset) if not M.ensure_jj() then return {} end - local result, success = M.execute_command("jj status", "Error getting status") + if revset == nil then + revset = '@' + end + + cmd = "jj log -r " .. revset .. " --no-graph -T 'self.diff().summary()'" + local result, success = M.execute_command(cmd, "Error getting status") if not success or not result then return {} end @@ -304,12 +309,11 @@ function M.open_ephemeral_buffer(initial_text, on_done) -- Position cursor at the end (after the last JJ: line) and enter insert mode vim.schedule(function() - -- Get the number of lines in the buffer - local line_count = vim.api.nvim_buf_line_count(buf) - -- Move cursor to the last line, column 0 - vim.api.nvim_win_set_cursor(0, { line_count, 0 }) - -- Enter insert mode - vim.cmd("startinsert") + local line_count = vim.api.nvim_buf_line_count(buf) + local target_line_idx = line_count - 1 -- 0-indexed line number for API calls + local last_line_content = vim.api.nvim_buf_get_lines(buf, target_line_idx, line_count, false)[1] + local col_index = #last_line_content + vim.api.nvim_win_set_cursor(0, { 1, 0 }) end) -- Handle :w and :wq commands