Back to Snacks Nvim

🍿 gh

docs/gh.md

2.31.07.9 KB
Original Source

🍿 gh

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

lua
{
  "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

lua
-- 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:

KeyActionDescription
<cr>Select ActionShow available actions menu
iEditEdit issue/PR title and body
aAdd CommentAdd a new comment
cCloseClose the issue/PR
oReopenReopen a closed issue/PR

See the config section to customize these keymaps.

<!-- docgen -->

📩 Setup

lua
-- 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

lua
---@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

lua
---@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

lua
---@class snacks.gh
---@field api snacks.gh.api
---@field item snacks.picker.gh.Item
Snacks.gh = {}

Snacks.gh.issue()

lua
---@param opts? snacks.picker.gh.issue.Config
Snacks.gh.issue(opts)

Snacks.gh.pr()

lua
---@param opts? snacks.picker.gh.pr.Config
Snacks.gh.pr(opts)