rsplug 0.2.3

A blazingly fast Neovim plugin manager written in Rust
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# rsplug.nvim

> A blazingly fast Neovim plugin manager written in Rust

[![Crates.io](https://img.shields.io/crates/v/rsplug.svg)](https://crates.io/crates/rsplug)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)

## Overview

**rsplug.nvim** is a modern Neovim plugin manager that takes a different approach: it's implemented as an external Rust binary to build a Vim pack package rather than a Vimscript/Lua plugin. Instead of installing plugins directly, rsplug **synchronizes Vim pack packages from TOML configuration files**, enabling fast, parallel Git operations, deterministic builds. In the future, rsplug will provide seamless integration with Nix-based workflows.

### Why rsplug.nvim?

- **External binary architecture**: Say goodbye to launching Neovim just to manage plugins
- **Blazingly fast**: Parallel shallow Git clones with depth=1 for rapid installation and updates
- **Deterministic and reproducible**: Lock file support ensures consistent plugin versions across machines
- **Lazy-loading first**: Sophisticated lazy-loading system with autocmds, filetypes, commands, and keymaps
- **Minimal runtime overhead**: Merges compatible plugins to reduce Neovim's `runtimepath` size
- **TOML configuration**: Clean, readable configuration format with version wildcards and dependency support

## Quick Start

### Installation

First, you need to add one liner to your Neovim configuration.

```lua
dofile(vim.fn.expand '~/.cache/rsplug/init.lua')
```

Then, install the `rsplug` binary:

#### From Source

Requires Rust 1.89+ and Git:

```bash
git clone https://github.com/gw31415/rsplug.nvim.git
cd rsplug.nvim
cargo build --release
# Binary will be at target/release/rsplug
```

#### Using Nix Flakes

```bash
nix build github:gw31415/rsplug.nvim
# Binary will be in result/bin/rsplug
```

Or add to your flake:

```nix
{
  inputs.rsplug.url = "github:gw31415/rsplug.nvim";
  # ...
}
```

### Basic Usage

1. **Create a configuration file** (e.g., `~/.config/nvim/rsplug.toml`):

```toml
[[plugins]]
repo = "nvim-lua/plenary.nvim"

[[plugins]]
repo = "neovim/nvim-lspconfig"
on_event = "BufReadPre"
lua_after = """
require('lspconfig').rust_analyzer.setup{}
"""

[[plugins]]
repo = "hrsh7th/nvim-cmp"
on_event = ["InsertEnter", "CmdlineEnter"]
lua_after = "require('cmp').setup{}"
```

2. **Synchronize plugins from the TOML file**:

>[!WARNING]
> rsplug synchronizes pack packages based on the configuration file(s) you provide. If you change which configuration file you specify as an argument, plugins defined in other files will no longer be loaded. Always use the same configuration file pattern or use the `RSPLUG_CONFIG_FILES` environment variable to ensure consistency.

```bash
rsplug -iu ~/.config/nvim/rsplug.toml
```

>[!TIP]
> **Use environment variable for convenience!**
> Instead of specifying the config file every time, set `RSPLUG_CONFIG_FILES` in your shell profile:

```bash
export RSPLUG_CONFIG_FILES="~/.config/nvim/rsplug.toml"
# Or use glob patterns:
export RSPLUG_CONFIG_FILES="~/.config/nvim/plugins/*.toml"
# Run rsplug without positional arguments:
rsplug -iu
```

#### CLI Options

- To install new plugins: `-i` or `--install`
  ```bash
  rsplug --install ~/.config/nvim/rsplug.toml
  rsplug -i ~/.config/nvim/rsplug.toml
  ```
- To update existing plugins: `-u` or `--update`
  ```bash
  rsplug --update ~/.config/nvim/rsplug.toml
  rsplug -u ~/.config/nvim/rsplug.toml
  ```
- To sync hooks or scripts only: (no git operations)
  ```bash
  rsplug ~/.config/nvim/rsplug.toml
  ```
- To use the lock file for exact versions: `--locked`
  ```bash
  rsplug --locked ~/.config/nvim/rsplug.toml
  ```

## Key Features

### Lazy Loading

rsplug.nvim provides comprehensive lazy-loading triggers:

```toml
[[plugins]]
repo = "nvim-telescope/telescope.nvim"
on_cmd = "Telescope"                    # Load on command
on_map = { n = "<leader>ff" }           # Load on keymap
on_event = "VimEnter"                   # Load on autocmd event
on_ft = ["lua", "vim"]                  # Load on filetype
```

Lua modules are automatically detected and loaded on `require`:

```toml
[[plugins]]
repo = "nvim-lua/plenary.nvim"
# Will auto-load when you call require('plenary')
```

### Lifecycle Hooks

Execute Lua code / subprocess before/after plugin load or install:

```toml
[[plugins]]
repo = "folke/which-key.nvim"
lua_before = "vim.g.which_key_timeout = 300"  # Before plugin loads
lua_after = "require('which-key').setup{}"    # After plugin loads

[[plugins]]
repo = "yetone/avante.nvim"
build = ["make"]                              # Run after install/update
lua_build = "vim.fn.system({'make', 'docs'})" # Run Lua after install/update
```

### Dependencies

Declare plugin dependencies that load together:

```toml
[[plugins]]
repo = "nvim-telescope/telescope.nvim"
depends = ["plenary.nvim"]             # Load plenary.nvim simultaneously
on_cmd = "Telescope"
```

### Repository Sources

#### GitHub (shorthand)

```toml
[[plugins]]
repo = "owner/repo"               # Default branch
repo = "owner/repo@main"          # Specific branch
repo = "owner/repo@v1.2.0"        # Exact tag
repo = "owner/repo@v*"            # Latest matching tag (wildcard)
```

#### General Git URL

Any repository accessible via `git` can be specified by providing a full URL containing `://`:

```toml
[[plugins]]
repo = "https://gitlab.com/owner/plugin"          # Default branch
repo = "https://gitlab.com/owner/plugin@main"     # Branch / tag / commit
repo = "https://codeberg.org/owner/plugin@v1.0"  # Any hosting
```

The `@rev` suffix works the same as for GitHub shorthand — it accepts branch names, tags, and wildcard patterns.

The cache directory is derived from the URL's host and path (scheme, authentication, port, and `.git` suffix are stripped), e.g.:

```
https://gitlab.com/owner/plugin  →  ~/.cache/rsplug/repos/gitlab.com/owner/plugin
```

### Version Control

Lock to specific versions or use wildcards:

```toml
[[plugins]]
repo = "j-hui/fidget.nvim@v1.2.0"      # Exact version

[[plugins]]
repo = "j-hui/fidget.nvim@v*"          # Latest v* tag

[[plugins]]
repo = "j-hui/fidget.nvim@main"        # Specific branch
```

### Lock File for Reproducibility

Every time you run `rsplug` a lock file is generated at `~/.cache/rsplug/rsplug.lock.json` by default.
This file records the exact commit hashes of all installed plugins.

To sync plugins the exact versions from the lock file, use the `--locked` flag:

```bash
rsplug --locked
```

### Multiple Configuration Files

Combine multiple configuration files using glob patterns:

```bash
rsplug '~/.config/nvim/plugins/*.toml'
# Or with multiple patterns:
rsplug '~/.config/nvim/base.toml:~/.config/nvim/plugins/*.toml'
```

Or set via environment variable:

```bash
export RSPLUG_CONFIG_FILES="~/.config/nvim/plugins/*.toml"
rsplug
```

## Configuration Reference

### Plugin Fields

| Field | Type | Description |
|-------|------|-------------|
| `repo` | String | `owner/repo[@rev]` (GitHub) or any Git URL (`https://…[@rev]`) |
| `start` | Boolean | If `true`, always load at startup (default: `false`) |
| `on_event` | String/Array | Autocmd event(s) to trigger lazy-load |
| `on_cmd` | String/Array | User command(s) to trigger lazy-load |
| `on_ft` | String/Array | Filetype(s) to trigger lazy-load |
| `on_map` | String/Table | Keymap(s) to trigger lazy-load |
| `depends` | Array | Plugin dependencies loaded simultaneously |
| `lua_start` | String | Lua code to run at Neovim startup |
| `lua_before` | String | Lua code to run before plugin loads |
| `lua_after` | String | Lua code to run after plugin loads |
| `build` | Array | Subprocess to run after install/update |
| `lua_build` | String | Lua script to run after install/update |
| `name` | String | Custom plugin name (default: repo name) |
| `sym` | Boolean | Use symlink instead of file copy |
| `ignore` | String | Gitignore-style patterns for files to exclude |

### Key Mapping Syntax

```toml
# Simple (`nxo` modes)
on_map = "<leader>f"

# Single mode
on_map = { n = "<leader>f" }

# Multiple modes
on_map = { nx = "<leader>f" }

# Multiple keys
on_map = { n = ["<leader>f", "<leader>g"] }
```

## Command-Line Interface

```
Vim plugin manager written in Rust

Usage: rsplug [OPTIONS] <CONFIG_FILES>...

Arguments:
  <CONFIG_FILES>...  Glob-patterns of the config files. Split by ':' to specify multiple patterns [env: RSPLUG_CONFIG_FILES]

Options:
  -i, --install              Install plugins which are not installed yet
  -u, --update               Access remote and update repositories
      --locked               Fix the repo version with rev in the lockfile
      --lockfile <LOCKFILE>  Specify the lockfile path
  -h, --help                 Print help
```

## How It Works

rsplug.nvim operates in two phases:

### 1. Build Phase (CLI)

rsplug **synchronizes the pack packages** from your TOML configuration:

1. Parses TOML configuration file(s)
2. Resolves plugin dependencies using DAG (Directed Acyclic Graph)
3. Clones/updates Git repositories to `~/.cache/rsplug/repos/`
  - Clone new plugins if it provided the option `--install`
  - Update repos if it provided the option `--update`
  - Synchronizes to specific commit from lock file if `--locked` is provided
4. Writes lock file with exact commit hashes
5. Runs build commands / `lua_build` scripts if specified
6. Generates `~/.cache/rsplug/init.lua`, writes the generated control plugin under `~/.cache/rsplug/pack/_gen/opt/`, and stores that generation's `manifest.json` inside the control plugin

**Important:** The pack directory reflects exactly what's in your current configuration file(s). If you change which configuration file you pass as an argument, the pack directory will be re-synchronized to match only those plugins.

### 2. Runtime Phase (Neovim)

- `~/.cache/rsplug/init.lua` prepends the generated packpath and explicitly `packadd`s the current generated control plugin
- `lua_start` scripts run at startup before controlled startup plugin loads
- `start = true` plugins are placed under `pack/_gen/opt/` and loaded during startup via rsplug's controlled `:packadd!` path
- Registers lazy-loading triggers (autocmds, commands, keymaps)
- On startup or lazy trigger, loads plugin via `:packadd` with before/after hooks

### v0.2.0 Breaking Change

rsplug v0.2.0 changes the Neovim bootstrap line and the generated package layout
so `start = true` plugins can run `lua_before` and `lua_after` deterministically.

Update your `init.lua` from:

```lua
vim.opt.packpath:prepend("~/.cache/rsplug")
```

to:

```lua
dofile(vim.fn.expand("~/.cache/rsplug/init.lua"))
```

Managed plugins, including `start = true` plugins, are now placed under
`pack/_gen/opt/` and loaded by rsplug's generated runtime.

To preserve deterministic `lua_before` / `lua_after` ordering, managed
`start = true` plugins are not merged with other managed startup plugins.

## Advanced Topics

### Plugin Merging

rsplug automatically merges plugins with the same lazy-loading trigger when their files don't conflict. This reduces `runtimepath` size and improves startup performance.

### Build Caching

Build commands are cached using a hash of:
- Git commit SHA
- Working directory changes
- Build command itself (`build` and `lua_build`)

Rebuilds only occur when necessary, speeding up subsequent runs.

### Startup Generation Manifests

rsplug writes the generated control plugin under `pack/_gen/opt/<hash>/` and
loads the current control plugin from `init.lua` with `:packadd <hash>`. Older
retained control plugins are not native startup packages, so they are not loaded
by `packloadall` or startup package scanning.

Each control plugin contains a `manifest.json` listing the shared `_gen` entries
that generation references. The runtime exposes this data as `_rsplug.manifest`.
rsplug keeps the latest few control manifests and only cleans `_gen` plugin
directories when no retained manifest references them. This avoids deleting Lua
hook/runtime modules still referenced by a Neovim instance that started before a
later rsplug update.

## Who Is This For?

rsplug.nvim is ideal for:

- **Advanced Neovim users** who want precise control over plugin loading
- **Performance enthusiasts** seeking fast startup times
- **Rust developers** who prefer Rust tooling
- **Configuration hackers** who enjoy TOML over Lua for data

## Documentation

- **Quick reference**: This README
- **Detailed documentation**: `:help rsplug` (see `doc/rsplug.txt`)
- **Example configuration**: See `example.toml` in this repository
- **Repository**: https://github.com/gw31415/rsplug.nvim

## Known Limitations

- Plugin merging may occur between sibling dependencies without clear ordering control

## Contributing

Issues and pull requests are welcome! See the [issue tracker](https://github.com/gw31415/rsplug.nvim/issues) for current tasks and known issues.

## License

Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details.

---

**Note**: This is an early-stage project under active development. APIs and behaviors may change.