From 25509a48ace98e11b44f5846eb92dfdc02795725 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 27 Mar 2026 18:17:12 -0400 Subject: [PATCH] more tweaks --- .github/ISSUE_TEMPLATE/bug_report.yaml | 29 ++++ .github/pull_request_template.md | 3 + .github/workflows/quality.yaml | 14 ++ README.md | 66 +++++++ doc/forge.nvim.txt | 230 +++++++++++++++++++++++++ flake.lock | 134 ++++++++++++++ flake.nix | 46 +++++ 7 files changed, 522 insertions(+) create mode 100644 .github/pull_request_template.md create mode 100644 README.md create mode 100644 doc/forge.nvim.txt create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 9c2430b..7ef11ff 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -58,3 +58,32 @@ body: label: 'Forge CLI version' description: 'Output of `gh --version`, `glab --version`, or `tea --version`' render: text + + - type: textarea + attributes: + label: Minimal reproduction + description: | + Save the script below as `repro.lua`, edit if needed, and run: + ``` + nvim -u repro.lua + ``` + Confirm the bug reproduces with this config before submitting. + render: lua + value: | + vim.env.LAZY_STDPATH = '.repro' + load(vim.fn.system('curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua'))() + require('lazy.nvim').setup({ + spec = { + { 'barrettruth/midnight.nvim', lazy = false, config = function() vim.cmd.colorscheme('midnight') end }, + { 'tpope/vim-fugitive' }, + { 'ibhagwan/fzf-lua' }, + { + 'barrettruth/forge.nvim', + keys = { + { '', mode = { 'n', 'v' } }, + }, + }, + }, + }) + validations: + required: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..b98ec6e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,3 @@ +## Problem + +## Solution diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 303519f..b85e4d4 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -14,6 +14,7 @@ jobs: lua: ${{ steps.changes.outputs.lua }} markdown: ${{ steps.changes.outputs.markdown }} vimdoc: ${{ steps.changes.outputs.vimdoc }} + nix: ${{ steps.changes.outputs.nix }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3 @@ -31,6 +32,9 @@ jobs: - '*.md' vimdoc: - 'doc/**' + nix: + - '*.nix' + - 'flake.lock' lua-format: name: Lua Format Check @@ -76,6 +80,16 @@ jobs: - uses: cachix/install-nix-action@v31 - run: nix develop .#ci --command vimdoc-language-server check doc/ + nix-format: + name: Nix Format Check + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.nix == 'true' }} + steps: + - uses: actions/checkout@v4 + - uses: cachix/install-nix-action@v31 + - run: nix fmt -- --check . + markdown-format: name: Markdown Format Check runs-on: ubuntu-latest diff --git a/README.md b/README.md new file mode 100644 index 0000000..b6e023d --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# forge.nvim + +**Forge-agnostic git workflow for Neovim** + +PR, issue, and CI workflows across GitHub, GitLab, and more — without leaving +your editor. + +## Features + +- 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, + 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 + +## Dependencies + +- Neovim 0.10.0+ +- [fzf-lua](https://github.com/ibhagwan/fzf-lua) +- At least one forge CLI: + - [`gh`](https://cli.github.com/) for GitHub + - [`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) +- [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): + +``` +luarocks install forge.nvim +``` + +## Documentation + +```vim +:help forge.nvim +``` + +## FAQ + +**Q: How do I create a PR?** + +Press `` to open the forge picker, select "Pull Requests", then `` +to create. Or from a fugitive buffer: `cpr` (create), `cpd` (draft), `cpf` +(instant), `cpw` (web). + +**Q: Does forge.nvim support review diffs?** + +Yes, with [diffs.nvim](https://github.com/barrettruth/diffs.nvim) installed. +Select a PR and press `` to enter review mode with unified diff. Press +`s` to toggle split/unified view. Navigate files with `]q`/`[q`. diff --git a/doc/forge.nvim.txt b/doc/forge.nvim.txt new file mode 100644 index 0000000..3a86083 --- /dev/null +++ b/doc/forge.nvim.txt @@ -0,0 +1,230 @@ +*forge.nvim.txt* Forge-agnostic git workflow for Neovim + +Author: Barrett Ruth License: MIT + +============================================================================== +INTRODUCTION *forge.nvim* + +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* + +- 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) +- diffs.nvim (optional, for review mode) + +============================================================================== +SETUP *forge-setup* + +Install with lazy.nvim: >lua + { + 'barrettruth/forge.nvim', + keys = { + { '', mode = { 'n', 'v' } }, + }, + } +< + +Run `:checkhealth forge` to verify CLIs and dependencies. + +============================================================================== +CONFIGURATION *forge-config* + +Configuration is done via `vim.g.forge`: >lua + vim.g.forge = { + ci = { lines = 10000 }, + } +< + + *forge-config-ci-lines* +ci.lines ~ + Number of log lines to fetch for CI runs. Default: `10000`. + +============================================================================== +FORGE PICKER *forge-picker* + + ** +Press `` in normal or visual mode to open the main forge picker. The +picker adapts based on the detected forge and current buffer state. + +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 + +============================================================================== +PULL REQUESTS *forge-pr* + +The PR picker lists open PRs by default. Toggle state with `` to +cycle through open/closed/all. + +PR picker keybinds: ~ + `` Checkout PR branch + `` Review diff (requires diffs.nvim) + `` Create worktree from PR + `` View checks/CI status + `` Open in browser + `` Manage (merge, approve, close, draft toggle) + `` Create new PR + `` Toggle state filter (open/closed/all) + `` Refresh list + +PR management actions: ~ + Approve, Merge (per available method), Close/Reopen, Draft toggle. + +============================================================================== +ISSUES *forge-issues* + +Issue picker keybinds: ~ + `` Open in browser + `` Close/reopen issue + `` Toggle state filter + `` Refresh list + +============================================================================== +CI/CD *forge-ci* + +CI picker shows workflow runs for the current branch. + +CI picker keybinds: ~ + `` View logs (tail for in-progress, full for completed) + `` Open in browser + `` Refresh + +Checks picker (from PR): ~ + `` View check logs + `` Open in browser + `` Filter to failed + `` Filter to passed + `` Filter to running + `` Show all + +============================================================================== +COMMITS *forge-commits* + +Commit picker keybinds: ~ + `` Checkout commit (detached HEAD) + `` Review diff + `` Open in browser (requires forge) + `` Yank commit hash + +============================================================================== +BRANCHES *forge-branches* + +Branch picker keybinds: ~ + `` Review diff against branch + `` Open branch on remote (requires forge) + +============================================================================== +REVIEW *forge-review* + +Review mode requires diffs.nvim. Enter review via `` on a PR or +commit. + +Review keybinds: ~ + `s` Toggle unified/split view + `]q` / `[q` Next/previous quickfix entry (file navigation) + `]l` / `[l` Next/previous loclist entry + +Review mode ends automatically when the review buffer is wiped. + +============================================================================== +COMPOSE BUFFER *forge-compose* + +Creating a PR opens a compose buffer (`forge://pr/new`) with: + +- 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) + +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. + +Metadata fields (editable in the comment block): ~ + `Draft:` Set to `yes` or `true` to create as draft + `Reviewers:` Comma-separated list of reviewer 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 + +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 *forge-highlights* + +The compose buffer uses the following highlight groups: + + `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` + +All groups are defined with `default = true` so colorschemes can override them. + +============================================================================== +HEALTH CHECK *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) + +============================================================================== + vim:tw=78:ts=8:ft=help:norl: diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..b3157b1 --- /dev/null +++ b/flake.lock @@ -0,0 +1,134 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1774273680, + "narHash": "sha256-a++tZ1RQsDb1I0NHrFwdGuRlR5TORvCEUksM459wKUA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "fdc7b8f7b30fdbedec91b71ed82f36e1637483ed", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1772624091, + "narHash": "sha256-QKyJ0QGWBn6r0invrMAK8dmJoBYWoOWy7lN+UHzW1jc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "80bdc1e5ce51f56b19791b52b2901187931f5353", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "systems": "systems", + "vimdoc-language-server": "vimdoc-language-server" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "vimdoc-language-server", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1772852295, + "narHash": "sha256-3FB/WzLZSiU2Mc50C9q9VXU1LRUZbsU6UHKmZG1C+hU=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "c10801f59c68e14c308aea8fa6b0b3d81d43c61e", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "vimdoc-language-server": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs_2", + "rust-overlay": "rust-overlay" + }, + "locked": { + "lastModified": 1774450573, + "narHash": "sha256-K2oPjDWruf+QL/81SLmY01fPM8vXDycTugh3J8wEQdo=", + "owner": "barrettruth", + "repo": "vimdoc-language-server", + "rev": "6a6ecc8077a50f871b7611e1b318823925171a39", + "type": "github" + }, + "original": { + "owner": "barrettruth", + "repo": "vimdoc-language-server", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..e11545a --- /dev/null +++ b/flake.nix @@ -0,0 +1,46 @@ +{ + description = "forge.nvim — forge-agnostic git workflow for Neovim"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + systems.url = "github:nix-systems/default"; + vimdoc-language-server.url = "github:barrettruth/vimdoc-language-server"; + }; + + outputs = + { + nixpkgs, + systems, + vimdoc-language-server, + ... + }: + let + forEachSystem = + f: nixpkgs.lib.genAttrs (import systems) (system: f nixpkgs.legacyPackages.${system}); + in + { + formatter = forEachSystem (pkgs: pkgs.nixfmt-tree); + + devShells = forEachSystem ( + pkgs: + let + vimdoc-ls = vimdoc-language-server.packages.${pkgs.system}.default; + commonPackages = [ + pkgs.prettier + pkgs.stylua + pkgs.selene + pkgs.lua-language-server + vimdoc-ls + ]; + in + { + default = pkgs.mkShell { + packages = commonPackages; + }; + ci = pkgs.mkShell { + packages = commonPackages; + }; + } + ); + }; +}