From 3d0d721896cc633646c300f289e32cd5014356f6 Mon Sep 17 00:00:00 2001 From: Dominik Polakovics Date: Mon, 23 Dec 2024 19:54:52 +0100 Subject: [PATCH] syntax highlighting --- after/syntax/gitea.vim | 34 ++++++ lua/gitea/commands.lua | 230 +++++++++++++++++++-------------------- lua/gitea/highlights.lua | 58 ++++++++++ 3 files changed, 203 insertions(+), 119 deletions(-) create mode 100644 after/syntax/gitea.vim create mode 100644 lua/gitea/highlights.lua diff --git a/after/syntax/gitea.vim b/after/syntax/gitea.vim new file mode 100644 index 0000000..2f4cdae --- /dev/null +++ b/after/syntax/gitea.vim @@ -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 diff --git a/lua/gitea/commands.lua b/lua/gitea/commands.lua index 60b7b60..6336027 100644 --- a/lua/gitea/commands.lua +++ b/lua/gitea/commands.lua @@ -3,8 +3,11 @@ local M = {} local config = require("gitea.config") local api = require("gitea.api") local auth = require("gitea.auth") +local highlights = require("gitea.highlights") -- we still require highlights --- We'll keep detect_owner_repo() from before +------------------------------------------------------------------------------ +-- DETECT OWNER/REPO +------------------------------------------------------------------------------ local function detect_owner_repo() local cmd = "git config remote.origin.url" local url = vim.fn.systemlist(cmd)[1] @@ -29,39 +32,69 @@ local function detect_owner_repo() return nil, nil end --------------------------------------------------------------------------------- ---- 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. --------------------------------------------------------------------------------- - +------------------------------------------------------------------------------ +-- BUFFER METADATA +------------------------------------------------------------------------------ local function create_issue_buffer_metadata(issue) - -- track lines for the title, the body, each comment - local meta = { + return { 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 + title_start = nil, + title_end = nil, + body_start = nil, + body_end = nil, + comments = {}, -- each = { id, start_line, end_line } + new_comment_start = nil, } - return meta end --------------------------------------------------------------------------------- ---- RENDER ISSUE INTO BUFFER --------------------------------------------------------------------------------- +------------------------------------------------------------------------------ +-- APPLY HIGHLIGHTS +------------------------------------------------------------------------------ +local function apply_issue_highlights(bufnr, meta) + -- The first line in the file is line 0 in Lua’s API, so we subtract 1 + -- from meta.* to convert to 0-based indexing. + + ------------------------------------------------------------------------ + -- Lines #0 & #1 hold meta info (β€œ# Gitea Issue #...”, β€œ# STATE: ...”) + ------------------------------------------------------------------------ + vim.api.nvim_buf_add_highlight(bufnr, 0, "GiteaIssueMeta", 0, 0, -1) + vim.api.nvim_buf_add_highlight(bufnr, 0, "GiteaIssueMeta", 1, 0, -1) + + ------------------------------------------------------------------------ + -- Title + ------------------------------------------------------------------------ + local title_line_0 = meta.title_start - 1 + vim.api.nvim_buf_add_highlight(bufnr, 0, "GiteaIssueTitle", title_line_0, 0, -1) + + ------------------------------------------------------------------------ + -- Body lines + ------------------------------------------------------------------------ + -- meta.body_start .. meta.body_end (inclusive, 1-based) + for line = meta.body_start, meta.body_end do + local line_0 = line - 1 + vim.api.nvim_buf_add_highlight(bufnr, 0, "GiteaCommentBody", line_0, 0, -1) + end + + ------------------------------------------------------------------------ + -- Comment headings & bodies + ------------------------------------------------------------------------ + for _, c in ipairs(meta.comments) do + -- The heading line has β€œCOMMENT #123 by user” + local heading_line_0 = c.start_line - 1 + vim.api.nvim_buf_add_highlight(bufnr, 0, "GiteaCommentHeading", heading_line_0, 0, -1) + + -- The actual comment text is lines [heading_line_0+1 .. c.end_line-1] + for text_line = (c.start_line + 1), c.end_line do + local text_line_0 = text_line - 1 + vim.api.nvim_buf_add_highlight(bufnr, 0, "GiteaCommentBody", text_line_0, 0, -1) + end + end +end + +------------------------------------------------------------------------------ +-- RENDER ISSUE INTO BUFFER +------------------------------------------------------------------------------ local function render_issue_into_buf(bufnr, issue, comments) - -- 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")) @@ -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, "") - -- store line index references for metadata local meta = create_issue_buffer_metadata(issue) - - -- place the "title line" - meta.title_start = #lines+1 + 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 + meta.body_start = #lines + 1 if issue.body then local body_lines = vim.split(issue.body, "\n", true) if #body_lines == 0 then @@ -95,19 +123,16 @@ local function render_issue_into_buf(bufnr, issue, comments) 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 "?" + local start_line = #lines + 1 + local author = c.user and (c.user.login or c.user.username) or "?" table.insert(lines, string.format("COMMENT #%d by %s", c.id, author)) - -- 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, "") @@ -120,101 +145,73 @@ local function render_issue_into_buf(bufnr, issue, comments) table.insert(comment_meta, { id = c.id, start_line = start_line, - end_line = end_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 + meta.new_comment_start = #lines + 1 + table.insert(lines, "") + -- Save comment meta + meta.comments = comment_meta + + -- Put all lines into the buffer vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - -- 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 + -- Now apply highlight groups + apply_issue_highlights(bufnr, meta) + + return meta end --------------------------------------------------------------------------------- ---- Parse buffer changes and do API updates on save --------------------------------------------------------------------------------- +------------------------------------------------------------------------------ +-- ON SAVE (BufWriteCmd) +------------------------------------------------------------------------------ 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 changed_title = (current_issue_data.title or "") ~= new_title local new_body_lines = vim.list_slice(new_lines, metadata.body_start, metadata.body_end) local new_body = table.concat(new_body_lines, "\n") - local changed_body = false - if (current_issue_data.body or "") ~= new_body then - changed_body = true - end + local changed_body = (current_issue_data.body or "") ~= new_body - ----------------- 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 # by " - -- The rest is the actual body - -- e.g. `COMMENT #112 by alice` - local first_line = c_lines[1] or "" + -- first line => "COMMENT # by " local c_body = {} - for i=2,#c_lines do + 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, + 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 + 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 -------------- + -- Edit issue if title or body changed if changed_title or changed_body then local updated, st = api.edit_issue(owner, repo, metadata.issue_number, { title = changed_title and new_title or current_issue_data.title, - body = changed_body and new_body or current_issue_data.body, + body = changed_body and new_body or current_issue_data.body, }) if updated then vim.notify("[gitea.nvim] Issue updated!") @@ -223,9 +220,7 @@ local function on_issue_buf_write(bufnr, metadata, owner, repo) 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 + -- Edit each comment for _, cupd in ipairs(changed_comments) do local updated_comment, st = api.edit_issue_comment(owner, repo, metadata.issue_number, cupd.id, cupd.new_body) if not updated_comment then @@ -235,6 +230,7 @@ local function on_issue_buf_write(bufnr, metadata, owner, repo) end end + -- Possibly add a new comment if create_new_comment then local created, st = api.comment_issue(owner, repo, metadata.issue_number, new_comment_body) if created then @@ -245,9 +241,9 @@ local function on_issue_buf_write(bufnr, metadata, owner, repo) 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 issue_data, status = api.get_issue(owner, repo, number) if not issue_data then @@ -255,9 +251,6 @@ local function open_full_issue_buffer(owner, repo, number) 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) @@ -266,12 +259,18 @@ local function open_full_issue_buffer(owner, repo, number) 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") + + -- Use our new "gitea" filetype, so after/syntax/gitea.vim will load + vim.api.nvim_buf_set_option(buf, "filetype", "gitea") + vim.api.nvim_buf_set_name(buf, string.format("gitea_issue_full_%d", number)) + -- Load highlight definitions (e.g. GiteaIssueTitle, GiteaCommentBody, etc.) + highlights.setup() + local metadata = render_issue_into_buf(buf, issue_data, all_comments) - -- We create an autocmd on BufWriteCmd to parse changes and do updates + -- Auto command to handle saving vim.api.nvim_create_autocmd("BufWriteCmd", { buffer = buf, callback = function() @@ -282,9 +281,9 @@ local function open_full_issue_buffer(owner, repo, number) vim.api.nvim_set_current_buf(buf) end --------------------------------------------------------------------------------- ---- SUBCOMMANDS --------------------------------------------------------------------------------- +------------------------------------------------------------------------------ +-- SUBCOMMANDS +------------------------------------------------------------------------------ local subcommands = {} subcommands.issue = { @@ -294,8 +293,7 @@ subcommands.issue = { vim.notify("[gitea.nvim] Could not detect owner/repo.", vim.log.levels.ERROR) return end - -- use the same telescope approach as before: - local has_telescope, tele = pcall(require, "telescope") + local has_telescope, _ = pcall(require, "telescope") if not has_telescope then vim.notify("[gitea.nvim] telescope.nvim is not installed", vim.log.levels.ERROR) return @@ -333,7 +331,6 @@ subcommands.issue = { 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", "", select_issue) @@ -344,7 +341,6 @@ subcommands.issue = { end, show = function(args) - -- Alternative: show a single issue by number local number = tonumber(args[1]) if not number then vim.notify("[gitea.nvim] Usage: :Gitea issue show ", vim.log.levels.ERROR) @@ -357,19 +353,15 @@ subcommands.issue = { end open_full_issue_buffer(owner, repo, number) 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 = { - -- 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() vim.api.nvim_create_user_command("Gitea", function(cmd_opts) auth.ensure_token() @@ -433,4 +425,4 @@ function M.register() }) end -return M \ No newline at end of file +return M diff --git a/lua/gitea/highlights.lua b/lua/gitea/highlights.lua new file mode 100644 index 0000000..7947acc --- /dev/null +++ b/lua/gitea/highlights.lua @@ -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 \ No newline at end of file