429 lines
15 KiB
Lua
429 lines
15 KiB
Lua
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 #<id> by <author>"
|
||
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", "<CR>", select_issue)
|
||
map("n", "<CR>", 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 <number>", 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 <object> <action> [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
|