initial commit

This commit is contained in:
2024-12-23 01:46:04 +01:00
commit c408cd3d24
7 changed files with 886 additions and 0 deletions

3
.chatgpt_config.yaml Normal file
View File

@@ -0,0 +1,3 @@
project_name: "gitea.nvim"
default_prompt_blocks:
- "basic-prompt"

72
README.md Normal file
View File

@@ -0,0 +1,72 @@
# gitea.nvim
A Neovim plugin to manage [Gitea](https://gitea.io) issues and pull requests right from your editor, inspired by [octo.nvim](https://github.com/pwntester/octo.nvim).
## Features
- Browse, open, and edit issues
- Create new issues (including adding labels, assignees, etc.)
- Browse, open, and edit pull requests
- Merge, squash, or rebase pull requests
- Add/modify/delete comments on issues and PRs
- Inline PR reviews and comments
- Add/remove labels, reviewers, assignees, etc.
- And more (mirroring much of octo.nvims functionality)
## Requirements
- **Neovim 0.10+**
- [plenary.nvim](https://github.com/nvim-lua/plenary.nvim) for async utilities
- A running Gitea server
- A Personal Access Token (PAT) for Gitea
## Installation
Install using your favorite plugin manager, for example with **packer**:
```lua
use {
"your-username/gitea.nvim",
requires = {"nvim-lua/plenary.nvim"}
}
```
## Setup & First Run
There's no need to store your Gitea server or token directly in your `init.lua`.
The plugin will:
1. Parse your `.git/config` to attempt to detect your Gitea server URL (e.g. from `git config remote.origin.url`).
2. Check for a local token file (by default `.gitea_nvim_token`) in your repo directory.
3. If no token file is found, `gitea.nvim` will prompt you for one. The plugin will then offer to add that token file to your `.gitignore` (highly recommended).
Example usage in your config:
```lua
require("gitea").setup({
-- No need to provide a hostname or token here;
-- but you can override defaults if needed:
-- e.g. config_file = ".my_custom_token_file"
-- or ignore_file = ".my_custom_gitignore"
})
```
## Usage
Once installed and configured, you can use the `:Gitea` command with subcommands:
- `:Gitea issue list` — Lists issues for the current repo.
- `:Gitea issue create` — Opens a scratch buffer to create a new issue.
- `:Gitea issue edit <number>` — Opens an issue buffer for `<number>`.
- `:Gitea pr list` — Lists pull requests for the current repo.
- `:Gitea pr edit <number>` — Opens a PR buffer for `<number>`.
- `:Gitea pr merge [commit|rebase|squash] [delete|nodelete]` — Merges a PR with the selected method.
- `:Gitea pr create` — Creates a new pull request from your current branch to the base branch.
- More subcommands (close, reopen, comment, reaction, etc.) will be added similarly to octo.nvim.
When you open an issue or PR buffer, simply edit the title, body, or add new comments. When you save (`:w`), changes are pushed to Gitea.
## Roadmap
- Inline PR reviews (open diff views, comment on lines, etc.)
- Advanced searching and filtering (like `:Gitea issue search <query>`)
- Further config options (like picking a fuzzy-finder: telescope or fzf-lua)
## Contributing
Pull requests and issue reports are welcome! The goal is to replicate and adapt the core [octo.nvim](https://github.com/pwntester/octo.nvim) workflow for Gitea.
## License
[MIT](https://choosealicense.com/licenses/mit/)

168
lua/gitea/api.lua Normal file
View File

@@ -0,0 +1,168 @@
local config = require("gitea.config")
local auth = require("gitea.auth")
local curl = require("plenary.curl")
local M = {}
local function get_base_url()
local server = config.values.server_url
if not server or server == "" then
-- fallback
server = "https://gitea.example.com"
end
return server
end
local function get_auth_header()
local token = auth.get_token()
if not token or token == "" then
error("[gitea.nvim] Missing Gitea token. Please run :Gitea again or restart after entering a token.")
end
return "token " .. token
end
local function request(method, endpoint, opts)
opts = opts or {}
local url = get_base_url() .. endpoint
local headers = opts.headers or {}
headers["Authorization"] = get_auth_header()
headers["Content-Type"] = "application/json"
local result = curl.request({
url = url,
method = method,
headers = headers,
timeout = 5000, -- we can also read config.values.timeout
body = opts.body and vim.json.encode(opts.body) or nil,
})
return result
end
-------------------------------------------------------
-- ISSUES
-------------------------------------------------------
function M.list_issues(owner, repo, opts)
local endpoint = string.format("/api/v1/repos/%s/%s/issues", owner, repo)
local result = request("GET", endpoint, { query = opts })
if result and result.status == 200 then
return vim.json.decode(result.body)
end
return nil, result and result.status
end
function M.get_issue(owner, repo, number)
local endpoint = string.format("/api/v1/repos/%s/%s/issues/%d", 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
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 result = request("POST", endpoint, { body = data })
if result and result.status == 201 then
return vim.json.decode(result.body)
end
return nil, result and result.status
end
function M.edit_issue(owner, repo, number, data)
local endpoint = string.format("/api/v1/repos/%s/%s/issues/%d", owner, repo, number)
local result = request("PATCH", endpoint, { body = data })
if result and result.status == 200 then
return vim.json.decode(result.body)
end
return nil, result and result.status
end
function M.close_issue(owner, repo, number)
-- Gitea: state -> "closed"
return M.edit_issue(owner, repo, number, { state = "closed" })
end
function M.reopen_issue(owner, repo, number)
return M.edit_issue(owner, repo, number, { state = "open" })
end
function M.comment_issue(owner, repo, number, body)
local endpoint = string.format("/api/v1/repos/%s/%s/issues/%d/comments", owner, repo, number)
local result = request("POST", endpoint, { body = { body = body } })
if result and result.status == 201 then
return vim.json.decode(result.body)
end
return nil, result and result.status
end
-------------------------------------------------------
-- PULL REQUESTS
-------------------------------------------------------
function M.list_pull_requests(owner, repo, opts)
local endpoint = string.format("/api/v1/repos/%s/%s/pulls", owner, repo)
local result = request("GET", endpoint, { query = opts })
if result and result.status == 200 then
return vim.json.decode(result.body)
end
return nil, result and result.status
end
function M.get_pull_request(owner, repo, number)
local endpoint = string.format("/api/v1/repos/%s/%s/pulls/%d", 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
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 result = request("POST", endpoint, { body = data })
if result and result.status == 201 then
return vim.json.decode(result.body)
end
return nil, result and result.status
end
function M.edit_pull_request(owner, repo, number, data)
local endpoint = string.format("/api/v1/repos/%s/%s/pulls/%d", owner, repo, number)
local result = request("PATCH", endpoint, { body = data })
if result and result.status == 200 then
return vim.json.decode(result.body)
end
return nil, result and result.status
end
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 users choice to Giteas).
local endpoint = string.format("/api/v1/repos/%s/%s/pulls/%d/merge", owner, repo, number)
local result = request("POST", endpoint, {
body = {
Do = merge_style or "merge", -- e.g. "merge"
MergeTitleField = merge_title or "",
MergeMessageField = merge_message or "",
}
})
if result and result.status == 200 then
return vim.json.decode(result.body)
end
return nil, result and result.status
end
function M.close_pull_request(owner, repo, number)
return M.edit_pull_request(owner, repo, number, { state = "closed" })
end
function M.reopen_pull_request(owner, repo, number)
return M.edit_pull_request(owner, repo, number, { state = "open" })
end
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)
end
return M

160
lua/gitea/auth.lua Normal file
View File

@@ -0,0 +1,160 @@
local config = require("gitea.config")
local uv = vim.loop
local M = {}
local token_cached = nil
-- Attempt to read the token file from disk
local function read_token_file(path)
local fd = uv.fs_open(path, "r", 438) -- 0666 in decimal
if not fd then return nil end
local stat = uv.fs_fstat(fd)
local data = uv.fs_read(fd, stat.size, 0)
uv.fs_close(fd)
return data
end
-- Write token file
local function write_token_file(path, token)
local fd = uv.fs_open(path, "w", 384) -- 0600 in decimal
if not fd then
error("[gitea.nvim] Failed to open token file for writing: " .. path)
end
uv.fs_write(fd, token, -1)
uv.fs_close(fd)
end
-- Checks if the token file is in .gitignore, if not ask user
local function ensure_ignored(token_file, ignore_file)
-- 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()
-- Attempt: use `git config remote.origin.url`
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
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
-- domain_and_path might be "gitea.myserver.com/owner/repo"
-- just extract "gitea.myserver.com"
local server = domain_and_path:match("^([^/]+)")
if not server then
return nil
end
return protocol .. server
end
-- Called during plugin setup or first command usage
function M.ensure_token()
if token_cached then
return token_cached
end
local cfg = config.values
local token_file = cfg.config_file
local ignore_file = cfg.ignore_file
-- 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
function M.get_token()
return token_cached
end
return M

443
lua/gitea/commands.lua Normal file
View File

@@ -0,0 +1,443 @@
local M = {}
local config = require("gitea.config")
local api = require("gitea.api")
local auth = require("gitea.auth")
-- Utility: parse "owner/repo" from the remote URL
local function detect_owner_repo()
local cmd = "git config remote.origin.url"
local url = vim.fn.systemlist(cmd)[1]
if vim.v.shell_error ~= 0 or not url or url == "" then
return nil, nil
end
-- Remove trailing ".git"
url = url:gsub("%.git$", "")
-- Try to parse: e.g. https://my.gitea.com/owner/repo
local _, after = url:match("^(https?://)(.*)$")
if after then
-- after might be "my.gitea.com/owner/repo"
-- let's capture the part after the first slash
local path = after:match("[^/]+/(.*)$") -- skip domain
if not path then
return nil, nil
end
local owner, repo = path:match("^(.-)/(.*)$")
return owner, repo
end
-- SSH style: git@my.gitea.com:owner/repo
local user_host, path = url:match("^(.-):(.*)$")
if user_host and path then
-- user_host might be "git@my.gitea.com"
local owner, repo = path:match("^(.-)/(.*)$")
return owner, repo
end
return nil, nil
end
-------------------------------
-- Subcommands
-------------------------------
local subcommands = {}
subcommands.issue = {
list = function(args)
local owner, repo = detect_owner_repo()
if not owner or not repo then
vim.notify("[gitea.nvim] Could not detect owner/repo.", vim.log.levels.ERROR)
return
end
local issues, status = api.list_issues(owner, repo, {})
if not issues then
vim.notify(string.format("[gitea.nvim] Error fetching issues (HTTP %s).", status or "?"), vim.log.levels.ERROR)
return
end
-- For now, print to message area
print("Issues for " .. owner .. "/" .. repo .. ":")
for _, issue in ipairs(issues) do
print(string.format("#%d %s", issue.number, issue.title))
end
end,
create = function(args)
local owner, repo = detect_owner_repo()
if not owner or not repo then
vim.notify("[gitea.nvim] Could not detect owner/repo.", vim.log.levels.ERROR)
return
end
-- We'll open a scratch buffer for user to fill in Title/Body
-- Then upon writing, we create the issue.
-- For brevity, let's do a simpler approach: ask input in command line
local title = vim.fn.input("Issue title: ")
local body = vim.fn.input("Issue body: ")
local created, status = api.create_issue(owner, repo, {
title = title,
body = body,
})
if created then
vim.notify("[gitea.nvim] Created issue #" .. created.number .. "!")
else
vim.notify(string.format("[gitea.nvim] Failed to create issue (HTTP %s).", status or "?"), vim.log.levels.ERROR)
end
end,
edit = function(args)
local number = tonumber(args[1])
if not number then
vim.notify("[gitea.nvim] Usage: :Gitea issue edit <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
})
vim.api.nvim_set_current_buf(buf)
end,
close = function(args)
local number = tonumber(args[1])
if not number then
vim.notify("[gitea.nvim] Usage: :Gitea issue close <number>", vim.log.levels.ERROR)
return
end
local owner, repo = detect_owner_repo()
local res, status = api.close_issue(owner, repo, number)
if res then
vim.notify("[gitea.nvim] Issue #"..number.." closed.")
else
vim.notify(string.format("[gitea.nvim] Failed to close issue (HTTP %s).", status or "?"), vim.log.levels.ERROR)
end
end,
reopen = function(args)
local number = tonumber(args[1])
if not number then
vim.notify("[gitea.nvim] Usage: :Gitea issue reopen <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 = {
list = function(args)
local owner, repo = detect_owner_repo()
if not owner or not repo then
vim.notify("[gitea.nvim] Could not detect owner/repo.", vim.log.levels.ERROR)
return
end
local prs, status = api.list_pull_requests(owner, repo, {})
if not prs then
vim.notify(string.format("[gitea.nvim] Error fetching PRs (HTTP %s).", status or "?"), vim.log.levels.ERROR)
return
end
print("Pull Requests for " .. owner .. "/" .. repo .. ":")
for _, pr in ipairs(prs) do
print(string.format("#%d %s (state=%s)", pr.number, pr.title, pr.state))
end
end,
create = function(args)
local owner, repo = detect_owner_repo()
if not owner or not repo then
vim.notify("[gitea.nvim] Could not detect owner/repo.", vim.log.levels.ERROR)
return
end
local title = vim.fn.input("PR title: ")
local body = vim.fn.input("PR body: ")
local base = vim.fn.input("Base branch (e.g. main): ")
local head = vim.fn.input("Head branch (e.g. feature/my-branch): ")
local created, status = api.create_pull_request(owner, repo, {
title = title,
body = body,
base = base,
head = head,
})
if created then
vim.notify("[gitea.nvim] Created PR #" .. created.number .. ".")
else
vim.notify(string.format("[gitea.nvim] Failed to create PR (HTTP %s).", status or "?"), vim.log.levels.ERROR)
end
end,
edit = function(args)
local number = tonumber(args[1])
if not number then
vim.notify("[gitea.nvim] Usage: :Gitea pr edit <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.
-------------------------------
-- Command dispatcher
-------------------------------
function M.register()
vim.api.nvim_create_user_command("Gitea", function(cmd_opts)
-- Ensure token is loaded (in case user runs a command after startup)
local _ = require("gitea.auth").ensure_token()
local args = {}
for w in string.gmatch(cmd_opts.args, "%S+") do
table.insert(args, w)
end
local object = args[1] -- "issue", "pr", etc.
local action = args[2] -- "list", "create", etc.
if not object then
print("Usage: :Gitea <object> <action> [args]")
print("Examples:")
print(" :Gitea issue list")
print(" :Gitea pr list")
return
end
if not subcommands[object] then
vim.notify("[gitea.nvim] Unknown object: "..object, vim.log.levels.ERROR)
return
end
if not action then
vim.notify("[gitea.nvim] Please specify an action for "..object, vim.log.levels.ERROR)
return
end
local fn = subcommands[object][action]
if not fn then
vim.notify(string.format("[gitea.nvim] Unknown action '%s' for object '%s'", action, object), vim.log.levels.ERROR)
return
end
-- remove first two tokens
table.remove(args, 1)
table.remove(args, 1)
fn(args)
end, {
nargs = "*",
complete = function(arg_lead, cmd_line, cursor_pos)
-- Very basic completion
local parts = vim.split(cmd_line, "%s+")
if #parts == 2 then
-- user typed: :Gitea <object-partial>
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

22
lua/gitea/config.lua Normal file
View File

@@ -0,0 +1,22 @@
local Config = {}
-- Default settings (user can override in setup{})
local defaults = {
config_file = ".gitea_nvim_token", -- local file containing the token
ignore_file = ".gitignore", -- file to which we can append the token file
server_url = nil, -- extracted from .git remote if possible
}
Config.values = {}
function Config.setup(user_opts)
for k, v in pairs(defaults) do
if user_opts[k] ~= nil then
Config.values[k] = user_opts[k]
else
Config.values[k] = v
end
end
end
return Config

18
lua/gitea/init.lua Normal file
View File

@@ -0,0 +1,18 @@
local M = {}
local config = require("gitea.config")
local commands = require("gitea.commands")
local auth = require("gitea.auth")
-- Main entrypoint
function M.setup(user_opts)
config.setup(user_opts or {})
-- Attempt to load existing token; if not found, prompt user
auth.ensure_token()
-- Register :Gitea command and subcommands
commands.register()
end
return M