๐ฟ gh
November 8, 2025 ยท View on GitHub
A modern GitHub CLI integration for Neovim that brings GitHub issues and pull requests directly into your editor.
โจ Features
- ๐ Browse and search GitHub issues and pull requests with fuzzy finding
- ๐ View full issue/PR details including comments, reactions, and status checks
- ๐ Perform GitHub actions directly from Neovim:
- Comment on issues and PRs
- Close, reopen, edit, and merge PRs
- Add reactions and labels
- Review PRs (approve, request changes, comment)
- Checkout PR branches locally
- View PR diffs with syntax highlighting
- โจ๏ธ Customizable keymaps for common GitHub operations
- ๐จ Beautiful syntax highlighting using Treesitter
- ๐ Open issues/PRs in your web browser
- ๐ Yank URLs to clipboard
- ๐ฒ Built on top of the powerful Snacks picker
โก๏ธ Requirements
- GitHub CLI (
gh) - must be installed and authenticated - Snacks picker enabled
๐ Recommended Setup
{
"folke/snacks.nvim",
opts = {
gh = {
-- your gh configuration comes here
-- or leave it empty to use the default settings
-- refer to the configuration section below
},
picker = {
sources = {
gh_issue = {
-- your gh_issue picker configuration comes here
-- or leave it empty to use the default settings
},
gh_pr = {
-- your gh_pr picker configuration comes here
-- or leave it empty to use the default settings
}
}
},
},
keys = {
{ "<leader>gi", function() Snacks.picker.gh_issue() end, desc = "GitHub Issues (open)" },
{ "<leader>gI", function() Snacks.picker.gh_issue({ state = "all" }) end, desc = "GitHub Issues (all)" },
{ "<leader>gp", function() Snacks.picker.gh_pr() end, desc = "GitHub Pull Requests (open)" },
{ "<leader>gP", function() Snacks.picker.gh_pr({ state = "all" }) end, desc = "GitHub Pull Requests (all)" },
},
}
๐ Usage
-- Browse open issues
Snacks.picker.gh_issue()
-- Browse all issues (including closed)
Snacks.picker.gh_issue({ state = "all" })
-- Browse open pull requests
Snacks.picker.gh_pr()
-- Browse all pull requests
Snacks.picker.gh_pr({ state = "all" })
-- View PR diff
Snacks.picker.gh_diff({ pr = 123 })
-- Open issue/PR in buffer
Snacks.gh.open({ type = "issue", number = 123, repo = "owner/repo" })
Available Actions
When viewing an issue or PR in the picker, press <cr> to show available actions:
Snacks.gh makes extensive use of Snacks.scratch for editing comments and descriptions.
Common Actions:
- Open in buffer - View full details with comments
- Open in browser - Open in GitHub web UI
- Add comment - Add a new comment
- Add reaction - React with emoji
- Add/Remove labels - Manage labels
- Close/Reopen - Change issue/PR state
- Edit - Edit title and body
- Yank URL - Copy URL to clipboard
Pull Request/Issue Specific:
- View diff - Show changed files with syntax highlighting
- Checkout - Checkout PR branch locally
- Merge - Merge, squash, or rebase and merge
- Review - Approve, request changes, or comment
- Mark as draft/ready - Change draft status
- and more...
GitHub Buffers
When you open an issue or PR in a buffer, you get a beautiful rendered view with:
- Metadata - Status, author, dates, labels, reactions, and assignees
- Description - Full issue/PR body with markdown rendering
- Comments - All comments with author info and timestamps
- Status Checks - PR status checks and CI results (for PRs)
- Syntax Highlighting - Full Treesitter support for markdown
- Folding - Foldable sections for comments and metadata
Default Keymaps in GitHub Buffers:
| Key | Action | Description |
|---|---|---|
<cr> | Select Action | Show available actions menu |
i | Edit | Edit issue/PR title and body |
a | Add Comment | Add a new comment |
c | Close | Close the issue/PR |
o | Reopen | Reopen a closed issue/PR |
See the config section to customize these keymaps.
๐ฆ Setup
-- lazy.nvim
{
"folke/snacks.nvim",
---@type snacks.Config
opts = {
gh = {
-- your gh configuration comes here
-- or leave it empty to use the default settings
-- refer to the configuration section below
}
}
}
โ๏ธ Config
---@class snacks.gh.Config
{
--- Keymaps for GitHub buffers
---@type table<string, snacks.gh.Keymap|false>?
keys = {
select = { "<cr>", "gh_actions", desc = "Select Action" },
edit = { "i" , "gh_edit" , desc = "Edit" },
comment = { "a" , "gh_comment", desc = "Add Comment" },
close = { "c" , "gh_close" , desc = "Close" },
reopen = { "o" , "gh_reopen" , desc = "Reopen" },
},
---@type vim.wo|{}
wo = {
breakindent = true,
wrap = true,
showbreak = "",
linebreak = true,
number = false,
relativenumber = false,
foldexpr = "v:lua.vim.treesitter.foldexpr()",
foldmethod = "expr",
concealcursor = "n",
conceallevel = 2,
list = false,
winhighlight = Snacks.util.winhl({
Normal = "SnacksGhNormal",
NormalFloat = "SnacksGhNormalFloat",
FloatBorder = "SnacksGhBorder",
FloatTitle = "SnacksGhTitle",
FloatFooter = "SnacksGhFooter",
}),
},
---@type vim.bo|{}
bo = {},
diff = {
min = 4, -- minimum number of lines changed to show diff
wrap = 80, -- wrap diff lines at this length
},
scratch = {
height = 15, -- height of scratch window
},
icons = {
logo = "๏ ",
user= "๏ฝ ",
checkmark = "๏ฎ ",
crossmark = "๎ฉถ ",
block = "โ ",
file = "๏ฅ ",
checks = {
pending = "๏บ ",
success = "๏ฎ ",
failure = "๏ง",
skipped = "๎ชฝ ",
},
issue = {
open = "๏ ",
completed = "๏ ",
other = "๎ชฝ "
},
pr = {
open = "๎ฉค ",
closed = "๎ฏ ",
merged = "๏ ",
draft = "๎ฏ ",
other = "๎ฏ ",
},
review = {
approved = "๏ฎ ",
changes_requested = "๎ญ ",
commented = "๏ ",
dismissed = "๏ฑ ",
pending = "๏บ ",
},
merge_status = {
clean = "๏ฎ ",
dirty = "๎ฉถ ",
blocked = "๎ชฝ ",
unstable = "๏ฑ "
},
reactions = {
thumbs_up = "๐",
thumbs_down = "๐",
eyes = "๐",
confused = "๐",
heart = "โค๏ธ",
hooray = "๐",
laugh = "๐",
rocket = "๐",
},
},
}
๐ Types
---@alias snacks.gh.Keymap.fn fun(item:snacks.picker.gh.Item, buf:snacks.gh.Buf)
---@class snacks.gh.Keymap: vim.keymap.set.Opts
---@field [1] string lhs
---@field [2] string|snacks.gh.Keymap.fn rhs
---@field mode? string|string[] defaults to `n`
๐ฆ Module
---@class snacks.gh
---@field api snacks.gh.api
---@field item snacks.picker.gh.Item
Snacks.gh = {}
Snacks.gh.issue()
---@param opts? snacks.picker.gh.issue.Config
Snacks.gh.issue(opts)
Snacks.gh.pr()
---@param opts? snacks.picker.gh.pr.Config
Snacks.gh.pr(opts)