diff --git a/lua/snacks/gitbrowse.lua b/lua/snacks/gitbrowse.lua index 455f8baa..00946e02 100644 --- a/lua/snacks/gitbrowse.lua +++ b/lua/snacks/gitbrowse.lua @@ -18,9 +18,11 @@ local defaults = { end vim.ui.open(url) end, + ---@type "repo" | "branch" | "file" + what = "file", -- what to open. not all remotes support all types -- patterns to transform remotes to an actual URL -- stylua: ignore - patterns = { + remote_patterns = { { "^(https?://.*)%.git$" , "%1" }, { "^git@(.+):(.+)%.git$" , "https://%1/%2" }, { "^git@(.+):(.+)$" , "https://%1/%2" }, @@ -34,48 +36,84 @@ local defaults = { { ":%d+" , "" }, { "%.git$" , "" }, }, + url_patterns = { + ["github.com"] = { + branch = "/tree/{branch}", + file = "/blob/{branch}/{file}#L{line}", + }, + ["gitlab.com"] = { + branch = "/-/tree/{branch}", + file = "/-/blob/{branch}/{file}#L{line}", + }, + }, } ---@private ---@param remote string ---@param opts? snacks.gitbrowse.Config -function M.get_url(remote, opts) +function M.get_repo(remote, opts) opts = Snacks.config.get("gitbrowse", defaults, opts) local ret = remote - for _, pattern in ipairs(opts.patterns) do - ret = ret:gsub(pattern[1], pattern[2]) + for _, pattern in ipairs(opts.remote_patterns) do + ret = ret:gsub(pattern[1], pattern[2]) --[[@as string]] end return ret:find("https://") == 1 and ret or ("https://%s"):format(ret) end +---@param repo string +---@param opts? snacks.gitbrowse.Config +function M.get_url(repo, opts) + opts = Snacks.config.get("gitbrowse", defaults, opts) + for remote, patterns in pairs(opts.url_patterns) do + if repo:find(remote) then + return patterns[opts.what] and (repo .. patterns[opts.what]) or repo + end + end + return repo +end + +---@param cmd string[] +---@param err string +local function system(cmd, err) + local proc = vim.system(cmd, { text = true }):wait() + if proc.code ~= 0 then + Snacks.notify.error({ err, proc.stderr, proc.stdout }, { title = "Git Browse" }) + error(err) + end + return vim.split(vim.trim(proc.stdout), "\n") +end + ---@param opts? snacks.gitbrowse.Config function M.open(opts) - opts = Snacks.config.get("gitbrowse", defaults, opts) - local proc = vim.system({ "git", "remote", "-v" }, { text = true }):wait() - if proc.code ~= 0 then - return Snacks.notify.error("Failed to get git remotes", { title = "Git Browse" }) - end - local lines = vim.split(proc.stdout, "\n") + pcall(M._open, opts) -- errors are handled with notifications +end - proc = vim.system({ "git", "rev-parse", "--abbrev-ref", "HEAD" }):wait() - if proc.code ~= 0 then - return Snacks.notify.error("Failed to get current branch", { title = "Git Browse" }) - end - local branch = proc.stdout:gsub("\n", "") +---@param opts? snacks.gitbrowse.Config +function M._open(opts) + opts = Snacks.config.get("gitbrowse", defaults, opts) + local file = vim.api.nvim_buf_get_name(0) ---@type string? + file = file and (vim.uv.fs_stat(file) or {}).type == "file" and vim.fs.normalize(file) or nil + local cwd = file and vim.fn.fnamemodify(file, ":h") or vim.fn.getcwd() + local fields = { + branch = system({ "git", "-C", cwd, "rev-parse", "--abbrev-ref", "HEAD" }, "Failed to get current branch")[1], + file = file and system({ "git", "-C", cwd, "ls-files", "--full-name", file }, "Failed to get git file path")[1], + line = file and vim.fn.line("."), + } + opts.what = opts.what == "file" and not fields.file and "branch" or opts.what + opts.what = opts.what == "branch" and not fields.branch and "repo" or opts.what local remotes = {} ---@type {name:string, url:string}[] - for _, line in ipairs(lines) do + for _, line in ipairs(system({ "git", "-C", cwd, "remote", "-v" }, "Failed to get git remotes")) do local name, remote = line:match("(%S+)%s+(%S+)%s+%(fetch%)") if name and remote then - local url = M.get_url(remote, opts) - if url:find("github") and branch and branch ~= "master" and branch ~= "main" then - url = ("%s/tree/%s"):format(url, branch) - end - if url then + local repo = M.get_repo(remote, opts) + if repo then table.insert(remotes, { name = name, - url = url, + url = M.get_url(repo, opts):gsub("(%b{})", function(key) + return fields[key:sub(2, -2)] or key + end), }) end end diff --git a/tests/gitbrowse_spec.lua b/tests/gitbrowse_spec.lua index f87658ba..6aeeaf5a 100644 --- a/tests/gitbrowse_spec.lua +++ b/tests/gitbrowse_spec.lua @@ -35,7 +35,7 @@ local git_remotes_cases = { describe("util.lazygit", function() for remote, expected in pairs(git_remotes_cases) do it("should parse git remote " .. remote, function() - local url = gitbrowse.get_url(remote) + local url = gitbrowse.get_repo(remote) assert.are.equal(expected, url) end) end