PR code review inside Neovim. Review GitHub pull requests without leaving your editor.
- Base branch preview - Toggle side-by-side diff view showing the base branch version
- Follow code jumps - Preview updates when navigating to other files via LSP
- PR comments - Create, view, reply, edit, and delete review comments on specific lines
- Suggest changes - Post GitHub suggestion blocks with pre-filled code for one-click apply
- Virtual text - Comment and pending indicators on lines with existing comments
- Pending review - Comments are saved as GitHub pending review (visible on PR page)
- Review submission - Submit pending comments as a GitHub review with Comment/Approve/Request Changes
- Comment navigation - Jump between comments with
]c/[c - Review scope - Review the full PR or focus on a specific commit, navigate scopes with next/prev, mark commits as reviewed, statusline integration
- Changed files - Browse PR changed files with Telescope (diff preview) or quickfix
- PR overview - Split-pane view with PR info, description, comments (left) and reviewers, assignees, labels, CI status (right). Sections are foldable with standard Neovim fold commands
- GitHub references -
#123and URLs are highlighted and openable withgx - GitHub completion -
@user,#issue, and_commitcompletion in comment windows (blink.cmp / nvim-cmp) - Viewed files - Mark/unmark files as viewed (synced with GitHub)
- Create PR - Create draft PRs from templates with a two-pane float (title + body)
- Open in browser - Open the PR in your browser
- Gitsigns integration - Automatically switches gitsigns diff base to PR base branch
- Neovim >= 0.10
- GitHub CLI (
gh) installed and authenticated - Optional: telescope.nvim for file picker
- Optional: gitsigns.nvim for diff base switching
- Optional: blink.cmp or nvim-cmp for
@user/#issue/_commitcompletion
{
"flexphere/fude.nvim",
opts = {},
cmd = {
"FudeReviewStart", "FudeReviewStop", "FudeReviewToggle", "FudeReviewDiff",
"FudeReviewComment", "FudeReviewSuggest", "FudeReviewViewComment", "FudeReviewListComments",
"FudeReviewFiles", "FudeReviewScope", "FudeReviewScopeNext", "FudeReviewScopePrev",
"FudeReviewOverview", "FudeReviewSubmit", "FudeReviewBrowse",
"FudeReviewViewed", "FudeReviewUnviewed", "FudeCreatePR",
},
keys = {
{ "<leader>et", "<cmd>FudeReviewToggle<cr>", desc = "Review: Toggle" },
{ "<leader>es", "<cmd>FudeReviewStart<cr>", desc = "Review: Start" },
{ "<leader>eq", "<cmd>FudeReviewStop<cr>", desc = "Review: Stop" },
{ "<leader>ec", "<cmd>FudeReviewComment<cr>", desc = "Review: Comment", mode = { "n" } },
{ "<leader>ec", ":FudeReviewComment<cr>", desc = "Review: Comment (selection)", mode = { "v" } },
{ "<leader>eS", "<cmd>FudeReviewSuggest<cr>", desc = "Review: Suggest change", mode = { "n" } },
{ "<leader>eS", ":FudeReviewSuggest<cr>", desc = "Review: Suggest change (selection)", mode = { "v" } },
{ "<leader>ev", "<cmd>FudeReviewViewComment<cr>", desc = "Review: View comments" },
{ "<leader>ef", "<cmd>FudeReviewFiles<cr>", desc = "Review: Changed files" },
{ "<leader>eo", "<cmd>FudeReviewOverview<cr>", desc = "Review: PR Overview" },
{ "<leader>ed", "<cmd>FudeReviewDiff<cr>", desc = "Review: Toggle diff" },
{ "<leader>eb", "<cmd>FudeReviewBrowse<cr>", desc = "Review: Open in browser" },
{ "<leader>eC", "<cmd>FudeReviewScope<cr>", desc = "Review: Select scope" },
{ "<leader>e]", "<cmd>FudeReviewScopeNext<cr>", desc = "Review: Next scope" },
{ "<leader>e[", "<cmd>FudeReviewScopePrev<cr>", desc = "Review: Prev scope" },
{ "<leader>el", "<cmd>FudeReviewListComments<cr>", desc = "Review: List comments" },
{
"<leader>er",
function() require("fude.comments").reply_to_comment() end,
desc = "Review: Reply",
},
{ "<leader>em", "<cmd>FudeReviewViewed<cr>", desc = "Review: Mark viewed" },
{ "<leader>eM", "<cmd>FudeReviewUnviewed<cr>", desc = "Review: Unmark viewed" },
-- ]c / [c are set automatically as buffer-local keymaps during review mode
-- <Tab> toggles viewed state in FudeReviewFiles / reviewed state in FudeReviewScope
},
}- Checkout a PR branch:
gh pr checkout <number> - Start review mode:
:FudeReviewStart(detects PR, fetches comments, sets up extmarks) - Optionally open diff preview:
:FudeReviewDiff(toggle side-by-side diff view) - Navigate code normally - the preview follows your movements when open
- Create comments with
:FudeReviewComment(saved as GitHub pending review) - View existing comments with
:FudeReviewViewComment - Submit pending comments as a review:
:FudeReviewSubmit(select Comment/Approve/Request Changes) - Browse changed files with
:FudeReviewFiles - View PR overview with
:FudeReviewOverview - Stop review mode:
:FudeReviewStop
| Command | Description |
|---|---|
:FudeReviewStart |
Start review session (PR detection, comments, extmarks) |
:FudeReviewStop |
Stop review session |
:FudeReviewToggle |
Toggle review session |
:FudeReviewDiff |
Toggle diff preview window |
:FudeReviewComment |
Create pending comment on current line/selection |
:FudeReviewSuggest |
Create pending suggestion on current line/selection |
:FudeReviewViewComment |
View comments on current line |
:FudeReviewFiles |
List PR changed files (Telescope/quickfix) |
:FudeReviewScope |
Select review scope (full PR or specific commit) |
:FudeReviewScopeNext |
Move to next review scope |
:FudeReviewScopePrev |
Move to previous review scope |
:FudeReviewOverview |
Show PR overview and issue-level comments |
:FudeReviewListComments |
Browse all PR review and issue comments in 3-pane floating window |
:FudeReviewSubmit |
Submit pending comments as a review (Comment/Approve/Request Changes) |
:FudeReviewViewed |
Mark current file as viewed on GitHub |
:FudeReviewUnviewed |
Unmark current file as viewed on GitHub |
:FudeReviewBrowse |
Open PR in browser |
:FudeCreatePR |
Create draft PR from template |
require("fude").setup({
-- File list mode: "telescope" or "quickfix"
file_list_mode = "telescope",
-- Diff filler character (nil to keep user's default)
diff_filler_char = nil,
-- Additional diffopt values applied during review
diffopt = { "algorithm:histogram", "linematch:60", "indent-heuristic" },
signs = {
comment = "#",
comment_hl = "DiagnosticInfo",
pending = "⏳ pending",
pending_hl = "DiagnosticHint",
viewed = "✓",
viewed_hl = "DiagnosticOk",
},
float = {
border = "single",
-- Width/height as percentage of screen (1-100)
width = 50,
height = 50,
},
overview = {
-- Width/height as percentage of screen (1-100)
width = 80,
height = 80,
-- Right pane width as percentage of total overview width
right_width = 30,
},
-- Flash highlight when navigating to a comment line (]c/[c)
flash = {
duration = 200, -- ms
hl_group = "Visual",
},
-- Auto-open comment viewer when navigating to a comment line (]c/[c/FudeReviewListComments)
auto_view_comment = true,
-- strftime format for timestamps (system timezone)
date_format = "%Y/%m/%d %H:%M",
-- Callback after review start completes (all data fetched)
-- Receives: { pr_number, base_ref, head_ref, pr_url }
on_review_start = nil,
})Comment input windows support @user, #issue/PR, and _commit completion.
| Trigger | Completes | Source |
|---|---|---|
@ |
GitHub collaborators | GitHub API (cached 5 min) |
# |
Issues and PRs | GitHub API (cached 5 min) |
_ |
PR commit hashes | Local cache (no API call) |
Commit completion shows entries in [n/m] <sha> <message> (<author>) format, matching the scope picker display. Selecting a commit inserts its short SHA.
Add the provider to your blink.cmp config:
sources = {
default = { "lsp", "path", "buffer", "snippets", "fude" },
providers = {
fude = {
name = "fude",
module = "fude.completion.blink",
score_offset = 50,
async = true,
},
},
},Register the source in your config:
require("cmp").register_source("fude", require("fude.completion.cmp").new())Then add { name = "fude" } to your nvim-cmp sources.
MIT
