diff --git a/README.md b/README.md index 1fb9cca..e03b5e8 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ ⚠️ **WORK IN PROGRESS** ⚠️ +> **Note:** This project is pre-v1. Breaking changes may occur in the configuration, API, and features until v1.0.0 is released. + A Neovim plugin for [Jujutsu (jj)](https://github.com/jj-vcs/jj) version control system. ## About @@ -19,12 +21,17 @@ This plugin aims to be something like vim-fugitive but for driving the jj-vcs CL - `describe` / `desc` - Set change descriptions with a Git-style commit message editor - `status` / `st` - Show repository status - `log` - Display log history with configurable options - - `diff` - Show changes - - `new` - Create a new change + - `diff` - Show changes with optional filtering by current file + - `new` - Create a new change with optional parent selection - `edit` - Edit a change - `squash` - Squash the current diff to it's parent + - `rebase` - Rebase changes to a destination + - `bookmark create/delete` - Create and delete bookmarks - `undo` - Undo the last operation - `redo` - Redo the last undone operation +- Diff commands + - `:Jdiff [revision]` - Vertical split diff against a jj revision + - `:Jhdiff [revision]` - Horizontal split diff - Picker for for [Snacks.nvim](https://github.com/folke/snacks.nvim) - `jj status` Displays the current changes diffs - `jj file_history` Displays a buffer's history changes and allows to edit it's change (including immutable changes) @@ -35,7 +42,7 @@ Here are some cool features you can do with jj.nvim ### Diff any change -You can diff any change in your log history by simply pressing `d` on it's line, yeah just like that! +You can diff any change in your log history by simply pressing `d` on its line, yeah just like that! ![Diff-from-log](https://github.com/NicolasGB/jj.nvim/raw/main/assets/diff-log.gif) ### Edit changes @@ -58,8 +65,8 @@ You can create new changes directly from the log buffer with multiple options: You can undo/redo changes directly from the log buffer: -- `u` - Undo the last operation -- `r` - Redo the last undone operation +- `` - Undo the last operation +- `` - Redo the last undone operation ### Open a changed file @@ -68,7 +75,7 @@ Just press enter to open the a file from the `status` output in your current win ### Restore a changed file -Press `X` on a file from the `status` output and that's it, it's restored. +Press `` on a file from the `status` output and that's it, it's restored. ![Restore-status](https://github.com/NicolasGB/jj.nvim/raw/main/assets/x-status.gif) @@ -98,7 +105,18 @@ The plugin provides a `:J` command that accepts jj subcommands: :J ``` -## Setup config +### Diff Commands + +The plugin also provides `:Jdiff`, `:Jvdiff`, and `:Jhdiff` commands for diffing against specific revisions: + +```sh +:Jdiff " Vertical diff against @- (parent) +:Jdiff @-2 " Vertical diff against specific revision +:Jvdiff main " Vertical diff against main bookmark +:Jhdiff trunk() " Horizontal diff against trunk +``` + +## Default Config ```lua { @@ -106,22 +124,56 @@ The plugin provides a `:J` command that accepts jj subcommands: picker = { -- Here you can pass the options as you would for snacks. -- It will be used when using the picker - snacks = { - - } + snacks = {} }, - -- Choose the editor mode for describe command - -- "buffer" - Opens a Git-style commit message buffer with syntax highlighting (default) - -- "input" - Uses a simple vim.ui.input prompt - describe_editor = "buffer", - -- Customize syntax highlighting colors for the describe buffer highlights = { added = { fg = "#3fb950", ctermfg = "Green" }, -- Added files modified = { fg = "#56d4dd", ctermfg = "Cyan" }, -- Modified files deleted = { fg = "#f85149", ctermfg = "Red" }, -- Deleted files renamed = { fg = "#d29922", ctermfg = "Yellow" }, -- Renamed files + }, + + -- Configure cmd module (describe editor, keymaps) + cmd = { + -- Configure describe editor + describe = { + editor = { + -- Choose the editor mode for describe command + -- "buffer" - Opens a Git-style commit message buffer with syntax highlighting (default) + -- "input" - Uses a simple vim.ui.input prompt + type = "buffer", + -- Customize keymaps for the describe editor buffer + keymaps = { + close = { "", "", "q" }, -- Keys to close editor without saving + } + } + }, + + -- Configure keymaps for command buffers + keymaps = { + -- Log buffer keymaps (set to nil to disable) + log = { + checkout = "", -- Edit revision under cursor + checkout_immutable = "", -- Edit revision (ignore immutability) + describe = "d", -- Describe revision under cursor + diff = "", -- Diff revision under cursor + edit = "e", -- Edit revision under cursor + new = "n", -- Create new change branching off + new_after = "", -- Create new change after revision + new_after_immutable = "", -- Create new change after (ignore immutability) + undo = "", -- Undo last operation + redo = "", -- Redo last undone operation + }, + -- Status buffer keymaps (set to nil to disable) + status = { + open_file = "", -- Open file under cursor + restore_file = "", -- Restore file under cursor + }, + -- Close keymaps (shared across all buffers) + close = { "q", "" }, + }, } } @@ -129,7 +181,7 @@ The plugin provides a `:J` command that accepts jj subcommands: ### Describe Editor Modes -The `describe_editor` option lets you choose how you want to write commit descriptions: +The `describe.editor.type` option lets you choose how you want to write commit descriptions: - **`"buffer"`** (default) - Opens a full buffer editor similar to Git's commit message editor - Shows file changes with syntax highlighting @@ -144,7 +196,26 @@ Example: ```lua require("jj").setup({ - describe_editor = "input", -- Use simple input mode + describe = { + editor = { + type = "input", -- Use simple input mode + } + } +}) +``` + +You can also customize the keymaps for the describe editor buffer: + +```lua +require("jj").setup({ + describe = { + editor = { + type = "buffer", + keymaps = { + close = { "q", "", "" }, -- Customize close keybindings + } + } + } }) ``` @@ -169,25 +240,93 @@ require("jj").setup({ }) ``` +## Lua API Usage + +Beyond the `:J` command, you can call functions directly from Lua for more control. The example config below shows how to use them with custom keymaps. + +### Log Command Options + +The `log` function accepts an options table: + +```lua +jj.log({ + summary = false, -- Show summary of changes (default: false) + reversed = false, -- Reverse the log order (default: false) + no_graph = false, -- Hide the graph (default: false) + limit = 20, -- Limit number of entries (default: 20) + revisions = "'all()'" -- Revision specifier (default: all reachable) +}) + +-- Examples: +jj.log({ limit = 50 }) -- Show 50 entries +jj.log({ revisions = "'main::@'" }) -- Show commits between main and current +jj.log({ summary = true, limit = 100 }) -- Show summary with high limit +jj.log({ raw = "-r 'main::@' --summary --no-graph" }) -- Pass raw flags directly +``` + +### New Command Options + +The `new` function accepts an options table: + +```lua +jj.new({ + show_log = false, -- Display log after creating new change (default: false) + with_input = false, -- Prompt for parent revision (default: false) + args = "" -- Additional arguments to pass to jj new +}) + +-- Examples: +jj.new({ show_log = true }) -- Create new and show log +jj.new({ show_log = true, with_input = true }) -- Prompt for parent +jj.new({ args = "--before @" }) -- Pass custom args +``` + +### Diff Split Views + +Use the `diff` module for opening splits: + +```lua +jj.diff.vsplit() -- Vertical split diff against parent +jj.diff.vsplit({ rev = "main" }) -- Vertical split against specific revision +jj.diff.hsplit() -- Horizontal split diff +jj.diff.hsplit({ rev = "@-2" }) -- Horizontal split against @-2 +``` + ## Example config ```lua { - "nicolasgb/jj.nvim", - dependencies = { - "folke/snacks.nvim", -- Optional only if you use picker's - }, config = function() - local jj = require("jj") jj.setup({ - + cmd = { + describe = { + editor = { + type = "buffer", + keymaps = { + close = { "q", "", "" }, + } + } + }, + keymaps = { + log = { + checkout = "", + describe = "d", + diff = "", + }, + status = { + open_file = "", + restore_file = "", + }, + close = { "q", "" }, + }, + }, highlights = { -- Customize colors if desired modified = { fg = "#89ddff" }, @@ -196,40 +335,28 @@ require("jj").setup({ + -- Core commands vim.keymap.set("n", "jd", jj.describe, { desc = "JJ describe" }) - vim.keymap.set("n", "jl", jj.log, { desc = "JJ log" }) - vim.keymap.set("n", "je", jj.edit, { desc = "JJ edit" }) - vim.keymap.set("n", "jn", jj.new, { desc = "JJ new" }) - vim.keymap.set("n", "js", jj.status, { desc = "JJ status" }) - vim.keymap.set("n", "sj", jj.squash, { desc = "JJ squash" }) - vim.keymap.set("n", "ju", jj.undo, { desc = "JJ undo" }) - vim.keymap.set("n", "jy", jj.redo, { desc = "JJ redo" }) + vim.keymap.set("n", "jr", jj.rebase, { desc = "JJ rebase" }) + vim.keymap.set("n", "jb", jj.bookmark_create, { desc = "JJ bookmark create" }) + vim.keymap.set("n", "jB", jj.bookmark_delete, { desc = "JJ bookmark delete" }) - - - -- Using the new `diff` table for clarity - + -- Diff commands vim.keymap.set("n", "dj", jj.diff.vsplit, { desc = "JJ diff vertical" }) - - + vim.keymap.set("n", "dJ", jj.diff.hsplit, { desc = "JJ diff horizontal" }) -- Pickers - vim.keymap.set("n", "gj", jj.picker.status, { desc = "JJ Picker status" }) - vim.keymap.set("n", "gl", jj.picker.file_history, { desc = "JJ Picker file history" }) - - -- Some functions like `log` can take parameters - vim.keymap.set("n", "jL", function() jj.log { revisions = "'all()'", -- equivalent to jj log -r :: @@ -237,9 +364,7 @@ require("jj").setup({ end, { desc = "JJ log all" }) - -- This is an alias i use for moving bookmarks its so good - vim.keymap.set("n", "jt", function() jj.j "tug" jj.log {} diff --git a/lua/jj/cmd.lua b/lua/jj/cmd.lua index f9e705d..8de03fa 100644 --- a/lua/jj/cmd.lua +++ b/lua/jj/cmd.lua @@ -9,24 +9,52 @@ local editor = require("jj.ui.editor") local diff = require("jj.diff") -- Config for cmd module +--- @class jj.cmd.describe.editor.keymaps +--- @field close? string|string[] Keymaps to close the editor buffer without saving + +--- @class jj.cmd.describe.editor +--- @field type? "buffer"|"input" Editor mode for describe command: "buffer" (Git-style editor) or "input" (simple input prompt) +--- @field keymaps? jj.cmd.describe.editor.keymaps Keymaps for the describe editor only when on "buffer" mode. + +--- @class jj.cmd.describe +--- @field editor? jj.cmd.describe.editor Options for the describe message editor + +--- @class jj.cmd.log.keymaps +--- @field checkout? string|string[] Keymaps for the log command buffer, setting a keymap to nil will disable it +--- @field checkout_immutable? string|string[] +--- @field describe? string|string[] +--- @field diff? string|string[] +--- @field edit? string|string[] +--- @field new? string|string[] +--- @field new_after? string|string[] +--- @field new_after_immutable? string|string[] +--- @field undo? string|string[] +--- @field redo? 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 +--- @field restore_file? string|string[] + +--- @class jj.cmd.keymaps +--- @field log? jj.cmd.log.keymaps Keymaps for the log command buffer +--- @field status? jj.cmd.status.keymaps Keymaps for the status command buffer +--- @field close? string|string[] Keymaps for the close keybind + --- @class jj.cmd.opts +--- @field describe? jj.cmd.describe +--- @field keymaps? jj.cmd.keymaps Keymaps for the buffers containing the output of the commands + +--- @type jj.cmd.opts M.config = { - --- @class jj.cmd.describe describe = { - --- @class jj.cmd.describe.editor Options for the describe message editor editor = { - --- @type "buffer"|"input" Editor mode for describe command: "buffer" (Git-style editor) or "input" (simple input prompt) type = "buffer", - --- @class jj.cmd.describe.editor.keymaps Keymaps for the describe editor buffer keymaps = { - --- @type string|string[] Keymaps to close the editor buffer without saving close = { "", "", "q" }, }, }, }, - --- @class jj.cmd.keymaps Keymaps for the buffers containing the ouptut of the commands keymaps = { - --- @class jj.cmd.log.keymaps: { [string]: string|string[] } Keymaps for the log command buffer, setting a keymap to nil will disable it log = { checkout = "", checkout_immutable = "", @@ -39,12 +67,10 @@ M.config = { undo = "", redo = "", }, - --- @class jj.cmd.status.keymaps: { [string]: string|string[] } Keymaps for the status command buffer, setting a keymap to nil will disable it status = { open_file = "", restore_file = "", }, - --- @type string|string[] Keymaps for the close keybind close = { "q", "" }, }, } @@ -835,6 +861,15 @@ function M.j(args) log = function() M.log({ raw_flags = remaining_args_str ~= "" and remaining_args_str or nil }) end, + diff = function() + M.diff({ current = false }) + end, + status = function() + M.status() + end, + st = function() + M.status() + end, } if handlers[subcommand] then diff --git a/lua/jj/init.lua b/lua/jj/init.lua index be9183c..f7f88d8 100644 --- a/lua/jj/init.lua +++ b/lua/jj/init.lua @@ -2,13 +2,12 @@ local M = {} local cmd = require("jj.cmd") local picker = require("jj.picker") local editor = require("jj.ui.editor") -local diff = require("jj.diff") -local utils = require("jj.utils") --- Jujutsu plugin configuration --- @class jj.Config --- @field cmd? jj.cmd.opts Options for command module --- @field picker? jj.picker.config Options for picker module +--- @field highlights? jj.ui.editor.highlights Highlight configuration for describe buffer M.config = { -- Default configuration --- @type jj.picker.config @@ -16,12 +15,7 @@ M.config = { snacks = {}, }, --- @type jj.ui.editor.highlights Highlight configuration for describe buffer - highlights = { - added = { fg = "#3fb950", ctermfg = "Green" }, - modified = { fg = "#56d4dd", ctermfg = "Cyan" }, - deleted = { fg = "#f85149", ctermfg = "Red" }, - renamed = { fg = "#d29922", ctermfg = "Yellow" }, - }, + highlights = {}, } --- Setup the plugin @@ -32,34 +26,9 @@ function M.setup(opts) -- Setup for sub-modules picker.setup(opts and opts.picker or {}) editor.setup({ highlights = M.config.highlights }) - cmd.setup(opts.cmd) - utils.setup(opts) -- Keep for future-proofing, even if it's a no-op now + cmd.setup(opts and opts.cmd or {}) cmd.register_command() - - -- Expose public API functions on the top-level module - M.status = cmd.status - M.describe = cmd.describe - M.log = cmd.log - M.new = cmd.new - M.edit = cmd.edit - M.squash = cmd.squash - M.rebase = cmd.rebase - M.undo = cmd.undo - M.redo = cmd.redo - M.bookmark_create = cmd.bookmark_create - M.bookmark_delete = cmd.bookmark_delete - M.j = cmd.j - - M.picker = { - status = picker.status, - file_history = picker.file_history, - } - - M.diff = { - vsplit = diff.open_vdiff, - hsplit = diff.open_hdiff, - } end return M diff --git a/lua/jj/test.lua b/lua/jj/test.lua deleted file mode 100644 index e69de29..0000000 diff --git a/lua/jj/ui/editor.lua b/lua/jj/ui/editor.lua index 278fc77..92160dc 100644 --- a/lua/jj/ui/editor.lua +++ b/lua/jj/ui/editor.lua @@ -4,10 +4,10 @@ local M = {} local buffer = require("jj.core.buffer") --- @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 +---@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 M.highlights = { added = { fg = "#3fb950", ctermfg = "Green" },