Files
gitea.nvim/lua/gitea/commands.lua

429 lines
15 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 Luas 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