changes
This commit is contained in:
@@ -5,18 +5,13 @@ local curl = require("plenary.curl")
|
|||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local function get_base_url()
|
local function get_base_url()
|
||||||
local server = config.values.server_url
|
return config.values.server_url
|
||||||
if not server or server == "" then
|
|
||||||
-- fallback
|
|
||||||
server = "https://gitea.example.com"
|
|
||||||
end
|
|
||||||
return server
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function get_auth_header()
|
local function get_auth_header()
|
||||||
local token = auth.get_token()
|
local token = auth.get_token()
|
||||||
if not token or token == "" then
|
if not token or token == "" then
|
||||||
error("[gitea.nvim] Missing Gitea token. Please run :Gitea again or restart after entering a token.")
|
error("[gitea.nvim] Missing Gitea token.")
|
||||||
end
|
end
|
||||||
return "token " .. token
|
return "token " .. token
|
||||||
end
|
end
|
||||||
@@ -31,15 +26,14 @@ local function request(method, endpoint, opts)
|
|||||||
url = url,
|
url = url,
|
||||||
method = method,
|
method = method,
|
||||||
headers = headers,
|
headers = headers,
|
||||||
timeout = 5000, -- we can also read config.values.timeout
|
timeout = 10000,
|
||||||
body = opts.body and vim.json.encode(opts.body) or nil,
|
body = opts.body and vim.json.encode(opts.body) or nil,
|
||||||
|
query = opts.query,
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------------------------
|
-- Issues
|
||||||
-- ISSUES
|
|
||||||
-------------------------------------------------------
|
|
||||||
function M.list_issues(owner, repo, opts)
|
function M.list_issues(owner, repo, opts)
|
||||||
local endpoint = string.format("/api/v1/repos/%s/%s/issues", owner, repo)
|
local endpoint = string.format("/api/v1/repos/%s/%s/issues", owner, repo)
|
||||||
local result = request("GET", endpoint, { query = opts })
|
local result = request("GET", endpoint, { query = opts })
|
||||||
@@ -59,7 +53,6 @@ function M.get_issue(owner, repo, number)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function M.create_issue(owner, repo, data)
|
function M.create_issue(owner, repo, data)
|
||||||
-- data = { title = "", body = "", labels = {"bug"}, etc. }
|
|
||||||
local endpoint = string.format("/api/v1/repos/%s/%s/issues", owner, repo)
|
local endpoint = string.format("/api/v1/repos/%s/%s/issues", owner, repo)
|
||||||
local result = request("POST", endpoint, { body = data })
|
local result = request("POST", endpoint, { body = data })
|
||||||
if result and result.status == 201 then
|
if result and result.status == 201 then
|
||||||
@@ -78,7 +71,6 @@ function M.edit_issue(owner, repo, number, data)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function M.close_issue(owner, repo, number)
|
function M.close_issue(owner, repo, number)
|
||||||
-- Gitea: state -> "closed"
|
|
||||||
return M.edit_issue(owner, repo, number, { state = "closed" })
|
return M.edit_issue(owner, repo, number, { state = "closed" })
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -95,9 +87,28 @@ function M.comment_issue(owner, repo, number, body)
|
|||||||
return nil, result and result.status
|
return nil, result and result.status
|
||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------------------------
|
-- ADDED: edit_issue_comment
|
||||||
-- PULL REQUESTS
|
-- Gitea supports: PATCH /repos/{owner}/{repo}/issues/comments/{id}
|
||||||
-------------------------------------------------------
|
function M.edit_issue_comment(owner, repo, number, comment_id, body)
|
||||||
|
local endpoint = string.format("/api/v1/repos/%s/%s/issues/comments/%d", owner, repo, comment_id)
|
||||||
|
local result = request("PATCH", endpoint, { body = { body = body } })
|
||||||
|
if result and result.status == 200 then
|
||||||
|
return vim.json.decode(result.body)
|
||||||
|
end
|
||||||
|
return nil, result and result.status
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ADDED: get_issue_comments
|
||||||
|
function M.get_issue_comments(owner, repo, number)
|
||||||
|
local endpoint = string.format("/api/v1/repos/%s/%s/issues/%d/comments", owner, repo, number)
|
||||||
|
local result = request("GET", endpoint)
|
||||||
|
if result and result.status == 200 then
|
||||||
|
return vim.json.decode(result.body)
|
||||||
|
end
|
||||||
|
return nil, result and result.status
|
||||||
|
end
|
||||||
|
|
||||||
|
-- PR
|
||||||
function M.list_pull_requests(owner, repo, opts)
|
function M.list_pull_requests(owner, repo, opts)
|
||||||
local endpoint = string.format("/api/v1/repos/%s/%s/pulls", owner, repo)
|
local endpoint = string.format("/api/v1/repos/%s/%s/pulls", owner, repo)
|
||||||
local result = request("GET", endpoint, { query = opts })
|
local result = request("GET", endpoint, { query = opts })
|
||||||
@@ -117,7 +128,6 @@ function M.get_pull_request(owner, repo, number)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function M.create_pull_request(owner, repo, data)
|
function M.create_pull_request(owner, repo, data)
|
||||||
-- data = { head = "branch", base = "master", title = "My PR", body = "..." }
|
|
||||||
local endpoint = string.format("/api/v1/repos/%s/%s/pulls", owner, repo)
|
local endpoint = string.format("/api/v1/repos/%s/%s/pulls", owner, repo)
|
||||||
local result = request("POST", endpoint, { body = data })
|
local result = request("POST", endpoint, { body = data })
|
||||||
if result and result.status == 201 then
|
if result and result.status == 201 then
|
||||||
@@ -136,12 +146,10 @@ function M.edit_pull_request(owner, repo, number, data)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function M.merge_pull_request(owner, repo, number, merge_style, merge_title, merge_message)
|
function M.merge_pull_request(owner, repo, number, merge_style, merge_title, merge_message)
|
||||||
-- merge_style: "merge"|"rebase"|"squash" (in Gitea it's "merge"|"rebase"|"rebase-merge"|"squash" -
|
|
||||||
-- see Gitea docs for specifics; we can map the user’s choice to Gitea’s).
|
|
||||||
local endpoint = string.format("/api/v1/repos/%s/%s/pulls/%d/merge", owner, repo, number)
|
local endpoint = string.format("/api/v1/repos/%s/%s/pulls/%d/merge", owner, repo, number)
|
||||||
local result = request("POST", endpoint, {
|
local result = request("POST", endpoint, {
|
||||||
body = {
|
body = {
|
||||||
Do = merge_style or "merge", -- e.g. "merge"
|
Do = merge_style or "merge",
|
||||||
MergeTitleField = merge_title or "",
|
MergeTitleField = merge_title or "",
|
||||||
MergeMessageField = merge_message or "",
|
MergeMessageField = merge_message or "",
|
||||||
}
|
}
|
||||||
@@ -161,8 +169,7 @@ function M.reopen_pull_request(owner, repo, number)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function M.comment_pull_request(owner, repo, number, body)
|
function M.comment_pull_request(owner, repo, number, body)
|
||||||
-- same endpoint as issues for Gitea. A PR is an issue with is_pull = true
|
|
||||||
return M.comment_issue(owner, repo, number, body)
|
return M.comment_issue(owner, repo, number, body)
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
@@ -4,9 +4,9 @@ local uv = vim.loop
|
|||||||
local M = {}
|
local M = {}
|
||||||
local token_cached = nil
|
local token_cached = nil
|
||||||
|
|
||||||
-- Attempt to read the token file from disk
|
-- Read token file from disk (if present)
|
||||||
local function read_token_file(path)
|
local function read_token_file(path)
|
||||||
local fd = uv.fs_open(path, "r", 438) -- 0666 in decimal
|
local fd = uv.fs_open(path, "r", 438)
|
||||||
if not fd then return nil end
|
if not fd then return nil end
|
||||||
local stat = uv.fs_fstat(fd)
|
local stat = uv.fs_fstat(fd)
|
||||||
local data = uv.fs_read(fd, stat.size, 0)
|
local data = uv.fs_read(fd, stat.size, 0)
|
||||||
@@ -14,9 +14,9 @@ local function read_token_file(path)
|
|||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Write token file
|
-- Write token file to disk with restricted permissions
|
||||||
local function write_token_file(path, token)
|
local function write_token_file(path, token)
|
||||||
local fd = uv.fs_open(path, "w", 384) -- 0600 in decimal
|
local fd = uv.fs_open(path, "w", 384) -- 0600 decimal
|
||||||
if not fd then
|
if not fd then
|
||||||
error("[gitea.nvim] Failed to open token file for writing: " .. path)
|
error("[gitea.nvim] Failed to open token file for writing: " .. path)
|
||||||
end
|
end
|
||||||
@@ -24,137 +24,86 @@ local function write_token_file(path, token)
|
|||||||
uv.fs_close(fd)
|
uv.fs_close(fd)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Checks if the token file is in .gitignore, if not ask user
|
----------------------------------------------------------------------------
|
||||||
local function ensure_ignored(token_file, ignore_file)
|
--- Attempt to detect the Gitea server from remote.origin.url or fail
|
||||||
-- Read .gitignore lines if present
|
----------------------------------------------------------------------------
|
||||||
local file = io.open(ignore_file, "r")
|
|
||||||
local lines = {}
|
|
||||||
if file then
|
|
||||||
for line in file:lines() do
|
|
||||||
table.insert(lines, line)
|
|
||||||
end
|
|
||||||
file:close()
|
|
||||||
end
|
|
||||||
-- Check if token_file is in .gitignore
|
|
||||||
for _, l in ipairs(lines) do
|
|
||||||
if l == token_file then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Not in ignore, ask user
|
|
||||||
vim.schedule(function()
|
|
||||||
vim.cmd([[echohl WarningMsg]])
|
|
||||||
vim.cmd([[echo "gitea.nvim: We recommend adding ']]..token_file..[[' to ']]..ignore_file..[['. Add now? (y/N)"]])
|
|
||||||
vim.cmd([[echohl None]])
|
|
||||||
|
|
||||||
local ans = vim.fn.getchar()
|
|
||||||
ans = vim.fn.nr2char(ans)
|
|
||||||
|
|
||||||
if ans == "y" or ans == "Y" then
|
|
||||||
local f = io.open(ignore_file, "a")
|
|
||||||
if f then
|
|
||||||
f:write("\n" .. token_file .. "\n")
|
|
||||||
f:close()
|
|
||||||
vim.notify("Added '"..token_file.."' to '"..ignore_file.."'", vim.log.levels.INFO)
|
|
||||||
else
|
|
||||||
vim.notify("Failed to open '"..ignore_file.."' for appending.", vim.log.levels.ERROR)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
vim.notify("Ok, not adding token file to '"..ignore_file.."'. Be mindful of security!", vim.log.levels.WARN)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Parse .git/config or `git remote get-url origin` to guess the Gitea server
|
|
||||||
local function detect_gitea_server()
|
local function detect_gitea_server()
|
||||||
-- Attempt: use `git config remote.origin.url`
|
-- Use `git config remote.origin.url`
|
||||||
local cmd = "git config remote.origin.url"
|
local cmd = "git config remote.origin.url"
|
||||||
local url = vim.fn.systemlist(cmd)[1]
|
local url = vim.fn.systemlist(cmd)[1]
|
||||||
if vim.v.shell_error ~= 0 or not url or url == "" then
|
if vim.v.shell_error ~= 0 or not url or url == "" then
|
||||||
return nil
|
return nil, "No valid remote.origin.url found"
|
||||||
end
|
|
||||||
-- Example: https://gitea.myserver.com/owner/repo.git
|
|
||||||
-- Strip `.git` suffix if present
|
|
||||||
url = url:gsub("%.git$", "")
|
|
||||||
-- Attempt to extract domain, e.g. "https://gitea.myserver.com"
|
|
||||||
-- We'll do a naive pattern match
|
|
||||||
local protocol, domain_and_path = url:match("^(https?://)(.*)$")
|
|
||||||
if not protocol or not domain_and_path then
|
|
||||||
-- Could be SSH or something else
|
|
||||||
-- For instance: git@gitea.myserver.com:owner/repo.git
|
|
||||||
-- Try a different parse
|
|
||||||
local user_host, path = url:match("^(.-):(.*)$")
|
|
||||||
if user_host and path and user_host:match(".*@") then
|
|
||||||
-- user_host could be "git@gitea.myserver.com"
|
|
||||||
local host = user_host:match("@(.*)")
|
|
||||||
if host then
|
|
||||||
return "https://"..host
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Fallback
|
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- domain_and_path might be "gitea.myserver.com/owner/repo"
|
-- Remove trailing ".git" if present
|
||||||
-- just extract "gitea.myserver.com"
|
url = url:gsub("%.git$", "")
|
||||||
local server = domain_and_path:match("^([^/]+)")
|
|
||||||
if not server then
|
-- Check for HTTPS style: https://my.gitea.com/owner/repo
|
||||||
return nil
|
local protocol, domain_and_path = url:match("^(https?://)(.*)$")
|
||||||
|
if protocol and domain_and_path then
|
||||||
|
local server = domain_and_path:match("^([^/]+)")
|
||||||
|
if server and server ~= "" then
|
||||||
|
return protocol .. server
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return protocol .. server
|
|
||||||
|
-- Check for SSH style: git@my.gitea.com:owner/repo
|
||||||
|
local user_host, path = url:match("^(.-):(.*)$")
|
||||||
|
if user_host and user_host:match(".*@") then
|
||||||
|
local host = user_host:match("@(.*)")
|
||||||
|
if host and host ~= "" then
|
||||||
|
-- We'll assume https for Gitea, if your instance is not https,
|
||||||
|
-- you can adapt or forcibly use "http://" below
|
||||||
|
return "https://" .. host
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil, "Unable to parse Gitea host from remote.origin.url: " .. url
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Called during plugin setup or first command usage
|
----------------------------------------------------------------------------
|
||||||
|
--- DO NOT PROMPT for server_url, do not fallback to "gitea.example.com"
|
||||||
|
--- If we cannot auto-detect, we throw an error.
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
local function ensure_server_url()
|
||||||
|
local server, err = detect_gitea_server()
|
||||||
|
if not server then
|
||||||
|
error(string.format("[gitea.nvim] Failed to auto-detect Gitea server: %s", err))
|
||||||
|
end
|
||||||
|
config.values.server_url = server
|
||||||
|
end
|
||||||
|
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
--- The user must have a Gitea token, we read from disk or error if missing
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
local function ensure_token_file()
|
||||||
|
local token_file = config.values.config_file
|
||||||
|
local token_data = read_token_file(token_file)
|
||||||
|
if token_data and token_data ~= "" then
|
||||||
|
token_cached = token_data
|
||||||
|
return
|
||||||
|
end
|
||||||
|
error(
|
||||||
|
"[gitea.nvim] No token found in " .. token_file ..
|
||||||
|
". Please manually create and store your Gitea token there with mode 600."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
--- Called by the plugin during setup or first usage
|
||||||
|
----------------------------------------------------------------------------
|
||||||
function M.ensure_token()
|
function M.ensure_token()
|
||||||
if token_cached then
|
if token_cached then
|
||||||
return token_cached
|
return token_cached
|
||||||
end
|
end
|
||||||
|
|
||||||
local cfg = config.values
|
ensure_server_url()
|
||||||
local token_file = cfg.config_file
|
ensure_token_file()
|
||||||
local ignore_file = cfg.ignore_file
|
return token_cached
|
||||||
|
|
||||||
-- Attempt to detect the Gitea server if not provided
|
|
||||||
if not cfg.server_url or cfg.server_url == "" then
|
|
||||||
cfg.server_url = detect_gitea_server()
|
|
||||||
end
|
|
||||||
if not cfg.server_url then
|
|
||||||
vim.notify("[gitea.nvim] Could not detect Gitea server from .git/config. Please configure manually.", vim.log.levels.WARN)
|
|
||||||
end
|
|
||||||
|
|
||||||
local token_data = read_token_file(token_file)
|
|
||||||
if token_data and token_data ~= "" then
|
|
||||||
token_cached = token_data
|
|
||||||
return token_cached
|
|
||||||
end
|
|
||||||
|
|
||||||
-- If we reach here, no token was found
|
|
||||||
vim.schedule(function()
|
|
||||||
vim.cmd([[echohl WarningMsg]])
|
|
||||||
vim.cmd([[echo "gitea.nvim: No Gitea token found. Please enter your Gitea Personal Access Token:"]])
|
|
||||||
vim.cmd([[echohl None]])
|
|
||||||
|
|
||||||
local user_token = vim.fn.input("Token: ")
|
|
||||||
vim.cmd("redraw")
|
|
||||||
|
|
||||||
if user_token == nil or user_token == "" then
|
|
||||||
vim.notify("[gitea.nvim] No token provided, plugin may not work properly.", vim.log.levels.ERROR)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
write_token_file(token_file, user_token)
|
|
||||||
token_cached = user_token
|
|
||||||
vim.notify("Token saved to " .. token_file .. ".", vim.log.levels.INFO)
|
|
||||||
|
|
||||||
-- Offer to add token_file to .gitignore
|
|
||||||
ensure_ignored(token_file, ignore_file)
|
|
||||||
end)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.get_token()
|
function M.get_token()
|
||||||
return token_cached
|
return token_cached
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
@@ -4,42 +4,287 @@ local config = require("gitea.config")
|
|||||||
local api = require("gitea.api")
|
local api = require("gitea.api")
|
||||||
local auth = require("gitea.auth")
|
local auth = require("gitea.auth")
|
||||||
|
|
||||||
-- Utility: parse "owner/repo" from the remote URL
|
-- We'll keep detect_owner_repo() from before
|
||||||
local function detect_owner_repo()
|
local function detect_owner_repo()
|
||||||
local cmd = "git config remote.origin.url"
|
local cmd = "git config remote.origin.url"
|
||||||
local url = vim.fn.systemlist(cmd)[1]
|
local url = vim.fn.systemlist(cmd)[1]
|
||||||
if vim.v.shell_error ~= 0 or not url or url == "" then
|
if vim.v.shell_error ~= 0 or not url or url == "" then
|
||||||
return nil, nil
|
return nil, nil
|
||||||
end
|
end
|
||||||
-- Remove trailing ".git"
|
|
||||||
url = url:gsub("%.git$", "")
|
url = url:gsub("%.git$", "")
|
||||||
|
|
||||||
-- Try to parse: e.g. https://my.gitea.com/owner/repo
|
|
||||||
local _, after = url:match("^(https?://)(.*)$")
|
local _, after = url:match("^(https?://)(.*)$")
|
||||||
if after then
|
if after then
|
||||||
-- after might be "my.gitea.com/owner/repo"
|
local path = after:match("[^/]+/(.*)$")
|
||||||
-- let's capture the part after the first slash
|
|
||||||
local path = after:match("[^/]+/(.*)$") -- skip domain
|
|
||||||
if not path then
|
if not path then
|
||||||
return nil, nil
|
return nil, nil
|
||||||
end
|
end
|
||||||
local owner, repo = path:match("^(.-)/(.*)$")
|
local owner, repo = path:match("^(.-)/(.*)$")
|
||||||
return owner, repo
|
return owner, repo
|
||||||
end
|
end
|
||||||
|
|
||||||
-- SSH style: git@my.gitea.com:owner/repo
|
|
||||||
local user_host, path = url:match("^(.-):(.*)$")
|
local user_host, path = url:match("^(.-):(.*)$")
|
||||||
if user_host and path then
|
if user_host and path then
|
||||||
-- user_host might be "git@my.gitea.com"
|
|
||||||
local owner, repo = path:match("^(.-)/(.*)$")
|
local owner, repo = path:match("^(.-)/(.*)$")
|
||||||
return owner, repo
|
return owner, repo
|
||||||
end
|
end
|
||||||
return nil, nil
|
return nil, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Subcommands
|
--- Data Structures to track what's in the ephemeral buffer
|
||||||
-------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- We'll store some metadata for each "segment" in the buffer:
|
||||||
|
-- - The issue's title line range
|
||||||
|
-- - The issue's body line range
|
||||||
|
-- - Each existing comment (with start/end line range)
|
||||||
|
-- - Possibly space for a new comment
|
||||||
|
--
|
||||||
|
-- On save (:w), we compare what's changed and do the appropriate API calls.
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local function create_issue_buffer_metadata(issue)
|
||||||
|
-- track lines for the title, the body, each comment
|
||||||
|
local meta = {
|
||||||
|
issue_number = issue.number,
|
||||||
|
-- will fill in fields once we place them in the buffer
|
||||||
|
title_start = nil,
|
||||||
|
title_end = nil,
|
||||||
|
body_start = nil,
|
||||||
|
body_end = nil,
|
||||||
|
comments = {}, -- array of { id, start, end }
|
||||||
|
new_comment_start = nil, -- where user can type a new comment
|
||||||
|
}
|
||||||
|
return meta
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
--- RENDER ISSUE INTO BUFFER
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
local function render_issue_into_buf(bufnr, issue, comments)
|
||||||
|
-- We assume the buffer is empty and ephemeral.
|
||||||
|
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, "")
|
||||||
|
|
||||||
|
-- store line index references for metadata
|
||||||
|
local meta = create_issue_buffer_metadata(issue)
|
||||||
|
|
||||||
|
-- place the "title line"
|
||||||
|
meta.title_start = #lines+1
|
||||||
|
table.insert(lines, issue.title or "")
|
||||||
|
meta.title_end = #lines
|
||||||
|
|
||||||
|
-- a blank line
|
||||||
|
table.insert(lines, "")
|
||||||
|
-- place the "body 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
|
||||||
|
|
||||||
|
-- spacing
|
||||||
|
table.insert(lines, "")
|
||||||
|
table.insert(lines, "# --- Comments ---")
|
||||||
|
|
||||||
|
local line_count = #lines
|
||||||
|
local comment_meta = {}
|
||||||
|
for _, c in ipairs(comments) do
|
||||||
|
table.insert(lines, "")
|
||||||
|
local start_line = #lines+1
|
||||||
|
-- We'll place a small header: e.g. "Comment (ID=12345) by username"
|
||||||
|
local author = c.user and c.user.login or c.user and c.user.username or c.user or "?"
|
||||||
|
table.insert(lines, string.format("COMMENT #%d by %s", c.id, author))
|
||||||
|
-- Then the actual comment body lines
|
||||||
|
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
|
||||||
|
|
||||||
|
-- place a blank line for user to add a new comment
|
||||||
|
table.insert(lines, "")
|
||||||
|
table.insert(lines, "# --- Add a new comment below this line. If you leave it blank, no new comment is created. ---")
|
||||||
|
local new_comment_start = #lines+1
|
||||||
|
table.insert(lines, "") -- an empty line to start from
|
||||||
|
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||||
|
|
||||||
|
-- store metadata
|
||||||
|
local b_meta = {
|
||||||
|
issue_number = issue.number,
|
||||||
|
title_start = meta.title_start,
|
||||||
|
title_end = meta.title_end,
|
||||||
|
body_start = meta.body_start,
|
||||||
|
body_end = meta.body_end,
|
||||||
|
comments = comment_meta,
|
||||||
|
new_comment_start = new_comment_start,
|
||||||
|
}
|
||||||
|
return b_meta
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
--- Parse buffer changes and do API updates on save
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
local function on_issue_buf_write(bufnr, metadata, owner, repo)
|
||||||
|
-- read all buffer lines
|
||||||
|
local new_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||||
|
|
||||||
|
----------------- Title update check -----------------
|
||||||
|
local new_title = table.concat(vim.list_slice(new_lines, metadata.title_start, metadata.title_end), "\n")
|
||||||
|
-- remove trailing newlines just in case
|
||||||
|
new_title = new_title:gsub("%s+$", "")
|
||||||
|
-- we do a get_issue call to see the current title
|
||||||
|
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 = false
|
||||||
|
if (current_issue_data.title or "") ~= new_title then
|
||||||
|
changed_title = true
|
||||||
|
end
|
||||||
|
|
||||||
|
----------------- Body update check ------------------
|
||||||
|
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 = false
|
||||||
|
if (current_issue_data.body or "") ~= new_body then
|
||||||
|
changed_body = true
|
||||||
|
end
|
||||||
|
|
||||||
|
----------------- Comments update check -------------
|
||||||
|
local changed_comments = {}
|
||||||
|
for _, cmeta in ipairs(metadata.comments) do
|
||||||
|
local c_lines = vim.list_slice(new_lines, cmeta.start_line, cmeta.end_line)
|
||||||
|
-- The first line is "COMMENT #<id> by <author>"
|
||||||
|
-- The rest is the actual body
|
||||||
|
-- e.g. `COMMENT #112 by alice`
|
||||||
|
local first_line = c_lines[1] or ""
|
||||||
|
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")
|
||||||
|
-- We fetch the current comment from the server to check if changed
|
||||||
|
-- But Gitea's issue comment API does not necessarily let us get a single comment by ID easily.
|
||||||
|
-- Instead, we do "issue_data, with comments" or store old body in memory.
|
||||||
|
|
||||||
|
-- For simplicity, let's store the old body in an extmark or in a table. But let's just do a naive approach:
|
||||||
|
-- We'll assume if the line is changed, we want to patch it. We have no big reference of old body, so let's do it.
|
||||||
|
|
||||||
|
table.insert(changed_comments, {
|
||||||
|
id = cmeta.id,
|
||||||
|
new_body = new_comment_body
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
----------------- New comment check ------------------
|
||||||
|
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)
|
||||||
|
|
||||||
|
-------------- Perform the updates --------------
|
||||||
|
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
|
||||||
|
|
||||||
|
-- for each changed comment, attempt to patch
|
||||||
|
-- We do not do a diff, so we always do a "PATCH" if we suspect changes
|
||||||
|
-- => If the user changed nothing, Gitea might respond 200 anyway, no big deal
|
||||||
|
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
|
||||||
|
|
||||||
|
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 a single ephemeral buffer for the entire Issue with comments
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
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
|
||||||
|
|
||||||
|
-- get all existing comments from the Gitea API
|
||||||
|
-- for Gitea, we can do: GET /repos/{owner}/{repo}/issues/{index}/comments
|
||||||
|
-- The plugin's `api` might have a function for that, or we can add it quickly
|
||||||
|
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")
|
||||||
|
vim.api.nvim_buf_set_option(buf, "filetype", "markdown")
|
||||||
|
vim.api.nvim_buf_set_name(buf, string.format("gitea_issue_full_%d", number))
|
||||||
|
|
||||||
|
local metadata = render_issue_into_buf(buf, issue_data, all_comments)
|
||||||
|
|
||||||
|
-- We create an autocmd on BufWriteCmd to parse changes and do updates
|
||||||
|
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 = {}
|
local subcommands = {}
|
||||||
|
|
||||||
subcommands.issue = {
|
subcommands.issue = {
|
||||||
@@ -49,372 +294,120 @@ subcommands.issue = {
|
|||||||
vim.notify("[gitea.nvim] Could not detect owner/repo.", vim.log.levels.ERROR)
|
vim.notify("[gitea.nvim] Could not detect owner/repo.", vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
-- use the same telescope approach as before:
|
||||||
|
local has_telescope, tele = 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, {})
|
local issues, status = api.list_issues(owner, repo, {})
|
||||||
if not issues then
|
if not issues then
|
||||||
vim.notify(string.format("[gitea.nvim] Error fetching issues (HTTP %s).", status or "?"), vim.log.levels.ERROR)
|
vim.notify(string.format("[gitea.nvim] Error fetching issues (HTTP %s).", status or "?"), vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
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)
|
pickers.new({}, {
|
||||||
local owner, repo = detect_owner_repo()
|
prompt_title = string.format("Issues: %s/%s", owner, repo),
|
||||||
if not owner or not repo then
|
finder = finders.new_table {
|
||||||
vim.notify("[gitea.nvim] Could not detect owner/repo.", vim.log.levels.ERROR)
|
results = issues,
|
||||||
return
|
entry_maker = function(issue)
|
||||||
end
|
return {
|
||||||
-- We'll open a scratch buffer for user to fill in Title/Body
|
value = issue,
|
||||||
-- Then upon writing, we create the issue.
|
display = string.format("#%d %s", issue.number, issue.title),
|
||||||
-- For brevity, let's do a simpler approach: ask input in command line
|
ordinal = issue.number .. " " .. issue.title
|
||||||
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 <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
|
|
||||||
|
|
||||||
-- 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
|
||||||
end
|
},
|
||||||
})
|
sorter = conf.generic_sorter({}),
|
||||||
|
attach_mappings = function(prompt_bufnr, map)
|
||||||
vim.api.nvim_set_current_buf(buf)
|
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
|
||||||
|
-- Instead of just editing, we now open the full timeline buffer
|
||||||
|
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,
|
end,
|
||||||
|
|
||||||
close = function(args)
|
show = function(args)
|
||||||
|
-- Alternative: show a single issue by number
|
||||||
local number = tonumber(args[1])
|
local number = tonumber(args[1])
|
||||||
if not number then
|
if not number then
|
||||||
vim.notify("[gitea.nvim] Usage: :Gitea issue close <number>", vim.log.levels.ERROR)
|
vim.notify("[gitea.nvim] Usage: :Gitea issue show <number>", vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local owner, repo = detect_owner_repo()
|
local owner, repo = detect_owner_repo()
|
||||||
local res, status = api.close_issue(owner, repo, number)
|
if not owner or not repo then
|
||||||
if res then
|
vim.notify("[gitea.nvim] Could not detect owner/repo.", vim.log.levels.ERROR)
|
||||||
vim.notify("[gitea.nvim] Issue #"..number.." closed.")
|
return
|
||||||
else
|
|
||||||
vim.notify(string.format("[gitea.nvim] Failed to close issue (HTTP %s).", status or "?"), vim.log.levels.ERROR)
|
|
||||||
end
|
end
|
||||||
|
open_full_issue_buffer(owner, repo, number)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
reopen = function(args)
|
-- The rest of your commands can remain or be removed as needed
|
||||||
local number = tonumber(args[1])
|
-- but presumably we can just use "show" for everything
|
||||||
if not number then
|
-- or keep an existing minimal create command, etc.
|
||||||
vim.notify("[gitea.nvim] Usage: :Gitea issue reopen <number>", 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 <number>", 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 = {
|
subcommands.pr = {
|
||||||
list = function(args)
|
-- We'll leave your existing PR commands as is or remove for brevity
|
||||||
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 <number>", 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 <number> [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 <branch>"))
|
|
||||||
vim.notify("[gitea.nvim] Branch deletion not implemented. Use `git push origin --delete <branch>`.")
|
|
||||||
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 <number>", 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 <number>", 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 <number>", 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.
|
--------------------------------------------------------------------------------
|
||||||
|
--- REGISTER
|
||||||
-------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Command dispatcher
|
|
||||||
-------------------------------
|
|
||||||
function M.register()
|
function M.register()
|
||||||
vim.api.nvim_create_user_command("Gitea", function(cmd_opts)
|
vim.api.nvim_create_user_command("Gitea", function(cmd_opts)
|
||||||
-- Ensure token is loaded (in case user runs a command after startup)
|
auth.ensure_token()
|
||||||
local _ = require("gitea.auth").ensure_token()
|
|
||||||
|
|
||||||
local args = {}
|
local args = {}
|
||||||
for w in string.gmatch(cmd_opts.args, "%S+") do
|
for w in string.gmatch(cmd_opts.args, "%S+") do
|
||||||
table.insert(args, w)
|
table.insert(args, w)
|
||||||
end
|
end
|
||||||
|
local object = args[1]
|
||||||
local object = args[1] -- "issue", "pr", etc.
|
local action = args[2]
|
||||||
local action = args[2] -- "list", "create", etc.
|
|
||||||
|
|
||||||
if not object then
|
if not object then
|
||||||
print("Usage: :Gitea <object> <action> [args]")
|
print("Usage: :Gitea <object> <action> [args]")
|
||||||
print("Examples:")
|
print("Examples:")
|
||||||
print(" :Gitea issue list")
|
print(" :Gitea issue list")
|
||||||
print(" :Gitea pr list")
|
print(" :Gitea issue show 123")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
local sc = subcommands[object]
|
||||||
if not subcommands[object] then
|
if not sc then
|
||||||
vim.notify("[gitea.nvim] Unknown object: "..object, vim.log.levels.ERROR)
|
vim.notify("[gitea.nvim] Unknown object: "..object, vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if not action then
|
if not action then
|
||||||
vim.notify("[gitea.nvim] Please specify an action for "..object, vim.log.levels.ERROR)
|
vim.notify("[gitea.nvim] Please specify an action for "..object, vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
local fn = sc[action]
|
||||||
local fn = subcommands[object][action]
|
|
||||||
if not fn then
|
if not fn then
|
||||||
vim.notify(string.format("[gitea.nvim] Unknown action '%s' for object '%s'", action, object), vim.log.levels.ERROR)
|
vim.notify(string.format("[gitea.nvim] Unknown action '%s' for object '%s'", action, object), vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
table.remove(args, 1) -- remove object
|
||||||
-- remove first two tokens
|
table.remove(args, 1) -- remove action
|
||||||
table.remove(args, 1)
|
|
||||||
table.remove(args, 1)
|
|
||||||
|
|
||||||
fn(args)
|
fn(args)
|
||||||
end, {
|
end, {
|
||||||
nargs = "*",
|
nargs = "*",
|
||||||
complete = function(arg_lead, cmd_line, cursor_pos)
|
complete = function(arg_lead, cmd_line, cursor_pos)
|
||||||
-- Very basic completion
|
|
||||||
local parts = vim.split(cmd_line, "%s+")
|
local parts = vim.split(cmd_line, "%s+")
|
||||||
if #parts == 2 then
|
if #parts == 2 then
|
||||||
-- user typed: :Gitea <object-partial>
|
|
||||||
local objs = {}
|
local objs = {}
|
||||||
for k, _ in pairs(subcommands) do
|
for k, _ in pairs(subcommands) do
|
||||||
if k:match("^"..arg_lead) then
|
if k:match("^"..arg_lead) then
|
||||||
@@ -440,4 +433,4 @@ function M.register()
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
@@ -1,18 +1,21 @@
|
|||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local config = require("gitea.config")
|
local config = require("gitea.config")
|
||||||
local commands = require("gitea.commands")
|
local commands = require("gitea.commands")
|
||||||
local auth = require("gitea.auth")
|
local auth = require("gitea.auth")
|
||||||
|
local highlights = require("gitea.highlights")
|
||||||
|
|
||||||
-- Main entrypoint
|
|
||||||
function M.setup(user_opts)
|
function M.setup(user_opts)
|
||||||
|
-- Load user config (if any), or defaults
|
||||||
config.setup(user_opts or {})
|
config.setup(user_opts or {})
|
||||||
|
|
||||||
-- Attempt to load existing token; if not found, prompt user
|
-- Ensure token is loaded
|
||||||
auth.ensure_token()
|
auth.ensure_token()
|
||||||
|
|
||||||
-- Register :Gitea command and subcommands
|
-- Register :Gitea commands
|
||||||
commands.register()
|
commands.register()
|
||||||
|
|
||||||
|
-- Apply the default Dracula-like highlights
|
||||||
|
highlights.setup()
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
Reference in New Issue
Block a user