syntax highlighting

This commit is contained in:
2024-12-23 19:54:52 +01:00
parent d8a7da82fe
commit 3d0d721896
3 changed files with 203 additions and 119 deletions

34
after/syntax/gitea.vim Normal file
View File

@@ -0,0 +1,34 @@
" quit when a syntax file was already loaded
if exists("b:current_syntax")
finish
endif
if !exists('main_syntax')
let main_syntax = 'gitea'
endif
runtime! syntax/markdown.vim ftplugin/markdown.vim ftplugin/markdown_*.vim ftplugin/markdown/*.vim
unlet! b:current_syntax
" Emoji conceal, similar to octo.nvim but for 'gitea'
call matchadd('Conceal', ':heart:', 10, -1, {'conceal':'❤️'})
call matchadd('Conceal', ':+1:', 10, -1, {'conceal':'👍'})
call matchadd('Conceal', ':see_no_evil:', 10, -1, {'conceal':'🙈'})
call matchadd('Conceal', ':laughing:', 10, -1, {'conceal':'😆'})
call matchadd('Conceal', ':thinking_face:', 10, -1, {'conceal':'🤔'})
call matchadd('Conceal', ':thinking:', 10, -1, {'conceal':'🤔'})
call matchadd('Conceal', ':ok_hand:', 10, -1, {'conceal':'👌'})
call matchadd('Conceal', ':upside_down_face:', 10, -1, {'conceal':'🙃'})
call matchadd('Conceal', ':grimacing:', 10, -1, {'conceal':'😬'})
call matchadd('Conceal', ':rocket:', 10, -1, {'conceal':'🚀'})
call matchadd('Conceal', ':blush:', 10, -1, {'conceal':'😊'})
call matchadd('Conceal', ':tada:', 10, -1, {'conceal':'🎉'})
call matchadd('Conceal', ':shrug:', 10, -1, {'conceal':'🤷'})
call matchadd('Conceal', ':man_shrugging:', 10, -1, {'conceal':'🤷'})
call matchadd('Conceal', ':face_palm:', 10, -1, {'conceal':'🤦'})
call matchadd('Conceal', ':man_facepalmin:', 10, -1, {'conceal':'🤦'})
let b:current_syntax = "gitea"
if main_syntax ==# 'gitea'
unlet main_syntax
endif

View File

@@ -3,8 +3,11 @@ local M = {}
local config = require("gitea.config") 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")
local highlights = require("gitea.highlights") -- we still require highlights
-- We'll keep detect_owner_repo() from before ------------------------------------------------------------------------------
-- DETECT OWNER/REPO
------------------------------------------------------------------------------
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]
@@ -29,39 +32,69 @@ local function detect_owner_repo()
return nil, nil return nil, nil
end end
-------------------------------------------------------------------------------- ------------------------------------------------------------------------------
--- Data Structures to track what's in the ephemeral buffer -- BUFFER METADATA
-------------------------------------------------------------------------------- ------------------------------------------------------------------------------
-- 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) local function create_issue_buffer_metadata(issue)
-- track lines for the title, the body, each comment return {
local meta = {
issue_number = issue.number, issue_number = issue.number,
-- will fill in fields once we place them in the buffer title_start = nil,
title_start = nil, title_end = nil,
title_end = nil, body_start = nil,
body_start = nil, body_end = nil,
body_end = nil, comments = {}, -- each = { id, start_line, end_line }
comments = {}, -- array of { id, start, end } new_comment_start = nil,
new_comment_start = nil, -- where user can type a new comment
} }
return meta
end end
-------------------------------------------------------------------------------- ------------------------------------------------------------------------------
--- RENDER ISSUE INTO BUFFER -- 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 function render_issue_into_buf(bufnr, issue, comments)
-- We assume the buffer is empty and ephemeral.
local lines = {} local lines = {}
table.insert(lines, string.format("# Gitea Issue #%d: %s", issue.number, issue.title)) table.insert(lines, string.format("# Gitea Issue #%d: %s", issue.number, issue.title))
table.insert(lines, "# STATE: " .. (issue.state or "unknown")) table.insert(lines, "# STATE: " .. (issue.state or "unknown"))
@@ -69,18 +102,13 @@ local function render_issue_into_buf(bufnr, issue, 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, "# 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, "") table.insert(lines, "")
-- store line index references for metadata
local meta = create_issue_buffer_metadata(issue) local meta = create_issue_buffer_metadata(issue)
meta.title_start = #lines + 1
-- place the "title line"
meta.title_start = #lines+1
table.insert(lines, issue.title or "") table.insert(lines, issue.title or "")
meta.title_end = #lines meta.title_end = #lines
-- a blank line
table.insert(lines, "") table.insert(lines, "")
-- place the "body lines" meta.body_start = #lines + 1
meta.body_start = #lines+1
if issue.body then if issue.body then
local body_lines = vim.split(issue.body, "\n", true) local body_lines = vim.split(issue.body, "\n", true)
if #body_lines == 0 then if #body_lines == 0 then
@@ -95,19 +123,16 @@ local function render_issue_into_buf(bufnr, issue, comments)
end end
meta.body_end = #lines meta.body_end = #lines
-- spacing
table.insert(lines, "") table.insert(lines, "")
table.insert(lines, "# --- Comments ---") table.insert(lines, "# --- Comments ---")
local line_count = #lines
local comment_meta = {} local comment_meta = {}
for _, c in ipairs(comments) do for _, c in ipairs(comments) do
table.insert(lines, "") table.insert(lines, "")
local start_line = #lines+1 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.username) or "?"
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)) 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) local c_body_lines = vim.split(c.body or "", "\n", true)
if #c_body_lines == 0 then if #c_body_lines == 0 then
table.insert(lines, "") table.insert(lines, "")
@@ -120,101 +145,73 @@ local function render_issue_into_buf(bufnr, issue, comments)
table.insert(comment_meta, { table.insert(comment_meta, {
id = c.id, id = c.id,
start_line = start_line, start_line = start_line,
end_line = end_line, end_line = end_line,
}) })
end end
-- place a blank line for user to add a new comment
table.insert(lines, "") table.insert(lines, "")
table.insert(lines, "# --- Add a new comment below this line. If you leave it blank, no new comment is created. ---") 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 meta.new_comment_start = #lines + 1
table.insert(lines, "") -- an empty line to start from 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) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
-- store metadata -- Now apply highlight groups
local b_meta = { apply_issue_highlights(bufnr, meta)
issue_number = issue.number,
title_start = meta.title_start, return meta
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 end
-------------------------------------------------------------------------------- ------------------------------------------------------------------------------
--- Parse buffer changes and do API updates on save -- ON SAVE (BufWriteCmd)
-------------------------------------------------------------------------------- ------------------------------------------------------------------------------
local function on_issue_buf_write(bufnr, metadata, owner, repo) 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) 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") 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+$", "") 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) local current_issue_data = api.get_issue(owner, repo, metadata.issue_number)
if not current_issue_data then if not current_issue_data then
vim.notify("[gitea.nvim] Could not re-fetch the issue to verify updates", vim.log.levels.ERROR) vim.notify("[gitea.nvim] Could not re-fetch the issue to verify updates", vim.log.levels.ERROR)
return return
end end
local changed_title = false local changed_title = (current_issue_data.title or "") ~= new_title
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_lines = vim.list_slice(new_lines, metadata.body_start, metadata.body_end)
local new_body = table.concat(new_body_lines, "\n") local new_body = table.concat(new_body_lines, "\n")
local changed_body = false local changed_body = (current_issue_data.body or "") ~= new_body
if (current_issue_data.body or "") ~= new_body then
changed_body = true
end
----------------- Comments update check -------------
local changed_comments = {} local changed_comments = {}
for _, cmeta in ipairs(metadata.comments) do for _, cmeta in ipairs(metadata.comments) do
local c_lines = vim.list_slice(new_lines, cmeta.start_line, cmeta.end_line) local c_lines = vim.list_slice(new_lines, cmeta.start_line, cmeta.end_line)
-- The first line is "COMMENT #<id> by <author>" -- first line => "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 = {} local c_body = {}
for i=2,#c_lines do for i = 2, #c_lines do
table.insert(c_body, c_lines[i]) table.insert(c_body, c_lines[i])
end end
local new_comment_body = table.concat(c_body, "\n") 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, { table.insert(changed_comments, {
id = cmeta.id, id = cmeta.id,
new_body = new_comment_body new_body = new_comment_body
}) })
end end
----------------- New comment check ------------------
local new_comment_body_lines = {} local new_comment_body_lines = {}
for i=metadata.new_comment_start, #new_lines do for i = metadata.new_comment_start, #new_lines do
table.insert(new_comment_body_lines, new_lines[i]) table.insert(new_comment_body_lines, new_lines[i])
end end
local new_comment_body = table.concat(new_comment_body_lines, "\n"):gsub("^%s+", ""):gsub("%s+$", "") local new_comment_body = table.concat(new_comment_body_lines, "\n"):gsub("^%s+", ""):gsub("%s+$", "")
local create_new_comment = (#new_comment_body > 0) local create_new_comment = (#new_comment_body > 0)
-------------- Perform the updates -------------- -- Edit issue if title or body changed
if changed_title or changed_body then if changed_title or changed_body then
local updated, st = api.edit_issue(owner, repo, metadata.issue_number, { local updated, st = api.edit_issue(owner, repo, metadata.issue_number, {
title = changed_title and new_title or current_issue_data.title, title = changed_title and new_title or current_issue_data.title,
body = changed_body and new_body or current_issue_data.body, body = changed_body and new_body or current_issue_data.body,
}) })
if updated then if updated then
vim.notify("[gitea.nvim] Issue updated!") vim.notify("[gitea.nvim] Issue updated!")
@@ -223,9 +220,7 @@ local function on_issue_buf_write(bufnr, metadata, owner, repo)
end end
end end
-- for each changed comment, attempt to patch -- Edit each comment
-- 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 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) local updated_comment, st = api.edit_issue_comment(owner, repo, metadata.issue_number, cupd.id, cupd.new_body)
if not updated_comment then if not updated_comment then
@@ -235,6 +230,7 @@ local function on_issue_buf_write(bufnr, metadata, owner, repo)
end end
end end
-- Possibly add a new comment
if create_new_comment then if create_new_comment then
local created, st = api.comment_issue(owner, repo, metadata.issue_number, new_comment_body) local created, st = api.comment_issue(owner, repo, metadata.issue_number, new_comment_body)
if created then if created then
@@ -245,9 +241,9 @@ local function on_issue_buf_write(bufnr, metadata, owner, repo)
end end
end end
-------------------------------------------------------------------------------- ------------------------------------------------------------------------------
--- Open a single ephemeral buffer for the entire Issue with comments -- OPEN ISSUE BUFFER
-------------------------------------------------------------------------------- ------------------------------------------------------------------------------
local function open_full_issue_buffer(owner, repo, number) local function open_full_issue_buffer(owner, repo, number)
local issue_data, status = api.get_issue(owner, repo, number) local issue_data, status = api.get_issue(owner, repo, number)
if not issue_data then if not issue_data then
@@ -255,9 +251,6 @@ local function open_full_issue_buffer(owner, repo, number)
return return
end 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) local all_comments, cstatus = api.get_issue_comments(owner, repo, number)
if not all_comments then 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) vim.notify(string.format("[gitea.nvim] Error retrieving issue #%d comments (HTTP %s).", number, cstatus or "?"), vim.log.levels.ERROR)
@@ -266,12 +259,18 @@ local function open_full_issue_buffer(owner, repo, number)
local buf = vim.api.nvim_create_buf(false, true) 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, "buftype", "acwrite")
vim.api.nvim_buf_set_option(buf, "filetype", "markdown")
-- 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)) 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) local metadata = render_issue_into_buf(buf, issue_data, all_comments)
-- We create an autocmd on BufWriteCmd to parse changes and do updates -- Auto command to handle saving
vim.api.nvim_create_autocmd("BufWriteCmd", { vim.api.nvim_create_autocmd("BufWriteCmd", {
buffer = buf, buffer = buf,
callback = function() callback = function()
@@ -282,9 +281,9 @@ local function open_full_issue_buffer(owner, repo, number)
vim.api.nvim_set_current_buf(buf) vim.api.nvim_set_current_buf(buf)
end end
-------------------------------------------------------------------------------- ------------------------------------------------------------------------------
--- SUBCOMMANDS -- SUBCOMMANDS
-------------------------------------------------------------------------------- ------------------------------------------------------------------------------
local subcommands = {} local subcommands = {}
subcommands.issue = { subcommands.issue = {
@@ -294,8 +293,7 @@ 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, _ = pcall(require, "telescope")
local has_telescope, tele = pcall(require, "telescope")
if not has_telescope then if not has_telescope then
vim.notify("[gitea.nvim] telescope.nvim is not installed", vim.log.levels.ERROR) vim.notify("[gitea.nvim] telescope.nvim is not installed", vim.log.levels.ERROR)
return return
@@ -333,7 +331,6 @@ subcommands.issue = {
if not selection or not selection.value then if not selection or not selection.value then
return return
end end
-- Instead of just editing, we now open the full timeline buffer
open_full_issue_buffer(owner, repo, selection.value.number) open_full_issue_buffer(owner, repo, selection.value.number)
end end
map("i", "<CR>", select_issue) map("i", "<CR>", select_issue)
@@ -344,7 +341,6 @@ subcommands.issue = {
end, end,
show = 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 show <number>", vim.log.levels.ERROR) vim.notify("[gitea.nvim] Usage: :Gitea issue show <number>", vim.log.levels.ERROR)
@@ -357,19 +353,15 @@ subcommands.issue = {
end end
open_full_issue_buffer(owner, repo, number) open_full_issue_buffer(owner, repo, number)
end, end,
-- The rest of your commands can remain or be removed as needed
-- but presumably we can just use "show" for everything
-- or keep an existing minimal create command, etc.
} }
subcommands.pr = { subcommands.pr = {
-- We'll leave your existing PR commands as is or remove for brevity -- your other PR-related commands, if any
} }
-------------------------------------------------------------------------------- ------------------------------------------------------------------------------
--- REGISTER -- REGISTER COMMAND
-------------------------------------------------------------------------------- ------------------------------------------------------------------------------
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)
auth.ensure_token() auth.ensure_token()
@@ -433,4 +425,4 @@ function M.register()
}) })
end end
return M return M

58
lua/gitea/highlights.lua Normal file
View File

@@ -0,0 +1,58 @@
local M = {}
-- A small Dracula-like palette, adapt or extend if needed
local palette = {
bg = "#282A36",
fg = "#F8F8F2",
selection = "#44475A",
comment = "#6272A4",
cyan = "#8BE9FD",
green = "#50FA7B",
orange = "#FFB86C",
pink = "#FF79C6",
purple = "#BD93F9",
red = "#FF5555",
yellow = "#F1FA8C",
}
function M.setup()
-- Title of an issue or PR
vim.api.nvim_set_hl(0, "GiteaIssueTitle", {
fg = palette.pink,
bold = true,
})
-- Meta lines, e.g. status lines or extra info
vim.api.nvim_set_hl(0, "GiteaIssueMeta", {
fg = palette.comment,
italic = true,
})
-- Heading for each comment block
vim.api.nvim_set_hl(0, "GiteaCommentHeading", {
fg = palette.orange,
bold = true,
})
-- The actual comment body text
vim.api.nvim_set_hl(0, "GiteaCommentBody", {
fg = palette.fg,
bg = nil, -- or palette.bg if you want a background
})
-- Something for a user mention
vim.api.nvim_set_hl(0, "GiteaUser", {
fg = palette.cyan,
bold = true,
})
-- If you'd like comment lines in a faint color
vim.api.nvim_set_hl(0, "GiteaInlineComment", {
fg = palette.comment,
italic = true,
})
-- ...Add or tweak any other highlight groups you need...
end
return M