local M = {} local config = require("gitea.config") local api = require("gitea.api") local auth = require("gitea.auth") local highlights = require("gitea.highlights") -- we still require highlights ------------------------------------------------------------------------------ -- DETECT OWNER/REPO ------------------------------------------------------------------------------ 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 url = url:gsub("%.git$", "") local _, after = url:match("^(https?://)(.*)$") if after then local path = after:match("[^/]+/(.*)$") if not path then return nil, nil end local owner, repo = path:match("^(.-)/(.*)$") return owner, repo end local user_host, path = url:match("^(.-):(.*)$") if user_host and path then local owner, repo = path:match("^(.-)/(.*)$") return owner, repo end return nil, nil end ------------------------------------------------------------------------------ -- BUFFER METADATA ------------------------------------------------------------------------------ local function create_issue_buffer_metadata(issue) return { issue_number = issue.number, title_start = nil, title_end = nil, body_start = nil, body_end = nil, comments = {}, -- each = { id, start_line, end_line } new_comment_start = nil, } end ------------------------------------------------------------------------------ -- APPLY HIGHLIGHTS ------------------------------------------------------------------------------ local function apply_issue_highlights(bufnr, meta) -- The first line in the file is line 0 in Lua’s API, so we subtract 1 -- from meta.* to convert to 0-based indexing. ------------------------------------------------------------------------ -- Lines #0 & #1 hold meta info (“# Gitea Issue #...”, “# STATE: ...”) ------------------------------------------------------------------------ vim.api.nvim_buf_add_highlight(bufnr, 0, "GiteaIssueMeta", 0, 0, -1) vim.api.nvim_buf_add_highlight(bufnr, 0, "GiteaIssueMeta", 1, 0, -1) ------------------------------------------------------------------------ -- Title ------------------------------------------------------------------------ local title_line_0 = meta.title_start - 1 vim.api.nvim_buf_add_highlight(bufnr, 0, "GiteaIssueTitle", title_line_0, 0, -1) ------------------------------------------------------------------------ -- Body lines ------------------------------------------------------------------------ -- meta.body_start .. meta.body_end (inclusive, 1-based) for line = meta.body_start, meta.body_end do local line_0 = line - 1 vim.api.nvim_buf_add_highlight(bufnr, 0, "GiteaCommentBody", line_0, 0, -1) end ------------------------------------------------------------------------ -- Comment headings & bodies ------------------------------------------------------------------------ for _, c in ipairs(meta.comments) do -- The heading line has “COMMENT #123 by user” local heading_line_0 = c.start_line - 1 vim.api.nvim_buf_add_highlight(bufnr, 0, "GiteaCommentHeading", heading_line_0, 0, -1) -- The actual comment text is lines [heading_line_0+1 .. c.end_line-1] for text_line = (c.start_line + 1), c.end_line do local text_line_0 = text_line - 1 vim.api.nvim_buf_add_highlight(bufnr, 0, "GiteaCommentBody", text_line_0, 0, -1) end end end ------------------------------------------------------------------------------ -- RENDER ISSUE INTO BUFFER ------------------------------------------------------------------------------ local function render_issue_into_buf(bufnr, issue, comments) local lines = {} table.insert(lines, string.format("# Gitea Issue #%d: %s", issue.number, issue.title)) table.insert(lines, "# STATE: " .. (issue.state or "unknown")) table.insert(lines, "# Edit the title below, then the body, then any comments.") table.insert(lines, "# On save (:w), changes to the title/body will be updated; changes to comment lines will be updated, and new text at the bottom becomes a new comment.") table.insert(lines, "") local meta = create_issue_buffer_metadata(issue) meta.title_start = #lines + 1 table.insert(lines, issue.title or "") meta.title_end = #lines table.insert(lines, "") meta.body_start = #lines + 1 if issue.body then local body_lines = vim.split(issue.body, "\n", true) if #body_lines == 0 then table.insert(lines, "") else for _, l in ipairs(body_lines) do table.insert(lines, l) end end else table.insert(lines, "No issue description.") end meta.body_end = #lines table.insert(lines, "") table.insert(lines, "# --- Comments ---") local comment_meta = {} for _, c in ipairs(comments) do table.insert(lines, "") local start_line = #lines + 1 local author = c.user and (c.user.login or c.user.username) or "?" table.insert(lines, string.format("COMMENT #%d by %s", c.id, author)) local c_body_lines = vim.split(c.body or "", "\n", true) if #c_body_lines == 0 then table.insert(lines, "") else for _, cb in ipairs(c_body_lines) do table.insert(lines, cb) end end local end_line = #lines table.insert(comment_meta, { id = c.id, start_line = start_line, end_line = end_line, }) end table.insert(lines, "") table.insert(lines, "# --- Add a new comment below this line. If you leave it blank, no new comment is created. ---") meta.new_comment_start = #lines + 1 table.insert(lines, "") -- Save comment meta meta.comments = comment_meta -- Put all lines into the buffer vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) -- Now apply highlight groups apply_issue_highlights(bufnr, meta) return meta end ------------------------------------------------------------------------------ -- ON SAVE (BufWriteCmd) ------------------------------------------------------------------------------ local function on_issue_buf_write(bufnr, metadata, owner, repo) local new_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) local new_title = table.concat(vim.list_slice(new_lines, metadata.title_start, metadata.title_end), "\n") new_title = new_title:gsub("%s+$", "") local current_issue_data = api.get_issue(owner, repo, metadata.issue_number) if not current_issue_data then vim.notify("[gitea.nvim] Could not re-fetch the issue to verify updates", vim.log.levels.ERROR) return end local changed_title = (current_issue_data.title or "") ~= new_title local new_body_lines = vim.list_slice(new_lines, metadata.body_start, metadata.body_end) local new_body = table.concat(new_body_lines, "\n") local changed_body = (current_issue_data.body or "") ~= new_body local changed_comments = {} for _, cmeta in ipairs(metadata.comments) do local c_lines = vim.list_slice(new_lines, cmeta.start_line, cmeta.end_line) -- first line => "COMMENT # by " local c_body = {} for i = 2, #c_lines do table.insert(c_body, c_lines[i]) end local new_comment_body = table.concat(c_body, "\n") table.insert(changed_comments, { id = cmeta.id, new_body = new_comment_body }) end local new_comment_body_lines = {} for i = metadata.new_comment_start, #new_lines do table.insert(new_comment_body_lines, new_lines[i]) end local new_comment_body = table.concat(new_comment_body_lines, "\n"):gsub("^%s+", ""):gsub("%s+$", "") local create_new_comment = (#new_comment_body > 0) -- Edit issue if title or body changed if changed_title or changed_body then local updated, st = api.edit_issue(owner, repo, metadata.issue_number, { title = changed_title and new_title or current_issue_data.title, body = changed_body and new_body or current_issue_data.body, }) if updated then vim.notify("[gitea.nvim] Issue updated!") else vim.notify(string.format("[gitea.nvim] Failed to update issue (HTTP %s).", st or "?"), vim.log.levels.ERROR) end end -- Edit each comment for _, cupd in ipairs(changed_comments) do local updated_comment, st = api.edit_issue_comment(owner, repo, metadata.issue_number, cupd.id, cupd.new_body) if not updated_comment then vim.notify(string.format("[gitea.nvim] Failed to update comment %d. (HTTP %s)", cupd.id, st or "?"), vim.log.levels.ERROR) else vim.notify(string.format("[gitea.nvim] Comment #%d updated.", cupd.id)) end end -- Possibly add a new comment if create_new_comment then local created, st = api.comment_issue(owner, repo, metadata.issue_number, new_comment_body) if created then vim.notify("[gitea.nvim] New comment posted.") else vim.notify(string.format("[gitea.nvim] Failed to create new comment (HTTP %s).", st or "?"), vim.log.levels.ERROR) end end end ------------------------------------------------------------------------------ -- OPEN ISSUE BUFFER ------------------------------------------------------------------------------ local function open_full_issue_buffer(owner, repo, number) local issue_data, status = api.get_issue(owner, repo, number) if not issue_data then vim.notify(string.format("[gitea.nvim] Error retrieving issue #%d (HTTP %s).", number, status or "?"), vim.log.levels.ERROR) return end local all_comments, cstatus = api.get_issue_comments(owner, repo, number) if not all_comments then vim.notify(string.format("[gitea.nvim] Error retrieving issue #%d comments (HTTP %s).", number, cstatus or "?"), vim.log.levels.ERROR) return end local buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_option(buf, "buftype", "acwrite") -- Use our new "gitea" filetype, so after/syntax/gitea.vim will load vim.api.nvim_buf_set_option(buf, "filetype", "gitea") vim.api.nvim_buf_set_name(buf, string.format("gitea_issue_full_%d", number)) -- Load highlight definitions (e.g. GiteaIssueTitle, GiteaCommentBody, etc.) highlights.setup() local metadata = render_issue_into_buf(buf, issue_data, all_comments) -- Auto command to handle saving vim.api.nvim_create_autocmd("BufWriteCmd", { buffer = buf, callback = function() on_issue_buf_write(buf, metadata, owner, repo) end }) vim.api.nvim_set_current_buf(buf) 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 has_telescope, _ = pcall(require, "telescope") if not has_telescope then vim.notify("[gitea.nvim] telescope.nvim is not installed", vim.log.levels.ERROR) return end local pickers = require("telescope.pickers") local finders = require("telescope.finders") local conf = require("telescope.config").values local actions = require("telescope.actions") local action_state = require("telescope.actions.state") 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 pickers.new({}, { prompt_title = string.format("Issues: %s/%s", owner, repo), finder = finders.new_table { results = issues, entry_maker = function(issue) return { value = issue, display = string.format("#%d %s", issue.number, issue.title), ordinal = issue.number .. " " .. issue.title } end }, sorter = conf.generic_sorter({}), attach_mappings = function(prompt_bufnr, map) local function select_issue() local selection = action_state.get_selected_entry() actions.close(prompt_bufnr) if not selection or not selection.value then return end open_full_issue_buffer(owner, repo, selection.value.number) end map("i", "", select_issue) map("n", "", select_issue) return true end, }):find() end, show = function(args) local number = tonumber(args[1]) if not number then vim.notify("[gitea.nvim] Usage: :Gitea issue show ", 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 open_full_issue_buffer(owner, repo, number) end, } subcommands.pr = { -- your other PR-related commands, if any } ------------------------------------------------------------------------------ -- REGISTER COMMAND ------------------------------------------------------------------------------ function M.register() vim.api.nvim_create_user_command("Gitea", function(cmd_opts) auth.ensure_token() local args = {} for w in string.gmatch(cmd_opts.args, "%S+") do table.insert(args, w) end local object = args[1] local action = args[2] if not object then print("Usage: :Gitea [args]") print("Examples:") print(" :Gitea issue list") print(" :Gitea issue show 123") return end local sc = subcommands[object] if not sc 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 = sc[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 table.remove(args, 1) -- remove object table.remove(args, 1) -- remove action fn(args) end, { nargs = "*", complete = function(arg_lead, cmd_line, cursor_pos) local parts = vim.split(cmd_line, "%s+") if #parts == 2 then 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