mirror of
https://github.com/zoriya/jj.nvim.git
synced 2026-08-05 02:36:07 +00:00
feat(picker): Add a snacks picker with jj status command
This commit is contained in:
@@ -23,6 +23,7 @@ This plugin aims to be something like vim-fugitive but for piloting the jj-vcs C
|
||||
- `new` - Create a new change
|
||||
- `edit` - Edit a change
|
||||
- `squash` - Squash the current diff to it's parent
|
||||
- Picker `git status` like for [Snacks.nvim](https://github.com/folke/snacks.nvim)
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -65,6 +66,10 @@ The plugin provides a `:J` command that accepts jj subcommands:
|
||||
vim.keymap.set("n", "<leader>js", cmd.status, { desc = "JJ status" })
|
||||
vim.keymap.set("n", "<leader>dj", cmd.diff, { desc = "JJ diff" })
|
||||
vim.keymap.set("n", "<leader>sj", cmd.squash, { desc = "JJ squash" })
|
||||
vim.keymap.set("n", "<leader>gj", function()
|
||||
require("jj.picker").status()
|
||||
end, { desc = "JJ Picker status" })
|
||||
|
||||
|
||||
-- Some functions like `describe` or `log` can take parameters
|
||||
vim.keymap.set("n", "<leader>jl", function()
|
||||
|
||||
+8
-6
@@ -1,22 +1,24 @@
|
||||
local M = {}
|
||||
local cmd = require("jj.cmd")
|
||||
|
||||
M.picker_config = {
|
||||
snacks = {
|
||||
layout = "horizontal",
|
||||
},
|
||||
}
|
||||
local picker = require("jj.picker")
|
||||
|
||||
--- Jujutsu plugin configuration
|
||||
--- @class jj.Config
|
||||
M.config = {
|
||||
-- Default configuration
|
||||
--- @type jj.picker.config
|
||||
picker = {
|
||||
snacks = {},
|
||||
},
|
||||
}
|
||||
|
||||
--- Setup the plugin
|
||||
--- @param opts jj.Config: Options to configure the plugin
|
||||
function M.setup(opts)
|
||||
M.config = vim.tbl_deep_extend("force", M.config, opts or {})
|
||||
|
||||
picker.setup(opts.picker)
|
||||
|
||||
cmd.register_command()
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
local utils = require("jj.utils")
|
||||
|
||||
--- @class jj.picker
|
||||
|
||||
--- @class jj.picker.config
|
||||
--- @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 diff_cmd string The command to get the diff of the file
|
||||
|
||||
local M = {
|
||||
--- @type jj.picker.config
|
||||
config = {
|
||||
snacks = {},
|
||||
},
|
||||
}
|
||||
|
||||
--- Initializes the picker
|
||||
--- @param opts jj.picker.config
|
||||
function M.setup(opts)
|
||||
M.config = vim.tbl_deep_extend("force", M.config, opts or {})
|
||||
end
|
||||
|
||||
--- Gets the files in the current jj repository
|
||||
--- @return jj.picker.file[]|nil A list of files with their changes or nil if not in a jj repo
|
||||
local function get_files()
|
||||
local diff_ouptut, ok = utils.execute_command("jj diff --summary --quiet")
|
||||
if not ok then
|
||||
return
|
||||
end
|
||||
|
||||
if type(diff_ouptut) ~= "string" then
|
||||
return utils.notify("Could not get diff output", vim.log.levels.ERROR)
|
||||
end
|
||||
|
||||
local files = {}
|
||||
|
||||
-- Split the output into lines
|
||||
local lines = vim.split(diff_ouptut, "\n", { trimempty = true })
|
||||
|
||||
for _, line in ipairs(lines) do
|
||||
local change, file_path = line:match("^(%a)%s(.+)$")
|
||||
|
||||
table.insert(files, {
|
||||
file = file_path,
|
||||
change = change,
|
||||
diff_cmd = string.format("jj diff %s", file_path),
|
||||
})
|
||||
end
|
||||
|
||||
return files
|
||||
end
|
||||
|
||||
--- Displays in the configurated picker the status of the files
|
||||
function M.status()
|
||||
-- Ensure jj is installed
|
||||
if not utils.ensure_jj() then
|
||||
return
|
||||
end
|
||||
|
||||
local files = get_files()
|
||||
if not files or #files == 0 then
|
||||
return utils.notify("`Picker`: No diffs found", vim.log.levels.INFO)
|
||||
end
|
||||
|
||||
if M.config.snacks then
|
||||
require("jj.picker.snacks").status(M.config, files)
|
||||
else
|
||||
return utils.notify("No `picker` enabled", vim.log.levels.INFO)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,40 @@
|
||||
local utils = require("jj.utils")
|
||||
|
||||
--- @class jj.picker.snacks
|
||||
local M = {}
|
||||
|
||||
--- Displays the status files in a snacks picker
|
||||
---@param opts jj.picker.config
|
||||
---@param files jj.picker.file[]
|
||||
function M.status(opts, files)
|
||||
if not opts.snacks then
|
||||
return utils.notify("Snacks picker is `disabled`", vim.log.levels.INFO)
|
||||
end
|
||||
|
||||
local snacks = require("snacks")
|
||||
|
||||
local snacks_opts
|
||||
-- If its true we default to an empty table
|
||||
if opts.snacks == true then
|
||||
snacks_opts = {}
|
||||
else
|
||||
--- Otherwise we get the table from the config
|
||||
---@type table
|
||||
snacks_opts = opts.snacks
|
||||
end
|
||||
|
||||
local merged_opts = vim.tbl_deep_extend("force", snacks_opts, {
|
||||
source = "jj",
|
||||
items = files,
|
||||
title = "JJ Status",
|
||||
preview = function(ctx)
|
||||
if ctx.item.file then
|
||||
snacks.picker.preview.cmd(ctx.item.diff_cmd, ctx, {})
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
snacks.picker.pick(merged_opts)
|
||||
end
|
||||
|
||||
return M
|
||||
+35
-11
@@ -1,25 +1,44 @@
|
||||
--- @class jj.utils
|
||||
local M = {}
|
||||
local M = {
|
||||
executable_cache = {},
|
||||
dependency_cache = {},
|
||||
}
|
||||
|
||||
--- Cache for executable checks to avoid repeated system calls
|
||||
local executable_cache = {}
|
||||
|
||||
--- Check if an executable exists in PATH
|
||||
--- @param name string The name of the executable to check
|
||||
--- @return boolean True if executable exists, false otherwise
|
||||
function M.has_executable(name)
|
||||
if executable_cache[name] ~= nil then
|
||||
return executable_cache[name]
|
||||
if M.executable_cache[name] ~= nil then
|
||||
return M.executable_cache[name]
|
||||
end
|
||||
|
||||
local exists = vim.fn.executable(name) == 1
|
||||
executable_cache[name] = exists
|
||||
M.executable_cache[name] = exists
|
||||
return exists
|
||||
end
|
||||
|
||||
--- Check if the dependency is currently installed
|
||||
--- @param module string The dependency module
|
||||
--- @return boolean
|
||||
function M.has_dependency(module)
|
||||
if M.dependency_cache[module] ~= nil then
|
||||
return true
|
||||
end
|
||||
|
||||
local exists, _ = pcall(require, module)
|
||||
if not exists then
|
||||
M.notify(string.format("Module %s not installed", module), vim.log.levels.ERROR)
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Clear the executable cache (useful for testing or if PATH changes)
|
||||
function M.clear_executable_cache()
|
||||
executable_cache = {}
|
||||
M.executable_cache = {}
|
||||
end
|
||||
|
||||
--- Check if jj executable exists, show error if not
|
||||
@@ -34,15 +53,20 @@ end
|
||||
|
||||
--- Execute a system command and return output with error handling
|
||||
--- @param cmd string The command to execute
|
||||
--- @param error_prefix string Optional error message prefix
|
||||
--- @param error_prefix string|nil Optional error message prefix
|
||||
--- @return string|nil output The command output, or nil if failed
|
||||
--- @return boolean success Whether the command succeeded
|
||||
function M.execute_command(cmd, error_prefix)
|
||||
local output = vim.fn.system(cmd)
|
||||
local success = vim.v.shell_error == 0
|
||||
|
||||
if not success and error_prefix then
|
||||
local error_message = string.format("%s: %s", error_prefix, output)
|
||||
if not success then
|
||||
local error_message
|
||||
if error_prefix then
|
||||
error_message = string.format("%s: %s", error_prefix, output)
|
||||
else
|
||||
error_message = output
|
||||
end
|
||||
M.notify(error_message, vim.log.levels.ERROR)
|
||||
return nil, false
|
||||
end
|
||||
@@ -57,7 +81,7 @@ function M.is_jj_repo()
|
||||
return false
|
||||
end
|
||||
|
||||
local _, success = M.execute_command("jj status", "")
|
||||
local _, success = M.execute_command("jj status")
|
||||
return success
|
||||
end
|
||||
|
||||
@@ -68,7 +92,7 @@ function M.get_jj_root()
|
||||
return nil
|
||||
end
|
||||
|
||||
local output, success = M.execute_command("jj root", "")
|
||||
local output, success = M.execute_command("jj root")
|
||||
if success and output then
|
||||
return vim.trim(output)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user