Neovim
The Neovim client lives in clients/neovim/ in the repo. It uses
Neovim 0.11's built-in LSP API (vim.lsp.enable) with automatic
workspace detection (root_markers = { '.jj' }).
Requirements
- Neovim 0.11+
badjujuserver binary on yourPATH(see Getting Started)
Installation
Pick the recipe that matches your plugin manager. Replace
/absolute/path/to/bad-juju with the path to your local checkout.
lazy.nvim (recommended)
{
dir = '/absolute/path/to/bad-juju/clients/neovim',
name = 'bad-juju',
ft = 'jujutsu',
opts = {},
}
packer.nvim
use {
'/absolute/path/to/bad-juju/clients/neovim',
config = function() require('badjuju').setup({}) end,
}
vim-plug
Plug 'jennings/bad-juju', { 'rtp': 'clients/neovim' }
require('badjuju').setup({})
pathogen
ln -s /absolute/path/to/bad-juju/clients/neovim \
~/.vim/bundle/bad-juju
Vundle
Plugin 'jennings/bad-juju'
set rtp+=~/.vim/bundle/bad-juju/clients/neovim
Neovim built-in packages
ln -s /absolute/path/to/bad-juju/clients/neovim \
~/.local/share/nvim/site/pack/badjuju/start/bad-juju
Manual / no plugin manager
vim.opt.rtp:prepend('/absolute/path/to/bad-juju/clients/neovim')
require('badjuju').setup({})
Configuration
setup() is optional. Only call it if you want to override defaults:
require('badjuju').setup({
-- Path to the jj binary; forwarded to the server.
binaryPath = nil,
-- Default revset for :JJLog when called with no argument.
defaultLogRevset = nil,
-- Hotkey profile: "magit" (default), "vim", or "none".
keymapProfile = nil,
})
Commands
| Command | Description |
|---|---|
:JJStatus | Open .jj/badjuju/status.jujutsu |
:JJLog [revset] | Open the log; defaults to @ |
:JJLogFile [revset] | Open the per-file log for the current buffer's file; defaults to ..@ |
:JJDescribe [revision] | Edit a commit message (defaults to @) |
:JJDiff [revision] | Open a diff (defaults to @) |
:JJNew | Create a new change |
:JJRefresh | Refresh the badjuju buffer at the cursor |
:JJSquash [file] [revision] | Squash a file into its parent |
:JJUnsquash [file] [revision] | Unsquash a file from parent into child |
:JJUndo | Run jj undo and refresh |
:JJAbandon [revision] | Abandon a revision (defaults to @) |
Commands auto-start the LSP for the current workspace if it isn't already running.
Keymaps
Two built-in profiles, plus "none" to disable defaults. Set
keymapProfile in setup() to switch.
magit profile (default)
status.jujutsu
| Key | Action |
|---|---|
R | Refresh |
n | New change |
L | Open log |
l f | Open the per-file log for the file at cursor |
f | Git fetch |
p | Git push |
P | Git push --force-with-lease |
e | Edit commit at cursor (move @) |
b c/m/d/t/f | Bookmark: create / move / delete / track / forget |
r s | Mark rebase source (jj rebase -s) |
r r | Mark rebase source (jj rebase -r) |
r b | Mark rebase source (jj rebase -b) |
r o | Rebase onto commit at cursor (-d) |
r A | Insert after commit at cursor (--insert-after) |
r B | Insert before commit at cursor (--insert-before) |
d | Diff change at cursor (updates on amend) |
D | Diff commit at cursor (pinned, immutable) |
c w | Commit transient → reword (describe) commit at cursor |
c n | Commit transient → new child commit |
s | Select squash source or destination (two-step) |
S | Squash file at cursor into parent |
u | Unsquash file at cursor |
a | Abandon commit at cursor |
U | Undo |
x | Cancel pending operation (squash or rebase) |
= | Diff (alias for d) |
q | Close window |
? | Show key binding help |
log.jujutsu
| Key | Action |
|---|---|
R | Refresh |
e | Edit commit at cursor (move @) |
b c/m/d/t/f | Bookmark: create / move / delete / track / forget |
r s | Mark rebase source (jj rebase -s) |
r r | Mark rebase source (jj rebase -r) |
r b | Mark rebase source (jj rebase -b) |
r o | Rebase onto commit at cursor (-d) |
r A | Insert after commit at cursor (--insert-after) |
r B | Insert before commit at cursor (--insert-before) |
d | Diff change at cursor (updates on amend) |
D | Diff commit at cursor (pinned, immutable) |
c w | Commit transient → reword (describe) commit at cursor |
c n | Commit transient → new child commit |
s | Select squash source or destination (two-step) |
a | Abandon commit at cursor |
U | Undo |
x | Cancel pending operation (squash or rebase) |
= | Diff (alias for d) |
<CR> | Apply revset shortcut on cursor line |
q | Close window |
? | Show key binding help |
diff.jujutsu
| Key | Action |
|---|---|
R | Refresh (re-runs jj diff) |
q | Close window |
? | Show key binding help |
describe.jujutsu
| Key (mode) | Action |
|---|---|
<C-c><C-c> (normal) | Finalize commit (save and close) |
<C-c><C-k> (normal) | Abort (close without saving) |
<C-c> (insert) | Finalize commit (save and close) |
? (normal) | Show key binding help |
vim profile
Two-letter verb chords inspired by Fugitive. Most actions use doubled
letters (nn, dd, etc.) to keep single keys free for text
navigation:
| Key | Action |
|---|---|
nn | New change |
ll | Open log |
ee | Edit commit at cursor (move @) |
dd | Describe commit at cursor |
d | Diff change at cursor (updates on amend) |
D | Diff commit at cursor (pinned, immutable) |
= | Diff (alias for d) |
bb c/m/d/t/f | Bookmark: create / move / delete / track / forget |
rr s | Mark rebase source (jj rebase -s) |
rr r | Mark rebase source (jj rebase -r) |
rr b | Mark rebase source (jj rebase -b) |
rr o | Rebase onto commit at cursor (-d) |
rr A | Insert after commit at cursor (--insert-after) |
rr B | Insert before commit at cursor (--insert-before) |
ss | Select squash source or destination (two-step) |
SS | Squash file at cursor into parent |
uu | Unsquash file at cursor |
UU | Undo |
aa | Abandon commit at cursor |
x | Cancel pending operation (squash or rebase) |
ff | Git fetch |
pp | Git push |
PP | Git push --force-with-lease |
Enable with:
require('badjuju').setup({ keymapProfile = 'vim' })
none profile
Disables all built-in keymaps. Define your own using the :JJ*
commands above.
Auto-refresh
Open status, log, and diff buffers auto-refresh whenever a jj
operation runs — even one you ran in a terminal. No manual reload
required.
Syntax highlighting
Highlights come from the LSP via semantic tokens. Your colorscheme's standard token groups (comments, keywords, strings, types, enum members, numbers, operators) are picked up automatically; no extra configuration needed.