Skip to content

Instantly share code, notes, and snippets.

@Zejnilovic
Created January 5, 2026 10:34
Show Gist options
  • Select an option

  • Save Zejnilovic/0a4a828bb57febeea37f09b085b17c91 to your computer and use it in GitHub Desktop.

Select an option

Save Zejnilovic/0a4a828bb57febeea37f09b085b17c91 to your computer and use it in GitHub Desktop.
A small Neovim helper that opens GitHub references under the cursor (or visual selection) in a browser. This is editor-native. No plugins, no API calls, no Tree-sitter, no LSP. Just Lua.
--[[
github_ref.lua
A small, editor-native Neovim helper for opening GitHub references found
under the cursor or in a visual selection.
This file intentionally avoids plugins, GitHub API calls, Tree-sitter,
LSP integration, or background jobs. It is designed to be fast, predictable,
and usable anywhere text appears (code, documentation, comments, or notes).
Usage
-----
Press <leader>gb in normal or visual mode on any of the following patterns:
`org/repo` - Opens the repository
`org/repo@ref` - Opens a branch, tag, or commit
(shorhand sha and full branch names with slashes are supported)
`org/repo#123` - Opens issue #123 (or PR via GitHub redirect)
`#123` - Resolves the issue via the current git remote and opens it
The mapping works in any buffer and does not depend on filetype.
Visual mode
-----------
When a visual selection is active, the first valid GitHub reference found
in the selection is used. If no selection is active, the WORD under the
cursor is used instead.
Configuration
-------------
By default, links are opened against https://github.com.
For GitHub Enterprise or self-hosted GitHub instances:
require("config.github_ref").setup({
github_base_url = "https://github.mycompany.com",
})
Git remotes using HTTPS and SSH are supported.
Design notes
------------
- No GitHub API usage (issues vs PRs are not distinguished)
- No network requests
- No assumptions about language or filetype
- Deterministic behavior over heuristics
- NB!: Pull requests are opened via /issues/<id> and allowed to redirect naturally
This file is intended to be easy to copy, modify, or later extract into a
proper Neovim plugin if desired.
]]
local M = {}
M.config = {
github_base_url = "https://github.com",
}
local function open_url(url)
if vim.fn.has("mac") == 1 then
vim.fn.jobstart({ "open", url }, { detach = true })
elseif vim.fn.has("unix") == 1 then
vim.fn.jobstart({ "xdg-open", url }, { detach = true })
elseif vim.fn.has("win32") == 1 then
vim.fn.jobstart({ "cmd.exe", "/c", "start", url }, { detach = true })
end
end
local function get_git_remote()
local handle = io.popen("git config --get remote.origin.url 2>/dev/null")
if not handle then return nil end
local result = handle:read("*a")
handle:close()
return result and result:gsub("%s+", "") or nil
end
local function parse_remote(remote)
if not remote then return nil end
-- git@github.com:org/repo.git
local path = remote:match("^git@[^:]+:(.+)$")
if path then
return path:gsub("%.git$", "")
end
-- ssh://git@github.com/org/repo.git
local path2 = remote:match("^ssh://git@[^/]+/(.+)$")
if path2 then
return path2:gsub("%.git$", "")
end
-- https://github.com/org/repo.git
local path3 = remote:match("^https?://[^/]+/(.+)$")
if path3 then
return path3:gsub("%.git$", "")
end
return nil
end
local function get_visual_selection()
local _, ls, cs = unpack(vim.fn.getpos("'<"))
local _, le, ce = unpack(vim.fn.getpos("'>"))
if ls == 0 then return nil end
local lines = vim.api.nvim_buf_get_lines(0, ls - 1, le, false)
if #lines == 0 then return nil end
lines[#lines] = string.sub(lines[#lines], 1, ce)
lines[1] = string.sub(lines[1], cs)
return table.concat(lines, " ")
end
local function get_token_under_cursor()
local token = vim.fn.expand("<cWORD>")
token = token:gsub("^[%(%[%{\"']+", "")
token = token:gsub("[%]%)}\"',%.]+$", "")
return token
end
local function parse_github(text)
local base = M.config.github_base_url
-- org/repo#123
local org, repo, issue = text:match("([%w%-_.]+)/([%w%-_.]+)#(%d+)")
if org then
return base .. "/" .. org .. "/" .. repo .. "/issues/" .. issue
end
-- #123 (resolve via git remote)
local issue_only = text:match("^#(%d+)$")
if issue_only then
local remote = get_git_remote()
local repo_path = parse_remote(remote)
if not repo_path then
vim.notify("Cannot resolve #"..issue_only.." (no git remote)", vim.log.levels.ERROR)
return nil
end
return base .. "/" .. repo_path .. "/issues/" .. issue_only
end
-- org/repo@ref (ref may contain slashes)
local org2, repo2, ref = text:match("([%w%-_.]+)/([%w%-_.]+)@(.+)")
if org2 then
return base .. "/" .. org2 .. "/" .. repo2 .. "/tree/" .. ref
end
-- org/repo
local org3, repo3 = text:match("([%w%-_.]+)/([%w%-_.]+)")
if org3 then
return base .. "/" .. org3 .. "/" .. repo3
end
return nil
end
local function open_github_ref()
local text = nil
if vim.fn.mode():match("[vV]") then
text = get_visual_selection()
end
if not text or text == "" then
text = get_token_under_cursor()
end
local url = parse_github(text)
if not url then
vim.notify("No GitHub reference found", vim.log.levels.WARN)
return
end
open_url(url)
end
-- =========================
-- Keymaps
-- =========================
vim.keymap.set({ "n", "v" }, "<leader>gb", open_github_ref, {
desc = "Open GitHub reference",
})
-- =========================
-- Optional: public setup
-- =========================
function M.setup(opts)
M.config = vim.tbl_deep_extend("force", M.config, opts or {})
end
return M
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment