mirror of
https://github.com/harivansh-afk/forge.nvim.git
synced 2026-04-15 05:02:09 +00:00
ci: typgin,e tc
This commit is contained in:
parent
c9d271c7a6
commit
f96eaf5938
3 changed files with 1147 additions and 185 deletions
342
README.md
342
README.md
|
|
@ -7,21 +7,19 @@ your editor.
|
|||
|
||||
## Features
|
||||
|
||||
- Forge detection from git remote (GitHub via `gh`, GitLab via `glab`,
|
||||
- Automatic forge detection from git remote (GitHub via `gh`, GitLab via `glab`,
|
||||
Codeberg/Gitea/Forgejo via `tea`)
|
||||
- PR lifecycle: list, create, checkout, review, merge, approve, close/reopen,
|
||||
- PR lifecycle: list, create (compose buffer with template discovery, diff stat,
|
||||
reviewers), checkout, worktree, review diff, merge, approve, close/reopen,
|
||||
draft toggle
|
||||
- Issue management: list, browse, close/reopen
|
||||
- CI/CD: view runs, stream logs, filter by status
|
||||
- PR compose buffer with diff stat, template discovery, and syntax highlighting
|
||||
- Code review via [diffs.nvim](https://github.com/barrettruth/diffs.nvim)
|
||||
unified/split diff with quickfix navigation
|
||||
- Commit browsing with checkout, diff review, and URL yanking
|
||||
- Branch browsing with diff review and remote links
|
||||
- Worktree creation from PRs
|
||||
- File/line permalink generation (commit and branch URLs)
|
||||
- [fzf-lua](https://github.com/ibhagwan/fzf-lua) pickers with contextual
|
||||
keybinds throughout
|
||||
- Issue management: list, browse, close/reopen, state filtering
|
||||
- CI/CD: view runs per-branch or repo-wide, stream logs, filter by status
|
||||
- Code review via [diffs.nvim](https://github.com/barrettruth/diffs.nvim) with
|
||||
unified/split toggle and quickfix navigation
|
||||
- Commit and branch browsing with checkout, diff, and URL generation
|
||||
- File/line permalink generation and yanking (commit and branch URLs)
|
||||
- [fzf-lua](https://github.com/ibhagwan/fzf-lua) pickers with contextual keybinds
|
||||
- Pluggable source registration for custom or self-hosted forges
|
||||
|
||||
## Dependencies
|
||||
|
||||
|
|
@ -32,35 +30,327 @@ your editor.
|
|||
- [`glab`](https://gitlab.com/gitlab-org/cli) for GitLab
|
||||
- [`tea`](https://gitea.com/gitea/tea) for Codeberg/Gitea/Forgejo
|
||||
- [vim-fugitive](https://github.com/tpope/vim-fugitive) (optional, for fugitive
|
||||
keymaps)
|
||||
keymaps and split diff)
|
||||
- [diffs.nvim](https://github.com/barrettruth/diffs.nvim) (optional, for review
|
||||
mode)
|
||||
|
||||
## Installation
|
||||
|
||||
Install with your package manager of choice or via
|
||||
[luarocks](https://luarocks.org/modules/barrettruth/forge.nvim):
|
||||
### [lazy.nvim](https://github.com/folke/lazy.nvim)
|
||||
|
||||
```lua
|
||||
{
|
||||
'barrettruth/forge.nvim',
|
||||
dependencies = { 'ibhagwan/fzf-lua' },
|
||||
}
|
||||
```
|
||||
|
||||
### [mini.deps](https://github.com/echasnovski/mini.deps)
|
||||
|
||||
```lua
|
||||
MiniDeps.add({
|
||||
source = 'barrettruth/forge.nvim',
|
||||
depends = { 'ibhagwan/fzf-lua' },
|
||||
})
|
||||
```
|
||||
|
||||
### [luarocks](https://luarocks.org/modules/barrettruth/forge.nvim)
|
||||
|
||||
```
|
||||
luarocks install forge.nvim
|
||||
```
|
||||
|
||||
## Documentation
|
||||
### Manual
|
||||
|
||||
```vim
|
||||
:help forge.nvim
|
||||
```sh
|
||||
git clone https://github.com/barrettruth/forge.nvim \
|
||||
~/.local/share/nvim/site/pack/plugins/start/forge.nvim
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
forge.nvim works through two entry points: the `:Forge` command and the
|
||||
`<c-g>` picker.
|
||||
|
||||
`:Forge` with no arguments (or `<c-g>`) opens the top-level picker — PRs,
|
||||
issues, CI, commits, branches, worktrees, and browse actions. Each sub-picker
|
||||
has contextual keybinds shown in the fzf header.
|
||||
|
||||
PR creation opens a compose buffer (markdown) pre-filled from commit messages
|
||||
and repo templates. First line is the title, everything after the blank line is
|
||||
the body. Draft, reviewers, and base branch are set in the HTML comment block
|
||||
below. Write (`:w`) to push and create.
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure via `vim.g.forge`. All fields are optional — defaults shown below.
|
||||
|
||||
```lua
|
||||
vim.g.forge = {
|
||||
ci = { lines = 10000 },
|
||||
sources = {},
|
||||
keys = {
|
||||
picker = '<c-g>',
|
||||
next_qf = ']q', prev_qf = '[q',
|
||||
next_loc = ']l', prev_loc = '[l',
|
||||
review_toggle = 's',
|
||||
terminal_open = 'gx',
|
||||
fugitive = {
|
||||
create = 'cpr', create_draft = 'cpd',
|
||||
create_fill = 'cpf', create_web = 'cpw',
|
||||
},
|
||||
},
|
||||
picker_keys = {
|
||||
pr = {
|
||||
checkout = 'default', diff = 'ctrl-d', worktree = 'ctrl-w',
|
||||
checks = 'ctrl-t', browse = 'ctrl-x', manage = 'ctrl-e',
|
||||
create = 'ctrl-a', toggle = 'ctrl-o', refresh = 'ctrl-r',
|
||||
},
|
||||
issue = { browse = 'default', close_reopen = 'ctrl-s', toggle = 'ctrl-o', refresh = 'ctrl-r' },
|
||||
checks = { log = 'default', browse = 'ctrl-x', failed = 'ctrl-f', passed = 'ctrl-p', running = 'ctrl-n', all = 'ctrl-a' },
|
||||
ci = { log = 'default', browse = 'ctrl-x', refresh = 'ctrl-r' },
|
||||
commits = { checkout = 'default', diff = 'ctrl-d', browse = 'ctrl-x', yank = 'ctrl-y' },
|
||||
branches = { diff = 'ctrl-d', browse = 'ctrl-x' },
|
||||
},
|
||||
display = {
|
||||
icons = { open = '+', merged = 'm', closed = 'x', pass = '*', fail = 'x', pending = '~', skip = '-', unknown = '?' },
|
||||
widths = { title = 45, author = 15, name = 35, branch = 25 },
|
||||
limits = { pulls = 100, issues = 100, runs = 30 },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Set `keys = false` to disable all keymaps. Set `picker_keys = false` to disable
|
||||
all picker keybinds. Set any individual key to `false` to disable it.
|
||||
|
||||
### Examples
|
||||
|
||||
Disable quickfix/loclist keymaps:
|
||||
|
||||
```lua
|
||||
vim.g.forge = {
|
||||
keys = { next_qf = false, prev_qf = false, next_loc = false, prev_loc = false },
|
||||
}
|
||||
```
|
||||
|
||||
Nerd font icons:
|
||||
|
||||
```lua
|
||||
vim.g.forge = {
|
||||
display = {
|
||||
icons = { open = '', merged = '', closed = '', pass = '', fail = '', pending = '', skip = '', unknown = '' },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Self-hosted GitLab:
|
||||
|
||||
```lua
|
||||
vim.g.forge = {
|
||||
sources = { gitlab = { hosts = { 'gitlab.mycompany.com' } } },
|
||||
}
|
||||
```
|
||||
|
||||
Override PR picker bindings:
|
||||
|
||||
```lua
|
||||
vim.g.forge = {
|
||||
picker_keys = { pr = { checkout = 'ctrl-o', diff = 'default' } },
|
||||
}
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
`:Forge` with no arguments opens the top-level picker. Subcommands:
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `:Forge pr` | List open PRs |
|
||||
| `:Forge pr --state={open,closed,all}` | List PRs by state |
|
||||
| `:Forge pr create [--draft] [--fill] [--web]` | Create PR |
|
||||
| `:Forge pr checkout {num}` | Checkout PR branch |
|
||||
| `:Forge pr diff {num}` | Review PR diff |
|
||||
| `:Forge pr worktree {num}` | Fetch PR into worktree |
|
||||
| `:Forge pr checks {num}` | Show PR checks |
|
||||
| `:Forge pr browse {num}` | Open PR in browser |
|
||||
| `:Forge pr manage {num}` | Merge/approve/close/draft actions |
|
||||
| `:Forge issue` | List all issues |
|
||||
| `:Forge issue --state={open,closed,all}` | List issues by state |
|
||||
| `:Forge issue browse {num}` | Open issue in browser |
|
||||
| `:Forge issue close {num}` | Close issue |
|
||||
| `:Forge issue reopen {num}` | Reopen issue |
|
||||
| `:Forge ci` | CI runs for current branch |
|
||||
| `:Forge ci --all` | CI runs for all branches |
|
||||
| `:Forge commit` | Browse commits |
|
||||
| `:Forge commit checkout {sha}` | Checkout commit |
|
||||
| `:Forge commit diff {sha}` | Review commit diff |
|
||||
| `:Forge commit browse {sha}` | Open commit in browser |
|
||||
| `:Forge branch` | Browse branches |
|
||||
| `:Forge branch diff {name}` | Review branch diff |
|
||||
| `:Forge branch browse {name}` | Open branch in browser |
|
||||
| `:Forge worktree` | List worktrees |
|
||||
| `:Forge browse [--root] [--commit]` | Open file/repo/commit in browser |
|
||||
| `:Forge yank [--commit]` | Yank permalink for file/line |
|
||||
| `:Forge review end` | End review session |
|
||||
| `:Forge review toggle` | Toggle split/unified review |
|
||||
| `:Forge cache clear` | Clear all caches |
|
||||
|
||||
## Keymaps
|
||||
|
||||
### Global
|
||||
|
||||
| Key | Mode | Description |
|
||||
|---|---|---|
|
||||
| `<c-g>` | n, v | Open forge picker |
|
||||
| `]q` / `[q` | n | Next/prev quickfix entry (wraps) |
|
||||
| `]l` / `[l` | n | Next/prev loclist entry (wraps) |
|
||||
|
||||
### Fugitive buffer
|
||||
|
||||
Active in `fugitive` filetype buffers when a forge is detected.
|
||||
|
||||
| Key | Description |
|
||||
|---|---|
|
||||
| `cpr` | Create PR (compose buffer) |
|
||||
| `cpd` | Create draft PR |
|
||||
| `cpf` | Create PR from commits (no compose) |
|
||||
| `cpw` | Push and open web creation |
|
||||
|
||||
### Review
|
||||
|
||||
Active during a review session.
|
||||
|
||||
| Key | Description |
|
||||
|---|---|
|
||||
| `s` | Toggle unified/split diff |
|
||||
|
||||
### Terminal (log buffers)
|
||||
|
||||
Active on CI/check log terminals when a URL is available.
|
||||
|
||||
| Key | Description |
|
||||
|---|---|
|
||||
| `gx` | Open run/check in browser |
|
||||
|
||||
## Picker Actions
|
||||
|
||||
Keybinds shown in the fzf header. `default` = `enter`.
|
||||
|
||||
| Picker | Key | Action |
|
||||
|---|---|---|
|
||||
| **PR** | `enter` | Checkout |
|
||||
| | `ctrl-d` | Review diff |
|
||||
| | `ctrl-w` | Worktree |
|
||||
| | `ctrl-t` | Checks |
|
||||
| | `ctrl-x` | Browse |
|
||||
| | `ctrl-e` | Manage (merge/approve/close/draft) |
|
||||
| | `ctrl-a` | Create new |
|
||||
| | `ctrl-o` | Cycle state (open/closed/all) |
|
||||
| | `ctrl-r` | Refresh |
|
||||
| **Issue** | `enter` | Browse |
|
||||
| | `ctrl-s` | Close/reopen |
|
||||
| | `ctrl-o` | Cycle state |
|
||||
| | `ctrl-r` | Refresh |
|
||||
| **Checks** | `enter` | View log (tails if running) |
|
||||
| | `ctrl-x` | Browse |
|
||||
| | `ctrl-f` / `ctrl-p` / `ctrl-n` | Filter: failed / passed / running |
|
||||
| | `ctrl-a` | Show all |
|
||||
| **CI** | `enter` | View log (tails if running) |
|
||||
| | `ctrl-x` | Browse |
|
||||
| | `ctrl-r` | Refresh |
|
||||
| **Commits** | `enter` | Checkout (detached) |
|
||||
| | `ctrl-d` | Review diff |
|
||||
| | `ctrl-x` | Browse |
|
||||
| | `ctrl-y` | Yank hash |
|
||||
| **Branches** | `ctrl-d` | Review diff |
|
||||
| | `ctrl-x` | Browse |
|
||||
|
||||
## Custom Sources
|
||||
|
||||
Register a custom forge source for self-hosted or alternative platforms:
|
||||
|
||||
```lua
|
||||
require('forge').register('mygitea', require('my_gitea_source'))
|
||||
```
|
||||
|
||||
Route remotes to your source by host:
|
||||
|
||||
```lua
|
||||
vim.g.forge = {
|
||||
sources = { mygitea = { hosts = { 'gitea.internal.dev' } } },
|
||||
}
|
||||
```
|
||||
|
||||
A source is a table implementing the `forge.Forge` interface. Required fields:
|
||||
`name` (string), `cli` (string, checked via `executable()`), `kinds` (`{ issue,
|
||||
pr }`), and `labels` (`{ issue, pr, pr_one, pr_full, ci }`).
|
||||
|
||||
Required methods (all receive `self`): `list_pr_json_cmd`, `list_issue_json_cmd`,
|
||||
`pr_json_fields`, `issue_json_fields`, `view_web`, `browse`, `browse_root`,
|
||||
`browse_branch`, `browse_commit`, `checkout_cmd`, `yank_branch`, `yank_commit`,
|
||||
`fetch_pr`, `pr_base_cmd`, `pr_for_branch_cmd`, `checks_cmd`, `check_log_cmd`,
|
||||
`check_tail_cmd`, `list_runs_json_cmd`, `list_runs_cmd`, `normalize_run`,
|
||||
`run_log_cmd`, `run_tail_cmd`, `merge_cmd`, `approve_cmd`, `repo_info`,
|
||||
`pr_state`, `close_cmd`, `reopen_cmd`, `close_issue_cmd`, `reopen_issue_cmd`,
|
||||
`draft_toggle_cmd`, `create_pr_cmd`, `create_pr_web_cmd`, `default_branch_cmd`,
|
||||
`template_paths`.
|
||||
|
||||
See `lua/forge/github.lua`, `lua/forge/gitlab.lua`, or `lua/forge/codeberg.lua`
|
||||
for complete implementations. The `forge.Forge` class definition with full type
|
||||
annotations is in `lua/forge/init.lua`.
|
||||
|
||||
### Skeleton
|
||||
|
||||
```lua
|
||||
local M = {
|
||||
name = 'mygitea',
|
||||
cli = 'tea',
|
||||
kinds = { issue = 'issues', pr = 'pulls' },
|
||||
labels = { issue = 'Issues', pr = 'PRs', pr_one = 'PR', pr_full = 'Pull Requests', ci = 'CI/CD' },
|
||||
}
|
||||
|
||||
function M:list_pr_json_cmd(state)
|
||||
return { 'tea', 'pr', 'list', '--state', state, '--output', 'json' }
|
||||
end
|
||||
|
||||
function M:pr_json_fields()
|
||||
return { number = 'number', title = 'title', branch = 'head', state = 'state', author = 'poster', created_at = 'created_at' }
|
||||
end
|
||||
|
||||
return M
|
||||
```
|
||||
|
||||
## Health
|
||||
|
||||
Run `:checkhealth forge` to verify your setup. Checks for `git`, forge CLIs
|
||||
(`gh`, `glab`, `tea`), required plugins (`fzf-lua`), optional plugins
|
||||
(`diffs.nvim`, `vim-fugitive`), and any registered custom sources.
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: How do I create a PR?**
|
||||
`<c-g>` -> Pull Requests -> `ctrl-a` to compose. Or from fugitive: `cpr`
|
||||
(compose), `cpd` (draft), `cpf` (instant), `cpw` (web).
|
||||
|
||||
Press `<c-g>` to open the forge picker, select "Pull Requests", then `<ctrl-a>`
|
||||
to create. Or from a fugitive buffer: `cpr` (create), `cpd` (draft), `cpf`
|
||||
(instant), `cpw` (web).
|
||||
**Q: Does review mode require diffs.nvim?**
|
||||
Yes. Without [diffs.nvim](https://github.com/barrettruth/diffs.nvim), the diff
|
||||
action and review toggling are unavailable.
|
||||
|
||||
**Q: Does forge.nvim support review diffs?**
|
||||
**Q: How does forge detection work?**
|
||||
forge.nvim reads the `origin` remote URL and matches against known hosts and
|
||||
any custom `sources.<name>.hosts`. The first match wins, and the CLI must be in
|
||||
`$PATH`.
|
||||
|
||||
Yes, with [diffs.nvim](https://github.com/barrettruth/diffs.nvim) installed.
|
||||
Select a PR and press `<ctrl-d>` to enter review mode with unified diff. Press
|
||||
`s` to toggle split/unified view. Navigate files with `]q`/`[q`.
|
||||
**Q: Can I use this with self-hosted GitLab/Gitea?**
|
||||
Yes. Add your host to `vim.g.forge.sources`. See the [examples](#examples).
|
||||
|
||||
**Q: What does `ctrl-o` do in pickers?**
|
||||
Cycles the state filter: open -> closed -> all -> open.
|
||||
|
||||
**Q: How do I merge/approve/close a PR?**
|
||||
`ctrl-e` on a PR in the picker opens the manage picker. Available actions depend
|
||||
on your repository permissions.
|
||||
|
||||
**Q: Does this work without a forge remote?**
|
||||
Partially. Commits, branches, and worktrees work in any git repo. PRs, issues,
|
||||
CI, and browse require a detected forge.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
*forge.nvim.txt* Forge-agnostic git workflow for Neovim
|
||||
|
||||
Author: Barrett Ruth <br.barrettruth@gmail.com> License: MIT
|
||||
|
||||
==============================================================================
|
||||
INTRODUCTION *forge.nvim*
|
||||
|
||||
|
|
@ -9,222 +7,788 @@ forge.nvim provides PR, issue, and CI workflows across GitHub, GitLab, and
|
|||
Codeberg from inside Neovim. It detects the forge from your git remote and
|
||||
delegates to the appropriate CLI (`gh`, `glab`, or `tea`).
|
||||
|
||||
Features: ~
|
||||
- Forge detection from git remote URL
|
||||
- PR lifecycle (list, create, checkout, review, merge, approve, draft toggle)
|
||||
- Issue management (list, browse, close/reopen)
|
||||
- CI/CD run viewing, log streaming, status filtering
|
||||
- PR compose buffer with diff stat and template discovery
|
||||
- Code review via diffs.nvim with unified/split toggle
|
||||
- Commit and branch browsing with forge permalinks
|
||||
- Worktree creation from PRs
|
||||
- fzf-lua pickers with contextual keybinds
|
||||
|
||||
==============================================================================
|
||||
CONTENTS *forge-contents*
|
||||
|
||||
1. Introduction ............................................... |forge.nvim|
|
||||
2. Requirements ....................................... |forge-requirements|
|
||||
3. Setup ..................................................... |forge-setup|
|
||||
4. Configuration ............................................ |forge-config|
|
||||
5. Forge Picker ............................................ |forge-picker|
|
||||
6. Pull Requests ................................................. |forge-pr|
|
||||
7. Issues .................................................. |forge-issues|
|
||||
8. CI/CD ...................................................... |forge-ci|
|
||||
9. Commits ............................................... |forge-commits|
|
||||
10. Branches .............................................. |forge-branches|
|
||||
11. Review ................................................ |forge-review|
|
||||
12. Compose Buffer ........................................ |forge-compose|
|
||||
13. Highlight Groups .................................... |forge-highlights|
|
||||
14. Health Check ............................................ |forge-health|
|
||||
|
||||
==============================================================================
|
||||
REQUIREMENTS *forge-requirements*
|
||||
|
||||
Requirements: ~
|
||||
- Neovim 0.10.0+
|
||||
- fzf-lua (required)
|
||||
- One or more forge CLIs:
|
||||
- `gh` for GitHub
|
||||
- `glab` for GitLab
|
||||
- `tea` for Codeberg/Gitea/Forgejo
|
||||
- vim-fugitive (optional, for fugitive keymaps)
|
||||
- vim-fugitive (optional, for fugitive keymaps and split review)
|
||||
- diffs.nvim (optional, for review mode)
|
||||
|
||||
==============================================================================
|
||||
SETUP *forge-setup*
|
||||
|
||||
Install with lazy.nvim: >lua
|
||||
{
|
||||
'barrettruth/forge.nvim',
|
||||
keys = {
|
||||
{ '<c-g>', mode = { 'n', 'v' } },
|
||||
},
|
||||
}
|
||||
<
|
||||
|
||||
Run `:checkhealth forge` to verify CLIs and dependencies.
|
||||
Run |:checkhealth| forge to verify CLIs and dependencies.
|
||||
|
||||
==============================================================================
|
||||
CONFIGURATION *forge-config*
|
||||
|
||||
Configuration is done via `vim.g.forge`: >lua
|
||||
Configuration is set via the `vim.g.forge` global. All keys are optional;
|
||||
unset keys use defaults. >lua
|
||||
vim.g.forge = {
|
||||
ci = { lines = 10000 },
|
||||
ci = { lines = 5000 },
|
||||
display = { icons = { open = '' } },
|
||||
}
|
||||
<
|
||||
|
||||
*forge-config-ci-lines*
|
||||
ci.lines ~
|
||||
Number of log lines to fetch for CI runs. Default: `10000`.
|
||||
Top-level keys: ~
|
||||
|
||||
`ci` *forge-config-ci*
|
||||
`ci.lines` `integer` (default `10000`)
|
||||
Maximum number of log lines fetched for CI/check log output.
|
||||
|
||||
`sources` *forge-config-sources*
|
||||
`table<string, { hosts: string[] }>` (default `{}`)
|
||||
Per-source host overrides for forge detection. Keys are source names
|
||||
(e.g. `"github"`, `"gitlab"`, or a custom name). Each value has a
|
||||
`hosts` list of hostname substrings matched against the git remote. >lua
|
||||
vim.g.forge = {
|
||||
sources = {
|
||||
gitlab = { hosts = { 'git.internal.co' } },
|
||||
myforgejo = { hosts = { 'forgejo.example.com' } },
|
||||
},
|
||||
}
|
||||
<
|
||||
|
||||
`keys` *forge-config-keys*
|
||||
`table|false` (default shown below)
|
||||
Global keymaps. Set to `false` to disable all global keymaps. Setting
|
||||
an individual key to `nil` or `false` disables that single keymap.
|
||||
|
||||
Defaults: >lua
|
||||
keys = {
|
||||
picker = '<c-g>',
|
||||
next_qf = ']q',
|
||||
prev_qf = '[q',
|
||||
next_loc = ']l',
|
||||
prev_loc = '[l',
|
||||
review_toggle = 's',
|
||||
terminal_open = 'gx',
|
||||
fugitive = {
|
||||
create = 'cpr',
|
||||
create_draft = 'cpd',
|
||||
create_fill = 'cpf',
|
||||
create_web = 'cpw',
|
||||
},
|
||||
}
|
||||
<
|
||||
`keys.picker` Open the main forge picker (|forge-picker|).
|
||||
`keys.next_qf` Navigate to next quickfix entry (wraps).
|
||||
`keys.prev_qf` Navigate to previous quickfix entry (wraps).
|
||||
`keys.next_loc` Navigate to next loclist entry (wraps).
|
||||
`keys.prev_loc` Navigate to previous loclist entry (wraps).
|
||||
`keys.review_toggle` Toggle unified/split review (|forge-review|).
|
||||
`keys.terminal_open` Open URL in browser from log terminal buffers.
|
||||
`keys.fugitive.create` Create PR via compose buffer.
|
||||
`keys.fugitive.create_draft` Create draft PR via compose buffer.
|
||||
`keys.fugitive.create_fill` Create PR instantly (skip compose buffer).
|
||||
`keys.fugitive.create_web` Push and open create-PR page in browser.
|
||||
|
||||
Set `keys.fugitive` to `false` to disable all fugitive-buffer keymaps.
|
||||
|
||||
`picker_keys` *forge-config-picker-keys*
|
||||
`table|false` (default shown below)
|
||||
Per-picker action bindings. Set to `false` to disable all picker-level
|
||||
actions. Use `"default"` to bind to `<enter>`. Other values use fzf-lua
|
||||
binding syntax (e.g. `"ctrl-d"`).
|
||||
|
||||
Defaults: >lua
|
||||
picker_keys = {
|
||||
pr = {
|
||||
checkout = 'default',
|
||||
diff = 'ctrl-d',
|
||||
worktree = 'ctrl-w',
|
||||
checks = 'ctrl-t',
|
||||
browse = 'ctrl-x',
|
||||
manage = 'ctrl-e',
|
||||
create = 'ctrl-a',
|
||||
toggle = 'ctrl-o',
|
||||
refresh = 'ctrl-r',
|
||||
},
|
||||
issue = {
|
||||
browse = 'default',
|
||||
close_reopen = 'ctrl-s',
|
||||
toggle = 'ctrl-o',
|
||||
refresh = 'ctrl-r',
|
||||
},
|
||||
checks = {
|
||||
log = 'default',
|
||||
browse = 'ctrl-x',
|
||||
failed = 'ctrl-f',
|
||||
passed = 'ctrl-p',
|
||||
running = 'ctrl-n',
|
||||
all = 'ctrl-a',
|
||||
},
|
||||
ci = {
|
||||
log = 'default',
|
||||
browse = 'ctrl-x',
|
||||
refresh = 'ctrl-r',
|
||||
},
|
||||
commits = {
|
||||
checkout = 'default',
|
||||
diff = 'ctrl-d',
|
||||
browse = 'ctrl-x',
|
||||
yank = 'ctrl-y',
|
||||
},
|
||||
branches = {
|
||||
diff = 'ctrl-d',
|
||||
browse = 'ctrl-x',
|
||||
},
|
||||
}
|
||||
<
|
||||
|
||||
`display` *forge-config-display*
|
||||
Controls icons, column widths, and fetch limits used in picker
|
||||
formatting.
|
||||
|
||||
`display.icons` *forge-config-display-icons*
|
||||
`table` Status icons used in picker lines.
|
||||
|
||||
Key Default Used for ~
|
||||
`open` `"+"` Open PRs/issues
|
||||
`merged` `"m"` Merged PRs
|
||||
`closed` `"x"` Closed PRs/issues
|
||||
`pass` `"*"` Passed checks/runs
|
||||
`fail` `"x"` Failed checks/runs
|
||||
`pending` `"~"` In-progress checks/runs
|
||||
`skip` `"-"` Skipped/cancelled runs
|
||||
`unknown` `"?"` Unknown status
|
||||
|
||||
`display.widths` *forge-config-display-widths*
|
||||
`table` Column widths (characters) for picker formatting.
|
||||
|
||||
Key Default ~
|
||||
`title` `45`
|
||||
`author` `15`
|
||||
`name` `35`
|
||||
`branch` `25`
|
||||
|
||||
`display.limits` *forge-config-display-limits*
|
||||
`table` Maximum items fetched per list API call.
|
||||
|
||||
Key Default ~
|
||||
`pulls` `100`
|
||||
`issues` `100`
|
||||
`runs` `30`
|
||||
|
||||
==============================================================================
|
||||
FORGE PICKER *forge-picker*
|
||||
COMMANDS *:Forge*
|
||||
|
||||
*<c-g>*
|
||||
Press `<c-g>` in normal or visual mode to open the main forge picker. The
|
||||
picker adapts based on the detected forge and current buffer state.
|
||||
`:Forge` *:Forge-no-args*
|
||||
Open the main forge picker (|forge-picker|). Same as pressing
|
||||
`keys.picker`.
|
||||
|
||||
Available entries: ~
|
||||
- Pull Requests / Merge Requests
|
||||
- Issues
|
||||
- CI / Pipelines
|
||||
- Browse Remote
|
||||
- Open File (current file on remote)
|
||||
- Yank Commit URL / Yank Branch URL
|
||||
- Commits
|
||||
- Branches
|
||||
- Worktrees
|
||||
`:Forge pr` [{flags}] *:Forge-pr*
|
||||
Open the PR list picker. Defaults to open PRs.
|
||||
`--state=open` Show open PRs (default).
|
||||
`--state=closed` Show closed PRs.
|
||||
`--state=all` Show all PRs.
|
||||
|
||||
`:Forge pr create` [{flags}] *:Forge-pr-create*
|
||||
Create a new PR.
|
||||
(no flags) Open the compose buffer (|forge-compose|).
|
||||
`--draft` Open compose buffer with draft pre-set.
|
||||
`--fill` Create instantly from commits (skip compose).
|
||||
`--web` Push and open the forge's web create-PR page.
|
||||
`--draft --fill` Create draft instantly.
|
||||
|
||||
`:Forge pr checkout` {num} *:Forge-pr-checkout*
|
||||
Check out the branch for PR `{num}`.
|
||||
|
||||
`:Forge pr diff` {num} *:Forge-pr-diff*
|
||||
Start a review for PR `{num}` (|forge-review|).
|
||||
|
||||
`:Forge pr worktree` {num} *:Forge-pr-worktree*
|
||||
Fetch PR `{num}` and create a git worktree.
|
||||
|
||||
`:Forge pr checks` {num} *:Forge-pr-checks*
|
||||
Open the checks picker for PR `{num}`.
|
||||
|
||||
`:Forge pr browse` {num} *:Forge-pr-browse*
|
||||
Open PR `{num}` in the browser.
|
||||
|
||||
`:Forge pr manage` {num} *:Forge-pr-manage*
|
||||
Open the management picker for PR `{num}` (approve, merge, close,
|
||||
reopen, draft toggle).
|
||||
|
||||
`:Forge issue` [{flags}] *:Forge-issue*
|
||||
Open the issue list picker. Defaults to all issues.
|
||||
`--state=open` Show open issues.
|
||||
`--state=closed` Show closed issues.
|
||||
`--state=all` Show all issues (default).
|
||||
|
||||
`:Forge issue browse` {num} *:Forge-issue-browse*
|
||||
Open issue `{num}` in the browser.
|
||||
|
||||
`:Forge issue close` {num} *:Forge-issue-close*
|
||||
Close issue `{num}`.
|
||||
|
||||
`:Forge issue reopen` {num} *:Forge-issue-reopen*
|
||||
Reopen issue `{num}`.
|
||||
|
||||
`:Forge ci` [{flags}] *:Forge-ci*
|
||||
Open the CI runs picker. Defaults to current branch.
|
||||
`--all` Show runs for all branches.
|
||||
|
||||
`:Forge commit` *:Forge-commit*
|
||||
Open the commit log picker.
|
||||
|
||||
`:Forge commit checkout` {sha} *:Forge-commit-checkout*
|
||||
Check out commit `{sha}` (detached HEAD).
|
||||
|
||||
`:Forge commit diff` {sha} *:Forge-commit-diff*
|
||||
Start a review for commit `{sha}`.
|
||||
|
||||
`:Forge commit browse` {sha} *:Forge-commit-browse*
|
||||
Open commit `{sha}` on the forge.
|
||||
|
||||
`:Forge branch` *:Forge-branch*
|
||||
Open the branch picker.
|
||||
|
||||
`:Forge branch diff` {name} *:Forge-branch-diff*
|
||||
Start a review diffing against `{name}`.
|
||||
|
||||
`:Forge branch browse` {name} *:Forge-branch-browse*
|
||||
Open branch `{name}` on the forge.
|
||||
|
||||
`:Forge worktree` *:Forge-worktree*
|
||||
Open fzf-lua's git worktree picker.
|
||||
|
||||
`:Forge browse` [{flags}] *:Forge-browse*
|
||||
Open the current file location on the forge.
|
||||
`--root` Open the repository root page.
|
||||
`--commit` Open the current HEAD commit.
|
||||
(no flags) Open the current file and line(s) on the current
|
||||
branch. In visual mode, the selected line range is
|
||||
included.
|
||||
|
||||
`:Forge yank` [{flags}] *:Forge-yank*
|
||||
Yank a permalink URL to the `+` register.
|
||||
`--commit` Yank a commit-pinned URL.
|
||||
(no flags) Yank a branch-pinned URL.
|
||||
|
||||
`:Forge review end` *:Forge-review-end*
|
||||
End the current review session.
|
||||
|
||||
`:Forge review toggle` *:Forge-review-toggle*
|
||||
Toggle between unified and split view (|forge-review|).
|
||||
|
||||
`:Forge cache clear` *:Forge-cache-clear*
|
||||
Clear all internal caches (forge detection, repo info, list data).
|
||||
|
||||
All subcommands support tab completion.
|
||||
|
||||
==============================================================================
|
||||
PULL REQUESTS *forge-pr*
|
||||
KEYMAPS *forge-keymaps*
|
||||
|
||||
The PR picker lists open PRs by default. Toggle state with `<ctrl-o>` to cycle
|
||||
through open/closed/all.
|
||||
GLOBAL KEYMAPS ~
|
||||
|
||||
PR picker keybinds: ~
|
||||
`<enter>` Checkout PR branch
|
||||
`<ctrl-d>` Review diff (requires diffs.nvim)
|
||||
`<ctrl-w>` Create worktree from PR
|
||||
`<ctrl-t>` View checks/CI status
|
||||
`<ctrl-x>` Open in browser
|
||||
`<ctrl-e>` Manage (merge, approve, close, draft toggle)
|
||||
`<ctrl-a>` Create new PR
|
||||
`<ctrl-o>` Toggle state filter (open/closed/all)
|
||||
`<ctrl-r>` Refresh list
|
||||
All global keymaps are configured via `keys` (|forge-config-keys|).
|
||||
|
||||
PR management actions: ~
|
||||
Approve, Merge (per available method), Close/Reopen, Draft toggle.
|
||||
Default Mode Description ~
|
||||
`<c-g>` n, v Open the main forge picker
|
||||
`]q` n Next quickfix entry (wraps to first)
|
||||
`[q` n Previous quickfix entry (wraps to last)
|
||||
`]l` n Next loclist entry (wraps to first)
|
||||
`[l` n Previous loclist entry (wraps to last)
|
||||
|
||||
FUGITIVE KEYMAPS ~
|
||||
|
||||
Active in `fugitive` filetype buffers when a forge is detected. Configured
|
||||
via `keys.fugitive`. Set `keys.fugitive = false` to disable.
|
||||
|
||||
Default Description ~
|
||||
`cpr` Create PR via compose buffer
|
||||
`cpd` Create draft PR via compose buffer
|
||||
`cpf` Create PR instantly (fill from commits)
|
||||
`cpw` Push and open create-PR page in browser
|
||||
|
||||
REVIEW KEYMAPS ~
|
||||
|
||||
Active during a review session (|forge-review|).
|
||||
|
||||
Default Description ~
|
||||
`s` Toggle unified/split view
|
||||
|
||||
TERMINAL KEYMAPS ~
|
||||
|
||||
Active in log terminal buffers opened by the checks or CI pickers.
|
||||
|
||||
Default Description ~
|
||||
`gx` Open the associated check/run URL in the browser
|
||||
|
||||
==============================================================================
|
||||
ISSUES *forge-issues*
|
||||
PICKERS *forge-pickers*
|
||||
|
||||
Issue picker keybinds: ~
|
||||
`<enter>` Open in browser
|
||||
`<ctrl-s>` Close/reopen issue
|
||||
`<ctrl-o>` Toggle state filter
|
||||
`<ctrl-r>` Refresh list
|
||||
*forge-picker*
|
||||
FORGE PICKER ~
|
||||
The main entry point. Adapts based on the detected forge and current
|
||||
buffer. Lists: PRs/MRs, Issues, CI/CD, Browse Remote, Open File, Yank
|
||||
Commit URL, Yank Branch URL, Commits, Branches, Worktrees. Items that
|
||||
require a forge are hidden when no forge is detected. "Open File" and yank
|
||||
entries require a named buffer on a branch.
|
||||
|
||||
==============================================================================
|
||||
CI/CD *forge-ci*
|
||||
*forge-picker-pr*
|
||||
PR PICKER ~
|
||||
Lists PRs/MRs with number, title, author, and relative time.
|
||||
|
||||
CI picker shows workflow runs for the current branch.
|
||||
Action Default Key Description ~
|
||||
`checkout` `<enter>` Check out the PR branch
|
||||
`diff` `ctrl-d` Start review (|forge-review|)
|
||||
`worktree` `ctrl-w` Create worktree from PR
|
||||
`checks` `ctrl-t` Open checks picker for this PR
|
||||
`browse` `ctrl-x` Open PR in browser
|
||||
`manage` `ctrl-e` Open management picker
|
||||
`create` `ctrl-a` Create new PR (|forge-compose|)
|
||||
`toggle` `ctrl-o` Cycle state: open -> closed -> all
|
||||
`refresh` `ctrl-r` Clear cache and re-fetch
|
||||
|
||||
CI picker keybinds: ~
|
||||
`<enter>` View logs (tail for in-progress, full for completed)
|
||||
`<ctrl-x>` Open in browser
|
||||
`<ctrl-r>` Refresh
|
||||
*forge-picker-issue*
|
||||
ISSUE PICKER ~
|
||||
Lists issues with number, title, author, and relative time.
|
||||
|
||||
Checks picker (from PR): ~
|
||||
`<enter>` View check logs
|
||||
`<ctrl-x>` Open in browser
|
||||
`<ctrl-f>` Filter to failed
|
||||
`<ctrl-p>` Filter to passed
|
||||
`<ctrl-n>` Filter to running
|
||||
`<ctrl-a>` Show all
|
||||
Action Default Key Description ~
|
||||
`browse` `<enter>` Open issue in browser
|
||||
`close_reopen` `ctrl-s` Close or reopen the issue
|
||||
`toggle` `ctrl-o` Cycle state: all -> open -> closed
|
||||
`refresh` `ctrl-r` Clear cache and re-fetch
|
||||
|
||||
==============================================================================
|
||||
COMMITS *forge-commits*
|
||||
*forge-picker-checks*
|
||||
CHECKS PICKER ~
|
||||
Lists PR check runs with status icon, name, and elapsed time.
|
||||
|
||||
Commit picker keybinds: ~
|
||||
`<enter>` Checkout commit (detached HEAD)
|
||||
`<ctrl-d>` Review diff
|
||||
`<ctrl-x>` Open in browser (requires forge)
|
||||
`<ctrl-y>` Yank commit hash
|
||||
Action Default Key Description ~
|
||||
`log` `<enter>` View log (tail for running, full otherwise)
|
||||
`browse` `ctrl-x` Open check URL in browser
|
||||
`failed` `ctrl-f` Filter to failed checks
|
||||
`passed` `ctrl-p` Filter to passed checks
|
||||
`running` `ctrl-n` Filter to running checks
|
||||
`all` `ctrl-a` Show all checks
|
||||
|
||||
==============================================================================
|
||||
BRANCHES *forge-branches*
|
||||
*forge-picker-ci*
|
||||
CI PICKER ~
|
||||
Lists CI/CD runs for the current branch (or all branches with `--all`).
|
||||
|
||||
Branch picker keybinds: ~
|
||||
`<ctrl-d>` Review diff against branch
|
||||
`<ctrl-x>` Open branch on remote (requires forge)
|
||||
Action Default Key Description ~
|
||||
`log` `<enter>` View log (tail for running, failed-only for
|
||||
failures, full otherwise)
|
||||
`browse` `ctrl-x` Open run URL in browser
|
||||
`refresh` `ctrl-r` Re-fetch runs
|
||||
|
||||
*forge-picker-commits*
|
||||
COMMITS PICKER ~
|
||||
Git log with colored output and commit preview.
|
||||
|
||||
Action Default Key Description ~
|
||||
`checkout` `<enter>` Checkout commit (detached HEAD)
|
||||
`diff` `ctrl-d` Review the commit diff
|
||||
`browse` `ctrl-x` Open commit on forge
|
||||
`yank` `ctrl-y` Yank commit hash to `+` register
|
||||
|
||||
*forge-picker-branches*
|
||||
BRANCHES PICKER ~
|
||||
Uses fzf-lua's `git_branches` with additional actions.
|
||||
|
||||
Action Default Key Description ~
|
||||
`diff` `ctrl-d` Review diff against branch
|
||||
`browse` `ctrl-x` Open branch on forge
|
||||
|
||||
*forge-picker-manage*
|
||||
MANAGE PICKER ~
|
||||
Contextual actions for a specific PR, shown based on permissions and
|
||||
state: Approve, Merge (per available method: squash, rebase, merge),
|
||||
Close/Reopen, Mark as draft/Mark as ready.
|
||||
|
||||
==============================================================================
|
||||
REVIEW *forge-review*
|
||||
|
||||
Review mode requires diffs.nvim. Enter review via `<ctrl-d>` on a PR or
|
||||
commit.
|
||||
Review mode provides unified and split diff viewing for PRs and commits.
|
||||
Requires diffs.nvim for unified view and vim-fugitive for split view.
|
||||
|
||||
Review keybinds: ~
|
||||
`s` Toggle unified/split view
|
||||
`]q` / `[q` Next/previous quickfix entry (file navigation)
|
||||
`]l` / `[l` Next/previous loclist entry
|
||||
Starting a review: ~
|
||||
- `ctrl-d` on a PR in the PR picker
|
||||
- `ctrl-d` on a commit in the commit picker
|
||||
- `:Forge pr diff {num}`
|
||||
- `:Forge commit diff {sha}`
|
||||
- `:Forge branch diff {name}`
|
||||
|
||||
Review mode ends automatically when the review buffer is wiped.
|
||||
Unified view (default): ~
|
||||
diffs.nvim renders a combined diff in a `diffs://review:*` buffer with
|
||||
a quickfix list of changed files.
|
||||
|
||||
Split view: ~
|
||||
Press the `review_toggle` key (default `s`) to switch to a side-by-side
|
||||
fugitive split (`:Gvdiffsplit`). Press again to return to unified.
|
||||
|
||||
Navigation: ~
|
||||
`]q` / `[q` navigate quickfix entries. In split mode, the split is
|
||||
automatically closed and reopened around the new file. Navigation wraps
|
||||
at list boundaries.
|
||||
|
||||
Ending a review: ~
|
||||
`:Forge review end` or wipe the `diffs://review:*` buffer.
|
||||
|
||||
State: ~
|
||||
`require('forge.review').state` holds: >lua
|
||||
{ base = 'origin/main', mode = 'unified' }
|
||||
<
|
||||
`base` is `nil` when no review is active.
|
||||
|
||||
==============================================================================
|
||||
COMPOSE BUFFER *forge-compose*
|
||||
|
||||
Creating a PR opens a compose buffer (`forge://pr/new`) with:
|
||||
Creating a PR (without `--fill` or `--web`) opens a compose buffer at
|
||||
`forge://pr/new` with filetype `markdown` and buftype `acwrite`.
|
||||
|
||||
- Line 1: PR title (pre-filled from commit subject or branch name)
|
||||
- Line 3+: PR body (pre-filled from commit body or PR template)
|
||||
- HTML comment block: metadata (branch info, draft, reviewers, diff stat)
|
||||
Layout: ~
|
||||
Line 1 PR title (pre-filled from single commit subject or branch)
|
||||
Line 2 Empty separator
|
||||
Lines 3+ PR body (from commit body or discovered PR template)
|
||||
`<!--` Metadata comment block (branch, forge, draft, reviewers,
|
||||
diff stat). Editable.
|
||||
`-->` End of metadata
|
||||
|
||||
The compose buffer is `filetype=markdown` with `buftype=acwrite`. Write the
|
||||
buffer (`:w`) to push and create the PR. An empty title or body aborts.
|
||||
Write (`:w`) to push the branch and create the PR. An empty title or body
|
||||
aborts creation. The PR URL is copied to the `+` register on success.
|
||||
|
||||
Metadata fields (editable in the comment block): ~
|
||||
`Draft:` Set to `yes` or `true` to create as draft
|
||||
`Reviewers:` Comma-separated list of reviewer usernames
|
||||
Metadata fields: ~
|
||||
`Draft: yes` Create as draft (also `true`). Empty = not draft.
|
||||
`Reviewers:` Comma or space separated usernames.
|
||||
|
||||
Template discovery: ~ forge.nvim searches for PR templates in the repository:
|
||||
- `.github/pull_request_template.md`
|
||||
- `.github/PULL_REQUEST_TEMPLATE/` (single file auto-selected, multiple
|
||||
prompts for choice)
|
||||
- GitLab/Codeberg equivalents
|
||||
Template discovery: ~
|
||||
forge.nvim searches forge-specific template paths. If a template
|
||||
directory contains multiple `.md` files, you are prompted to choose.
|
||||
GitHub: `.github/pull_request_template.md`,
|
||||
`.github/PULL_REQUEST_TEMPLATE/`
|
||||
GitLab: `.gitlab/merge_request_templates/`
|
||||
Codeberg: `.gitea/pull_request_template.md`,
|
||||
`.github/pull_request_template.md`
|
||||
|
||||
Fugitive keymaps: ~ From a fugitive buffer, the following keymaps are
|
||||
available:
|
||||
`cpr` Create PR
|
||||
`cpd` Create draft PR
|
||||
`cpf` Create PR instantly (skip compose buffer)
|
||||
`cpw` Create PR via web browser
|
||||
Highlight groups: ~
|
||||
|
||||
Group Default Link Description ~
|
||||
`ForgeComposeComment` `Comment` Comment block lines
|
||||
`ForgeComposeBranch` `Special` Branch names
|
||||
`ForgeComposeForge` `Type` Forge name
|
||||
`ForgeComposeDraft` `DiagnosticWarn` Draft status value
|
||||
`ForgeComposeFile` `Directory` File paths in diff stat
|
||||
`ForgeComposeAdded` `Added` Addition indicators (+)
|
||||
`ForgeComposeRemoved` `Removed` Deletion indicators (-)
|
||||
|
||||
All groups are defined with `default = true`.
|
||||
|
||||
==============================================================================
|
||||
HIGHLIGHT GROUPS *forge-highlights*
|
||||
SOURCES *forge-sources*
|
||||
|
||||
The compose buffer uses the following highlight groups:
|
||||
Built-in sources: ~
|
||||
|
||||
`ForgeComposeComment` Entire comment block Links to `Comment`
|
||||
`ForgeComposeBranch` Branch names Links to `Special`
|
||||
`ForgeComposeForge` Forge name Links to `Type`
|
||||
`ForgeComposeDraft` Draft status value Links to `DiagnosticWarn`
|
||||
`ForgeComposeFile` File paths in diff stat Links to `Directory`
|
||||
`ForgeComposeAdded` Addition indicators (+) Links to `Added`
|
||||
`ForgeComposeRemoved` Deletion indicators (-) Links to `Removed`
|
||||
Name CLI Hosts matched ~
|
||||
`github` `gh` `github`
|
||||
`gitlab` `glab` `gitlab`
|
||||
`codeberg` `tea` `codeberg`, `gitea`, `forgejo`
|
||||
|
||||
All groups are defined with `default = true` so colorschemes can override
|
||||
them.
|
||||
Sources are lazy-loaded the first time a matching remote is detected.
|
||||
Built-in host patterns are checked after user-configured `sources` hosts,
|
||||
so user overrides take priority.
|
||||
|
||||
HOST OVERRIDES ~
|
||||
*forge-sources-hosts*
|
||||
To route a self-hosted instance to a built-in source: >lua
|
||||
vim.g.forge = {
|
||||
sources = {
|
||||
gitlab = { hosts = { 'gitlab.corp.com' } },
|
||||
},
|
||||
}
|
||||
<
|
||||
|
||||
CUSTOM SOURCE REGISTRATION ~
|
||||
*forge-sources-register*
|
||||
Register a custom source with `require('forge').register()`: >lua
|
||||
local my_source = { ... }
|
||||
require('forge').register('myforgejo', my_source)
|
||||
<
|
||||
The name must match the key used in `sources` host config. The source
|
||||
module is also discoverable as `forge.<name>` (e.g. `require('forge.myforgejo')`
|
||||
if placed at `lua/forge/myforgejo.lua`).
|
||||
|
||||
THE `forge.Forge` INTERFACE ~
|
||||
*forge-Forge-interface*
|
||||
A source must implement all methods in the `forge.Forge` class. Each
|
||||
source is a table with these fields and methods:
|
||||
|
||||
Fields: ~
|
||||
`name` `string` Source name (e.g. `"github"`).
|
||||
`cli` `string` CLI executable name (e.g. `"gh"`).
|
||||
`kinds` `table` `{ issue = string, pr = string }` -- CLI
|
||||
subcommand names for issues and PRs.
|
||||
`labels` `table` `{ issue = string, pr = string,`
|
||||
`pr_one = string, pr_full = string,`
|
||||
`ci = string }` -- display labels.
|
||||
|
||||
Required methods: ~
|
||||
|
||||
All methods receive `self` as the first argument (use `:` syntax).
|
||||
|
||||
Method Returns ~
|
||||
`list_pr_json_cmd(state)` `string[]` cmd to list PRs as JSON.
|
||||
`list_issue_json_cmd(state)` `string[]` cmd to list issues as JSON.
|
||||
`pr_json_fields()` `table` field name mapping:
|
||||
`{ number, title, branch, state,`
|
||||
`author, created_at }`.
|
||||
`issue_json_fields()` `table` field name mapping:
|
||||
`{ number, title, state, author,`
|
||||
`created_at }`.
|
||||
`view_web(kind, num)` Opens PR/issue in browser.
|
||||
`browse(loc, branch)` Opens file location on remote.
|
||||
`browse_root()` Opens repo root in browser.
|
||||
`browse_branch(branch)` Opens branch in browser.
|
||||
`browse_commit(sha)` Opens commit in browser.
|
||||
`checkout_cmd(num)` `string[]` cmd to checkout a PR.
|
||||
`yank_branch(loc)` Yanks branch-pinned URL.
|
||||
`yank_commit(loc)` Yanks commit-pinned URL.
|
||||
`fetch_pr(num)` `string[]` git fetch refspec for PR.
|
||||
`pr_base_cmd(num)` `string[]` cmd returning base branch.
|
||||
`pr_for_branch_cmd(branch)` `string[]` cmd returning PR number
|
||||
for a branch (empty = no PR).
|
||||
`checks_cmd(num)` `string` shell command for checks.
|
||||
`check_log_cmd(run_id, failed_only)` `string[]` cmd to view check log.
|
||||
`check_tail_cmd(run_id)` `string[]` cmd to tail check log.
|
||||
`list_runs_cmd(branch?)` `string` shell command for CI runs.
|
||||
`normalize_run(entry)` `forge.CIRun` normalized run entry.
|
||||
`run_log_cmd(id, failed_only)` `string[]` cmd to view run log.
|
||||
`run_tail_cmd(id)` `string[]` cmd to tail run log.
|
||||
`merge_cmd(num, method)` `string[]` cmd to merge PR.
|
||||
`approve_cmd(num)` `string[]` cmd to approve PR.
|
||||
`repo_info()` `forge.RepoInfo` with `permission`
|
||||
(`"ADMIN"`, `"WRITE"`, `"READ"`)
|
||||
and `merge_methods` (`string[]`).
|
||||
`pr_state(num)` `forge.PRState` with `state`,
|
||||
`mergeable`, `review_decision`,
|
||||
`is_draft`.
|
||||
`close_cmd(num)` `string[]` cmd to close PR.
|
||||
`reopen_cmd(num)` `string[]` cmd to reopen PR.
|
||||
`close_issue_cmd(num)` `string[]` cmd to close issue.
|
||||
`reopen_issue_cmd(num)` `string[]` cmd to reopen issue.
|
||||
`create_pr_cmd(title, body,` `string[]` cmd to create a PR.
|
||||
`base, draft, reviewers?)`
|
||||
`default_branch_cmd()` `string[]` cmd returning default
|
||||
branch name.
|
||||
`template_paths()` `string[]` repo-relative paths to
|
||||
PR templates.
|
||||
`draft_toggle_cmd(num, is_draft)` `string[]?` cmd to toggle draft.
|
||||
Return `nil` if unsupported.
|
||||
|
||||
Optional methods: ~
|
||||
|
||||
Method Fallback ~
|
||||
`list_runs_json_cmd(branch?)` `string[]` JSON CI list cmd.
|
||||
If absent, `list_runs_cmd` is used
|
||||
as a raw fzf-lua command source.
|
||||
`checks_json_cmd(num)` `string[]` JSON checks cmd.
|
||||
If absent, `checks_cmd` string is
|
||||
used as a raw fzf-lua command.
|
||||
`create_pr_web_cmd()` `string[]?` cmd to open web PR
|
||||
creation. Return `nil` to no-op.
|
||||
|
||||
Skeleton: >lua
|
||||
local M = {
|
||||
name = 'myforgejo',
|
||||
cli = 'tea',
|
||||
kinds = { issue = 'issues', pr = 'pulls' },
|
||||
labels = {
|
||||
issue = 'Issues',
|
||||
pr = 'PRs',
|
||||
pr_one = 'PR',
|
||||
pr_full = 'Pull Requests',
|
||||
ci = 'CI/CD',
|
||||
},
|
||||
}
|
||||
|
||||
function M:list_pr_json_cmd(state)
|
||||
return { 'tea', 'pulls', 'list', '--state', state, '--output', 'json' }
|
||||
end
|
||||
|
||||
-- ... implement remaining methods ...
|
||||
|
||||
return M
|
||||
<
|
||||
|
||||
==============================================================================
|
||||
HEALTH CHECK *forge-health*
|
||||
HEALTH *forge-health*
|
||||
|
||||
Run `:checkhealth forge` to verify:
|
||||
- git is available
|
||||
- Forge CLIs (`gh`, `glab`, `tea`) and their status
|
||||
- fzf-lua is installed
|
||||
- diffs.nvim is available (for review mode)
|
||||
- vim-fugitive is available (for fugitive keymaps)
|
||||
`:checkhealth forge`
|
||||
|
||||
Reports on: ~
|
||||
- `git` availability
|
||||
- Forge CLI availability (`gh`, `glab`, `tea`)
|
||||
- `fzf-lua` installation (required)
|
||||
- `diffs.nvim` installation (review mode)
|
||||
- `vim-fugitive` availability (fugitive keymaps, split review)
|
||||
- Custom registered sources and their CLI availability
|
||||
|
||||
==============================================================================
|
||||
API *forge-api*
|
||||
|
||||
PUBLIC API: `require('forge')` ~
|
||||
*forge.detect()*
|
||||
`detect()`
|
||||
Returns `forge.Forge?`. Detects the forge for the current git
|
||||
repository by inspecting the `origin` remote URL. Returns `nil` if
|
||||
not in a git repo, no matching source, or the CLI is not installed.
|
||||
Results are cached per git root.
|
||||
|
||||
*forge.config()*
|
||||
`config()`
|
||||
Returns `table`. The resolved configuration, merging `vim.g.forge`
|
||||
over defaults. If `vim.g.forge.keys` is `false`, `cfg.keys` is
|
||||
`false`. Same for `picker_keys`.
|
||||
|
||||
*forge.register()*
|
||||
`register(name, source)`
|
||||
Registers a custom `forge.Forge` source under `name`.
|
||||
|
||||
*forge.registered_sources()*
|
||||
`registered_sources()`
|
||||
Returns `table<string, forge.Forge>`. All registered sources.
|
||||
|
||||
*forge.create_pr()*
|
||||
`create_pr(opts?)`
|
||||
Creates a PR. `opts` is `forge.CreatePROpts?`:
|
||||
`draft` `boolean?` Create as draft.
|
||||
`instant` `boolean?` Skip compose buffer, fill from commits.
|
||||
`web` `boolean?` Push and open web create page.
|
||||
|
||||
*forge.clear_cache()*
|
||||
`clear_cache()`
|
||||
Clears all internal caches (forge detection, repo info, git root,
|
||||
list data).
|
||||
|
||||
*forge.repo_info()*
|
||||
`repo_info(f)`
|
||||
Returns `forge.RepoInfo`. Fetches and caches repository info
|
||||
(permissions, merge methods) for forge `f`.
|
||||
|
||||
*forge.file_loc()*
|
||||
`file_loc()`
|
||||
Returns `string`. Current buffer file path relative to git root with
|
||||
line number(s). In visual mode, includes the selected line range
|
||||
(e.g. `"src/foo.lua:10-20"`).
|
||||
|
||||
*forge.remote_web_url()*
|
||||
`remote_web_url()`
|
||||
Returns `string`. The HTTPS URL of the `origin` remote, normalized
|
||||
from SSH or git URLs.
|
||||
|
||||
*forge.yank_url()*
|
||||
`yank_url(args)`
|
||||
Runs `args` (string[]) asynchronously and copies stdout to the `+`
|
||||
register.
|
||||
|
||||
*forge.clear_list()*
|
||||
`clear_list(key?)`
|
||||
Clears cached list data. If `key` is given, clears only that key.
|
||||
If `nil`, clears all list caches.
|
||||
|
||||
*forge.list_key()*
|
||||
`list_key(kind, state)`
|
||||
Returns `string`. Cache key for list data, scoped to git root.
|
||||
|
||||
*forge.get_list()*
|
||||
`get_list(key)`
|
||||
Returns `table[]?`. Cached list data for `key`.
|
||||
|
||||
*forge.set_list()*
|
||||
`set_list(key, data)`
|
||||
Stores `data` in the list cache under `key`.
|
||||
|
||||
*forge.log()*
|
||||
`log(msg, level?)`
|
||||
Schedules a `vim.notify` with `[forge.nvim]:` prefix.
|
||||
|
||||
*forge.log_now()*
|
||||
`log_now(msg, level?)`
|
||||
Synchronous `vim.notify` with `[forge.nvim]:` prefix and redraw.
|
||||
|
||||
PUBLIC API: `require('forge.pickers')` ~
|
||||
*forge.pickers.git()*
|
||||
`git()`
|
||||
Opens the main forge picker. Requires a git repository.
|
||||
|
||||
*forge.pickers.pr()*
|
||||
`pr(state, f)`
|
||||
Opens the PR list picker. `state`: `"open"`, `"closed"`, or `"all"`.
|
||||
`f`: `forge.Forge`.
|
||||
|
||||
*forge.pickers.issue()*
|
||||
`issue(state, f)`
|
||||
Opens the issue list picker. `state`: `"open"`, `"closed"`, or
|
||||
`"all"`. `f`: `forge.Forge`.
|
||||
|
||||
*forge.pickers.checks()*
|
||||
`checks(f, num, filter?, cached_checks?)`
|
||||
Opens the checks picker for PR `num`. `filter`: `"all"`, `"fail"`,
|
||||
`"pass"`, or `"pending"`.
|
||||
|
||||
*forge.pickers.ci()*
|
||||
`ci(f, branch?)`
|
||||
Opens the CI runs picker. `nil` branch shows all.
|
||||
|
||||
*forge.pickers.commits()*
|
||||
`commits(f)`
|
||||
Opens the commit log picker. `f` may be `nil` (browse action
|
||||
requires a forge).
|
||||
|
||||
*forge.pickers.branches()*
|
||||
`branches(f)`
|
||||
Opens the branch picker. `f` may be `nil`.
|
||||
|
||||
*forge.pickers.pr_manage()*
|
||||
`pr_manage(f, num)`
|
||||
Opens the management picker for PR `num`.
|
||||
|
||||
*forge.pickers.pr_actions()*
|
||||
`pr_actions(f, num)`
|
||||
Returns `table<string, function>`. Action functions for PR `num`,
|
||||
keyed by fzf binding. Also has `_by_name` table keyed by action
|
||||
name (`"checkout"`, `"diff"`, `"worktree"`, `"browse"`, `"checks"`,
|
||||
`"manage"`).
|
||||
|
||||
*forge.pickers.issue_close()*
|
||||
`issue_close(f, num)`
|
||||
Closes issue `num`.
|
||||
|
||||
*forge.pickers.issue_reopen()*
|
||||
`issue_reopen(f, num)`
|
||||
Reopens issue `num`.
|
||||
|
||||
PUBLIC API: `require('forge.review')` ~
|
||||
*forge.review.start()*
|
||||
`start(base, mode?)`
|
||||
Starts a review session. `base` is the diff range (e.g.
|
||||
`"origin/main"`, `"abc123^..abc123"`). `mode`: `"unified"` (default)
|
||||
or `"split"`.
|
||||
|
||||
*forge.review.stop()*
|
||||
`stop()`
|
||||
Ends the current review session. Clears state and removes the
|
||||
`review_toggle` keymap.
|
||||
|
||||
*forge.review.toggle()*
|
||||
`toggle()`
|
||||
Toggles between unified and split view. No-op if no review is
|
||||
active.
|
||||
|
||||
*forge.review.nav()*
|
||||
`nav(nav_cmd)`
|
||||
Returns `function`. Creates a navigation function for `nav_cmd`
|
||||
(`"cnext"`, `"cprev"`, `"lnext"`, `"lprev"`). In split mode,
|
||||
closes the split before navigating and reopens after. Wraps at
|
||||
list boundaries.
|
||||
|
||||
*forge.review.state*
|
||||
`state`
|
||||
`table` with fields:
|
||||
`base` `string?` The current diff range, or `nil`.
|
||||
`mode` `"unified"|"split"` Current view mode.
|
||||
|
||||
==============================================================================
|
||||
vim:tw=78:ts=8:ft=help:norl:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,109 @@
|
|||
local M = {}
|
||||
|
||||
---@class forge.Config
|
||||
---@field ci forge.CIConfig
|
||||
---@field sources table<string, forge.SourceConfig>
|
||||
---@field keys forge.KeysConfig|false
|
||||
---@field picker_keys forge.PickerKeysConfig|false
|
||||
---@field display forge.DisplayConfig
|
||||
|
||||
---@class forge.CIConfig
|
||||
---@field lines integer
|
||||
|
||||
---@class forge.SourceConfig
|
||||
---@field hosts string[]
|
||||
|
||||
---@class forge.KeysConfig
|
||||
---@field picker string|false
|
||||
---@field next_qf string|false
|
||||
---@field prev_qf string|false
|
||||
---@field next_loc string|false
|
||||
---@field prev_loc string|false
|
||||
---@field review_toggle string|false
|
||||
---@field terminal_open string|false
|
||||
---@field fugitive forge.FugitiveKeysConfig|false
|
||||
|
||||
---@class forge.FugitiveKeysConfig
|
||||
---@field create string|false
|
||||
---@field create_draft string|false
|
||||
---@field create_fill string|false
|
||||
---@field create_web string|false
|
||||
|
||||
---@class forge.PickerKeysConfig
|
||||
---@field pr forge.PRPickerKeys
|
||||
---@field issue forge.IssuePickerKeys
|
||||
---@field checks forge.ChecksPickerKeys
|
||||
---@field ci forge.CIPickerKeys
|
||||
---@field commits forge.CommitsPickerKeys
|
||||
---@field branches forge.BranchesPickerKeys
|
||||
|
||||
---@class forge.PRPickerKeys
|
||||
---@field checkout string|false
|
||||
---@field diff string|false
|
||||
---@field worktree string|false
|
||||
---@field checks string|false
|
||||
---@field browse string|false
|
||||
---@field manage string|false
|
||||
---@field create string|false
|
||||
---@field toggle string|false
|
||||
---@field refresh string|false
|
||||
|
||||
---@class forge.IssuePickerKeys
|
||||
---@field browse string|false
|
||||
---@field close_reopen string|false
|
||||
---@field toggle string|false
|
||||
---@field refresh string|false
|
||||
|
||||
---@class forge.ChecksPickerKeys
|
||||
---@field log string|false
|
||||
---@field browse string|false
|
||||
---@field failed string|false
|
||||
---@field passed string|false
|
||||
---@field running string|false
|
||||
---@field all string|false
|
||||
|
||||
---@class forge.CIPickerKeys
|
||||
---@field log string|false
|
||||
---@field browse string|false
|
||||
---@field refresh string|false
|
||||
|
||||
---@class forge.CommitsPickerKeys
|
||||
---@field checkout string|false
|
||||
---@field diff string|false
|
||||
---@field browse string|false
|
||||
---@field yank string|false
|
||||
|
||||
---@class forge.BranchesPickerKeys
|
||||
---@field diff string|false
|
||||
---@field browse string|false
|
||||
|
||||
---@class forge.DisplayConfig
|
||||
---@field icons forge.IconsConfig
|
||||
---@field widths forge.WidthsConfig
|
||||
---@field limits forge.LimitsConfig
|
||||
|
||||
---@class forge.IconsConfig
|
||||
---@field open string
|
||||
---@field merged string
|
||||
---@field closed string
|
||||
---@field pass string
|
||||
---@field fail string
|
||||
---@field pending string
|
||||
---@field skip string
|
||||
---@field unknown string
|
||||
|
||||
---@class forge.WidthsConfig
|
||||
---@field title integer
|
||||
---@field author integer
|
||||
---@field name integer
|
||||
---@field branch integer
|
||||
|
||||
---@class forge.LimitsConfig
|
||||
---@field pulls integer
|
||||
---@field issues integer
|
||||
---@field runs integer
|
||||
|
||||
---@type forge.Config
|
||||
local DEFAULTS = {
|
||||
ci = { lines = 10000 },
|
||||
sources = {},
|
||||
|
|
@ -71,10 +175,13 @@ local DEFAULTS = {
|
|||
---@type table<string, forge.Forge>
|
||||
local sources = {}
|
||||
|
||||
---@param name string
|
||||
---@param source forge.Forge
|
||||
function M.register(name, source)
|
||||
sources[name] = source
|
||||
end
|
||||
|
||||
---@return table<string, forge.Forge>
|
||||
function M.registered_sources()
|
||||
return sources
|
||||
end
|
||||
|
|
@ -635,6 +742,7 @@ function M.filter_checks(checks, filter)
|
|||
return filtered
|
||||
end
|
||||
|
||||
---@return forge.Config
|
||||
function M.config()
|
||||
local user = vim.g.forge or {}
|
||||
local cfg = vim.tbl_deep_extend('force', DEFAULTS, user)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue