local M = {} local config = require("gitea.config") local api = require("gitea.api") local auth = require("gitea.auth") -- Utility: parse "owner/repo" from the remote URL local function detect_owner_repo() local cmd = "git config remote.origin.url" local url = vim.fn.systemlist(cmd)[1] if vim.v.shell_error ~= 0 or not url or url == "" then return nil, nil end -- Remove trailing ".git" url = url:gsub("%.git$", "") -- Try to parse: e.g. https://my.gitea.com/owner/repo local _, after = url:match("^(https?://)(.*)$") if after then -- after might be "my.gitea.com/owner/repo" -- let's capture the part after the first slash local path = after:match("[^/]+/(.*)$") -- skip domain if not path then return nil, nil end local owner, repo = path:match("^(.-)/(.*)$") return owner, repo end -- SSH style: git@my.gitea.com:owner/repo local user_host, path = url:match("^(.-):(.*)$") if user_host and path then -- user_host might be "git@my.gitea.com" local owner, repo = path:match("^(.-)/(.*)$") return owner, repo end return nil, nil end ------------------------------- -- Subcommands ------------------------------- local subcommands = {} subcommands.issue = { list = function(args) local owner, repo = detect_owner_repo() if not owner or not repo then vim.notify("[gitea.nvim] Could not detect owner/repo.", vim.log.levels.ERROR) return end local issues, status = api.list_issues(owner, repo, {}) if not issues then vim.notify(string.format("[gitea.nvim] Error fetching issues (HTTP %s).", status or "?"), vim.log.levels.ERROR) return end -- For now, print to message area print("Issues for " .. owner .. "/" .. repo .. ":") for _, issue in ipairs(issues) do print(string.format("#%d %s", issue.number, issue.title)) end end, create = function(args) local owner, repo = detect_owner_repo() if not owner or not repo then vim.notify("[gitea.nvim] Could not detect owner/repo.", vim.log.levels.ERROR) return end -- We'll open a scratch buffer for user to fill in Title/Body -- Then upon writing, we create the issue. -- For brevity, let's do a simpler approach: ask input in command line local title = vim.fn.input("Issue title: ") local body = vim.fn.input("Issue body: ") local created, status = api.create_issue(owner, repo, { title = title, body = body, }) if created then vim.notify("[gitea.nvim] Created issue #" .. created.number .. "!") else vim.notify(string.format("[gitea.nvim] Failed to create issue (HTTP %s).", status or "?"), vim.log.levels.ERROR) end end, edit = function(args) local number = tonumber(args[1]) if not number then vim.notify("[gitea.nvim] Usage: :Gitea issue edit ", vim.log.levels.ERROR) return end local owner, repo = detect_owner_repo() if not owner or not repo then vim.notify("[gitea.nvim] Could not detect owner/repo.", vim.log.levels.ERROR) return end -- For a minimal approach: fetch issue, then open in a scratch buffer -- with title/body. On write, update it. We'll do a simple approach: local issue_data, status = api.get_issue(owner, repo, number) if not issue_data then vim.notify(string.format("[gitea.nvim] Error retrieving issue (HTTP %s).", status or "?"), vim.log.levels.ERROR) return end -- Create a new scratch buffer local buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_option(buf, "filetype", "markdown") vim.api.nvim_buf_set_name(buf, "gitea_issue_"..number) local lines = { "# Issue #"..issue_data.number, "Title: "..(issue_data.title or ""), "", issue_data.body or "", } vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) -- Attach an autocmd on BufWriteCmd to push changes vim.api.nvim_create_autocmd("BufWriteCmd", { buffer = buf, callback = function() local new_lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false) if #new_lines < 2 then return end local new_title = new_lines[1]:gsub("^Title:%s*", "") local new_body = table.concat({unpack(new_lines, 3)}, "\n") local updated, st = api.edit_issue(owner, repo, number, { title = new_title, body = new_body, }) if updated then vim.notify("[gitea.nvim] Issue updated successfully!") else vim.notify(string.format("[gitea.nvim] Failed to update issue (HTTP %s).", st or "?"), vim.log.levels.ERROR) end end }) vim.api.nvim_set_current_buf(buf) end, close = function(args) local number = tonumber(args[1]) if not number then vim.notify("[gitea.nvim] Usage: :Gitea issue close ", vim.log.levels.ERROR) return end local owner, repo = detect_owner_repo() local res, status = api.close_issue(owner, repo, number) if res then vim.notify("[gitea.nvim] Issue #"..number.." closed.") else vim.notify(string.format("[gitea.nvim] Failed to close issue (HTTP %s).", status or "?"), vim.log.levels.ERROR) end end, reopen = function(args) local number = tonumber(args[1]) if not number then vim.notify("[gitea.nvim] Usage: :Gitea issue reopen ", vim.log.levels.ERROR) return end local owner, repo = detect_owner_repo() local res, status = api.reopen_issue(owner, repo, number) if res then vim.notify("[gitea.nvim] Issue #"..number.." reopened.") else vim.notify(string.format("[gitea.nvim] Failed to reopen issue (HTTP %s).", status or "?"), vim.log.levels.ERROR) end end, comment = function(args) local number = tonumber(args[1]) if not number then vim.notify("[gitea.nvim] Usage: :Gitea issue comment ", vim.log.levels.ERROR) return end local owner, repo = detect_owner_repo() local body = vim.fn.input("Comment: ") if not body or body == "" then return end local res, status = api.comment_issue(owner, repo, number, body) if res then vim.notify("[gitea.nvim] Comment added to issue #"..number..".") else vim.notify(string.format("[gitea.nvim] Failed to add comment (HTTP %s).", status or "?"), vim.log.levels.ERROR) end end, } subcommands.pr = { list = function(args) local owner, repo = detect_owner_repo() if not owner or not repo then vim.notify("[gitea.nvim] Could not detect owner/repo.", vim.log.levels.ERROR) return end local prs, status = api.list_pull_requests(owner, repo, {}) if not prs then vim.notify(string.format("[gitea.nvim] Error fetching PRs (HTTP %s).", status or "?"), vim.log.levels.ERROR) return end print("Pull Requests for " .. owner .. "/" .. repo .. ":") for _, pr in ipairs(prs) do print(string.format("#%d %s (state=%s)", pr.number, pr.title, pr.state)) end end, create = function(args) local owner, repo = detect_owner_repo() if not owner or not repo then vim.notify("[gitea.nvim] Could not detect owner/repo.", vim.log.levels.ERROR) return end local title = vim.fn.input("PR title: ") local body = vim.fn.input("PR body: ") local base = vim.fn.input("Base branch (e.g. main): ") local head = vim.fn.input("Head branch (e.g. feature/my-branch): ") local created, status = api.create_pull_request(owner, repo, { title = title, body = body, base = base, head = head, }) if created then vim.notify("[gitea.nvim] Created PR #" .. created.number .. ".") else vim.notify(string.format("[gitea.nvim] Failed to create PR (HTTP %s).", status or "?"), vim.log.levels.ERROR) end end, edit = function(args) local number = tonumber(args[1]) if not number then vim.notify("[gitea.nvim] Usage: :Gitea pr edit ", vim.log.levels.ERROR) return end local owner, repo = detect_owner_repo() local pr_data, status = api.get_pull_request(owner, repo, number) if not pr_data then vim.notify(string.format("[gitea.nvim] Could not get PR (HTTP %s).", status or "?"), vim.log.levels.ERROR) return end -- Similar scratch buffer approach local buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_option(buf, "filetype", "markdown") vim.api.nvim_buf_set_name(buf, "gitea_pr_"..number) local lines = { "# PR #"..pr_data.number, "Title: "..(pr_data.title or ""), "", pr_data.body or "", } vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) vim.api.nvim_create_autocmd("BufWriteCmd", { buffer = buf, callback = function() local new_lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false) if #new_lines < 2 then return end local new_title = new_lines[1]:gsub("^Title:%s*", "") local new_body = table.concat({unpack(new_lines, 3)}, "\n") local updated, st = api.edit_pull_request(owner, repo, number, { title = new_title, body = new_body, }) if updated then vim.notify("[gitea.nvim] PR updated successfully!") else vim.notify(string.format("[gitea.nvim] Failed to update PR (HTTP %s).", st or "?"), vim.log.levels.ERROR) end end }) vim.api.nvim_set_current_buf(buf) end, merge = function(args) local number = tonumber(args[1]) if not number then vim.notify("[gitea.nvim] Usage: :Gitea pr merge [merge|rebase|squash] [delete|nodelete]", vim.log.levels.ERROR) return end local merge_style = args[2] or "merge" -- "merge", "rebase", "squash" local delete_arg = args[3] or "nodelete" local owner, repo = detect_owner_repo() local merged, status = api.merge_pull_request(owner, repo, number, merge_style) if merged then vim.notify(string.format("[gitea.nvim] PR #%d merged via %s.", number, merge_style)) if delete_arg == "delete" then -- The Gitea API doesn't directly delete the branch here, -- but you can do so with the Git CLI or Gitea API call. -- Deleting the branch example: -- os.execute(string.format("git push origin --delete ")) vim.notify("[gitea.nvim] Branch deletion not implemented. Use `git push origin --delete `.") end else vim.notify(string.format("[gitea.nvim] Failed to merge PR #%d (HTTP %s).", number, status or "?"), vim.log.levels.ERROR) end end, close = function(args) local number = tonumber(args[1]) if not number then vim.notify("[gitea.nvim] Usage: :Gitea pr close ", vim.log.levels.ERROR) return end local owner, repo = detect_owner_repo() local res, status = api.close_pull_request(owner, repo, number) if res then vim.notify("[gitea.nvim] PR #"..number.." closed.") else vim.notify(string.format("[gitea.nvim] Failed to close PR (HTTP %s).", status or "?"), vim.log.levels.ERROR) end end, reopen = function(args) local number = tonumber(args[1]) if not number then vim.notify("[gitea.nvim] Usage: :Gitea pr reopen ", vim.log.levels.ERROR) return end local owner, repo = detect_owner_repo() local res, status = api.reopen_pull_request(owner, repo, number) if res then vim.notify("[gitea.nvim] PR #"..number.." reopened.") else vim.notify(string.format("[gitea.nvim] Failed to reopen PR (HTTP %s).", status or "?"), vim.log.levels.ERROR) end end, comment = function(args) local number = tonumber(args[1]) if not number then vim.notify("[gitea.nvim] Usage: :Gitea pr comment ", vim.log.levels.ERROR) return end local owner, repo = detect_owner_repo() local body = vim.fn.input("Comment: ") if not body or body == "" then return end local res, status = api.comment_pull_request(owner, repo, number, body) if res then vim.notify("[gitea.nvim] Comment added to PR #"..number..".") else vim.notify(string.format("[gitea.nvim] Failed to add comment (HTTP %s).", status or "?"), vim.log.levels.ERROR) end end, } -- Expand with other objects (e.g. 'repo', 'review', etc.) if needed. ------------------------------- -- Command dispatcher ------------------------------- function M.register() vim.api.nvim_create_user_command("Gitea", function(cmd_opts) -- Ensure token is loaded (in case user runs a command after startup) local _ = require("gitea.auth").ensure_token() local args = {} for w in string.gmatch(cmd_opts.args, "%S+") do table.insert(args, w) end local object = args[1] -- "issue", "pr", etc. local action = args[2] -- "list", "create", etc. if not object then print("Usage: :Gitea [args]") print("Examples:") print(" :Gitea issue list") print(" :Gitea pr list") return end if not subcommands[object] then vim.notify("[gitea.nvim] Unknown object: "..object, vim.log.levels.ERROR) return end if not action then vim.notify("[gitea.nvim] Please specify an action for "..object, vim.log.levels.ERROR) return end local fn = subcommands[object][action] if not fn then vim.notify(string.format("[gitea.nvim] Unknown action '%s' for object '%s'", action, object), vim.log.levels.ERROR) return end -- remove first two tokens table.remove(args, 1) table.remove(args, 1) fn(args) end, { nargs = "*", complete = function(arg_lead, cmd_line, cursor_pos) -- Very basic completion local parts = vim.split(cmd_line, "%s+") if #parts == 2 then -- user typed: :Gitea local objs = {} for k, _ in pairs(subcommands) do if k:match("^"..arg_lead) then table.insert(objs, k) end end return objs elseif #parts == 3 then local object = parts[2] local acts = {} if subcommands[object] then for k, _ in pairs(subcommands[object]) do if k:match("^"..arg_lead) then table.insert(acts, k) end end end return acts else return {} end end }) end return M