-- lua/gpr_gh.lua -- Async PR opener using `vim.system` + GitHub CLI. -- 1) blame current line -> SHA -- 2) read commit subject -- 3) extract PR number "(#1234)" -- 4) `gh pr view --web` local M = {} -- Safe notify in fast-event contexts local notify = vim.schedule_wrap(function(msg, level) vim.notify(msg, level or vim.log.levels.INFO, { title = "GPR" }) end) local function run(cmd, opts, cb) opts = opts or {} opts.text = true vim.system(cmd, opts, function(obj) if obj.code ~= 0 then cb(nil, obj.stderr ~= "" and obj.stderr or obj.stdout) else cb(obj.stdout or "", nil) end end) end local function blame_sha_for_line(repo_cwd, cb) local file = vim.api.nvim_buf_get_name(0) if file == "" then return cb(nil, "Buffer has no filename") end -- Use Lua dirname to avoid vim.fn in fast context local file_dir = vim.fs.dirname(file) or repo_cwd local lnum = vim.api.nvim_win_get_cursor(0)[1] run({ "git", "blame", "-L", ("%d,%d"):format(lnum, lnum), "--porcelain", "--", file }, { cwd = file_dir }, function(out, err) if err then return cb(nil, "git blame failed: " .. err) end local sha = out:match("^([0-9a-fA-F]+)") if not sha then return cb(nil, "Could not parse a commit for this line") end cb(sha, nil) end) end local function commit_subject(repo_cwd, sha, cb) run({ "git", "log", "-n", "1", "--format=%s", sha }, { cwd = repo_cwd }, function(out, err) if err then return cb(nil, "git log failed: " .. err) end cb(vim.trim(out), nil) end) end local function extract_pr_number(subject) return subject:match("%(#(%d+)%)") -- Title (#1234) or subject:match("PR%s*#(%d+)") -- PR #1234 or subject:match("#(%d+)") -- #1234 anywhere end function M.open_pr_for_line() if vim.fn.executable("gh") == 0 then return notify("GitHub CLI (gh) not found", vim.log.levels.ERROR) end -- Capture CWD *before* async (pure Lua; safe in fast events) local repo_cwd = vim.uv.cwd() -- alias: vim.loop.cwd() blame_sha_for_line(repo_cwd, function(sha, e1) if not sha then return notify(e1, vim.log.levels.WARN) end commit_subject(repo_cwd, sha, function(subj, e2) if not subj then return notify(e2, vim.log.levels.WARN) end local pr = extract_pr_number(subj) if not pr then return notify("No PR number like (#1234) in: " .. subj, vim.log.levels.WARN) end -- Fire-and-forget: open PR in browser via gh run({ "gh", "pr", "view", pr, "--web" }, { cwd = repo_cwd }, function(_, err) if err and not err:match("^warning:") then notify("Failed to open PR #" .. pr .. ": " .. err, vim.log.levels.WARN) else notify("Opening PR #" .. pr) end end) end) end) end return M