From 168a3b5b958191ec3695dc3cb1e60dce2b433ae2 Mon Sep 17 00:00:00 2001 From: Shawon Date: Mon, 12 Jan 2026 07:50:02 +0600 Subject: [PATCH 01/19] chore: Added parser template for `asciidoc` --- lua/markview/parsers/asciidoc.lua | 116 ++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 lua/markview/parsers/asciidoc.lua diff --git a/lua/markview/parsers/asciidoc.lua b/lua/markview/parsers/asciidoc.lua new file mode 100644 index 0000000..f564d90 --- /dev/null +++ b/lua/markview/parsers/asciidoc.lua @@ -0,0 +1,116 @@ +--- HTML parser for `markview.nvim`. +local asciidoc = {}; + +--- Queried contents +---@type table[] +asciidoc.content = {}; + +--- Queried contents, but sorted +---@type { [string]: table } +asciidoc.sorted = {} + +--- Wrapper for `table.insert()`. +---@param data table +asciidoc.insert = function (data) + table.insert(asciidoc.content, data); + + if not asciidoc.sorted[data.class] then + asciidoc.sorted[data.class] = {}; + end + + table.insert(asciidoc.sorted[data.class], data); +end + +--- Heading element parser +---@param buffer integer +---@param TSNode table +---@param text string[] +---@param range markview.parsed.range +asciidoc.heading = function (buffer, TSNode, text, range) + local tag = vim.treesitter.get_node_text(TSNode:named_child(0):named_child(0), buffer); + + asciidoc.insert({ + class = "asciidoc_heading", + level = tonumber(tag:match("^h(%d)$")), + + text = text, + range = range + }); +end + +--- HTML parser +---@param buffer integer +---@param TSTree table +---@param from integer? +---@param to integer? +---@return markview.parsed.asciidoc[] +---@return markview.parsed.asciidoc_sorted +asciidoc.parse = function (buffer, TSTree, from, to) + -- Clear the previous contents + asciidoc.sorted = {}; + asciidoc.content = {}; + + local scanned_queries = vim.treesitter.query.parse("asciidoc", [[ + ]]); + + for capture_id, capture_node, _, _ in scanned_queries:iter_captures(TSTree:root(), buffer, from, to) do + local capture_name = scanned_queries.captures[capture_id]; + + if not capture_name:match("^asciidoc%.") then + goto continue; + end + + ---@type string? + local capture_text = vim.treesitter.get_node_text(capture_node, buffer); + local r_start, c_start, r_end, c_end = capture_node:range(); + + if capture_text == nil then + goto continue; + end + + if not capture_text:match("\n$") then + capture_text = capture_text .. "\n"; + end + + local lines = {}; + + for line in capture_text:gmatch("(.-)\n") do + table.insert(lines, line); + end + + ---@type boolean, string + local success, error = pcall( + asciidoc[capture_name:gsub("^asciidoc%.", "")], + + buffer, + capture_node, + lines, + { + row_start = r_start, + col_start = c_start, + + row_end = r_end, + col_end = c_end + } + ); + + if success == false then + require("markview.health").print({ + kind = "ERR", + + from = "parsers/asciidoc.lua", + fn = "parse()", + + message = { + { tostring(error), "DiagnosticError" } + } + }); + end + + ::continue:: + end + + return asciidoc.content, asciidoc.sorted; +end + +return asciidoc; From 966a713f3d28cb59f6cb8defa7b101561bac4a89 Mon Sep 17 00:00:00 2001 From: Shawon Date: Mon, 12 Jan 2026 16:00:02 +0600 Subject: [PATCH 02/19] feat(asciidoc): Added `document title` support --- lua/markview/config/asciidoc.lua | 7 ++ lua/markview/parser.lua | 1 + lua/markview/parsers/asciidoc.lua | 13 ++-- lua/markview/renderer.lua | 5 ++ lua/markview/renderers/asciidoc.lua | 82 +++++++++++++++++++++++ lua/markview/spec.lua | 3 +- lua/markview/types/parsers/asciidoc.lua | 9 +++ lua/markview/types/renderers/asciidoc.lua | 13 ++++ test/asciidoc.adoc | 3 + 9 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 lua/markview/config/asciidoc.lua create mode 100644 lua/markview/renderers/asciidoc.lua create mode 100644 lua/markview/types/parsers/asciidoc.lua create mode 100644 lua/markview/types/renderers/asciidoc.lua create mode 100644 test/asciidoc.adoc diff --git a/lua/markview/config/asciidoc.lua b/lua/markview/config/asciidoc.lua new file mode 100644 index 0000000..64d76cb --- /dev/null +++ b/lua/markview/config/asciidoc.lua @@ -0,0 +1,7 @@ +return { + document_title = { + icon = "H ", + line_hl = "MarkviewHeading1", + }, + section_title = {}, +}; diff --git a/lua/markview/parser.lua b/lua/markview/parser.lua index aa7a82b..c39533a 100644 --- a/lua/markview/parser.lua +++ b/lua/markview/parser.lua @@ -99,6 +99,7 @@ parser.init = function (buffer, from, to, cache) ---|fS local _parsers = { + asciidoc = require("markview.parsers.asciidoc"), comment = require("markview.parsers.comment"); markdown = require("markview.parsers.markdown"); markdown_inline = require("markview.parsers.markdown_inline"); diff --git a/lua/markview/parsers/asciidoc.lua b/lua/markview/parsers/asciidoc.lua index f564d90..2c6f98c 100644 --- a/lua/markview/parsers/asciidoc.lua +++ b/lua/markview/parsers/asciidoc.lua @@ -1,6 +1,10 @@ --- HTML parser for `markview.nvim`. local asciidoc = {}; +asciidoc.data = { + document_title = nil, +}; + --- Queried contents ---@type table[] asciidoc.content = {}; @@ -21,17 +25,13 @@ asciidoc.insert = function (data) table.insert(asciidoc.sorted[data.class], data); end ---- Heading element parser ---@param buffer integer ---@param TSNode table ---@param text string[] ---@param range markview.parsed.range -asciidoc.heading = function (buffer, TSNode, text, range) - local tag = vim.treesitter.get_node_text(TSNode:named_child(0):named_child(0), buffer); - +asciidoc.doc_title = function (buffer, TSNode, text, range) asciidoc.insert({ - class = "asciidoc_heading", - level = tonumber(tag:match("^h(%d)$")), + class = "asciidoc_document_title", text = text, range = range @@ -51,6 +51,7 @@ asciidoc.parse = function (buffer, TSTree, from, to) asciidoc.content = {}; local scanned_queries = vim.treesitter.query.parse("asciidoc", [[ + (document_title) @asciidoc.doc_title ]]); for capture_id, capture_node, _, _ in scanned_queries:iter_captures(TSTree:root(), buffer, from, to) do diff --git a/lua/markview/renderer.lua b/lua/markview/renderer.lua index 3beaf54..a7a62d3 100644 --- a/lua/markview/renderer.lua +++ b/lua/markview/renderer.lua @@ -15,6 +15,9 @@ renderer.__filter_cache = { renderer.option_maps = { ---|fS + asciidoc = { + document_title = { "asciidoc_document_title" }, + }, comment = { autolinks = { "comment_autolink" }, code_blocks = { "comment_code_block" }, @@ -366,6 +369,7 @@ renderer.render = function (buffer, parsed_content) ---|fS local _renderers = { + asciidoc = require("markview.renderers.asciidoc"), comment = require("markview.renderers.comment"), html = require("markview.renderers.html"), markdown = require("markview.renderers.markdown"), @@ -470,6 +474,7 @@ renderer.clear = function (buffer, from, to, hybrid_mode) ---|fS local _renderers = { + asciidoc = require("markview.renderers.asciidoc"), comment = require("markview.renderers.comment"); html = require("markview.renderers.html"); markdown = require("markview.renderers.markdown"); diff --git a/lua/markview/renderers/asciidoc.lua b/lua/markview/renderers/asciidoc.lua new file mode 100644 index 0000000..90f19b3 --- /dev/null +++ b/lua/markview/renderers/asciidoc.lua @@ -0,0 +1,82 @@ +local asciidoc = {}; + +local utils = require("markview.utils"); +local spec = require("markview.spec"); + +asciidoc.ns = vim.api.nvim_create_namespace("markview/asciidoc"); + +--- Renders void elements +---@param buffer integer +---@param item markview.parsed.asciidoc.document_titles +asciidoc.document_title = function (buffer, item) + ---@type markview.config.asciidoc.document_titles? + local config = spec.get({ "asciidoc", "document_titles" }, { eval_args = { buffer, item } }); + + if not config then + return; + end + + local range = item.range; + + utils.set_extmark(buffer, asciidoc.ns, range.row_start, range.col_start, { + -- Remove `#+%s*` amount of characters. + end_col = range.col_start + 1, + conceal = "", + + sign_text = tostring(config.sign or ""), + sign_hl_group = utils.set_hl(config.sign_hl), + + virt_text = { + { config.icon, config.icon_hl or config.hl }, + }, + line_hl_group = utils.set_hl(config.hl), + }); +end + +--- Renders HTML elements +---@param buffer integer +---@param content markview.parsed.asciidoc[] +asciidoc.render = function (buffer, content) + asciidoc.cache = { + font_regions = {}, + style_regions = { + superscripts = {}, + subscripts = {} + }, + }; + + local custom = spec.get({ "renderers" }, { fallback = {} }); + + for _, item in ipairs(content or {}) do + local success, err; + + if custom[item.class] then + success, err = pcall(custom[item.class], asciidoc.ns, buffer, item); + else + success, err = pcall(asciidoc[item.class:gsub("^asciidoc_", "")], buffer, item); + end + + if success == false then + require("markview.health").print({ + kind = "ERR", + + from = "renderers/asciidoc.lua", + fn = "render() -> " .. item.class, + + message = { + { tostring(err), "DiagnosticError" } + } + }); + end + end +end + +--- Clears decorations of HTML elements +---@param buffer integer +---@param from integer +---@param to integer +asciidoc.clear = function (buffer, from, to) + vim.api.nvim_buf_clear_namespace(buffer, asciidoc.ns, from or 0, to or -1); +end + +return asciidoc; diff --git a/lua/markview/spec.lua b/lua/markview/spec.lua index d35690f..a287bca 100644 --- a/lua/markview/spec.lua +++ b/lua/markview/spec.lua @@ -189,7 +189,7 @@ spec.default = { debounce = 150, icon_provider = "internal", - filetypes = { "markdown", "quarto", "rmd", "typst", }, + filetypes = { "markdown", "quarto", "rmd", "typst", "asciidoc", }, ignore_buftypes = { "nofile" }, condition = function (buffer) local is_enabled = spec.get({ "experimental", "fancy_comments" }, { @@ -227,6 +227,7 @@ spec.default = { ---@type string[] Properties that should be sourced *externally*. spec.__external_config = { + "asciidoc", "comment", "html", "markdown", diff --git a/lua/markview/types/parsers/asciidoc.lua b/lua/markview/types/parsers/asciidoc.lua new file mode 100644 index 0000000..756e86e --- /dev/null +++ b/lua/markview/types/parsers/asciidoc.lua @@ -0,0 +1,9 @@ +---@meta + +---@class markview.parsed.asciidoc.document_titles +--- +---@field class_name "asciidoc_document_title" +--- +---@field text string[] +---@field range markview.parsed.range + diff --git a/lua/markview/types/renderers/asciidoc.lua b/lua/markview/types/renderers/asciidoc.lua new file mode 100644 index 0000000..cd9b2c2 --- /dev/null +++ b/lua/markview/types/renderers/asciidoc.lua @@ -0,0 +1,13 @@ +---@meta + +---@class markview.config.asciidoc.document_titles +--- +---@field enable boolean +--- +---@field sign? string +---@field sign_hl? string +--- +---@field icon? string +---@field icon_hl? string +--- +---@field hl? string diff --git a/test/asciidoc.adoc b/test/asciidoc.adoc new file mode 100644 index 0000000..6c39ed8 --- /dev/null +++ b/test/asciidoc.adoc @@ -0,0 +1,3 @@ += Hello world + + From 4f45dad306acecbaa637913f7765f5ba5944fd80 Mon Sep 17 00:00:00 2001 From: Shawon Date: Mon, 12 Jan 2026 22:30:26 +0600 Subject: [PATCH 03/19] fewt(config, asciidoc): Updated document title config --- lua/markview/config/asciidoc.lua | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lua/markview/config/asciidoc.lua b/lua/markview/config/asciidoc.lua index 64d76cb..9e19db1 100644 --- a/lua/markview/config/asciidoc.lua +++ b/lua/markview/config/asciidoc.lua @@ -1,7 +1,13 @@ +---@type markview.config.asciidoc return { - document_title = { - icon = "H ", - line_hl = "MarkviewHeading1", + document_titles = { + enable = true, + + sign = "󰛓 ", + sign_hl = "MarkviewPalette7Sign", + + icon = "󰛓 ", + hl = "MarkviewPalette7", }, - section_title = {}, + section_titles = {}, }; From 33e897d8404b1fb97cb8aec3648a6bb36b7230be Mon Sep 17 00:00:00 2001 From: Shawon Date: Mon, 12 Jan 2026 22:30:49 +0600 Subject: [PATCH 04/19] fix(renderers, asciidoc, document_titles): Correctly handle conceal --- lua/markview/renderers/asciidoc.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/markview/renderers/asciidoc.lua b/lua/markview/renderers/asciidoc.lua index 90f19b3..9e49a3c 100644 --- a/lua/markview/renderers/asciidoc.lua +++ b/lua/markview/renderers/asciidoc.lua @@ -19,8 +19,8 @@ asciidoc.document_title = function (buffer, item) local range = item.range; utils.set_extmark(buffer, asciidoc.ns, range.row_start, range.col_start, { - -- Remove `#+%s*` amount of characters. - end_col = range.col_start + 1, + -- Remove `=%s*` amount of characters. + end_col = range.col_start + #string.match(item.text[1] or "", "=+%s*"), conceal = "", sign_text = tostring(config.sign or ""), From d313c1db77c86bc81ce815ca726568efc76065d5 Mon Sep 17 00:00:00 2001 From: Shawon Date: Mon, 12 Jan 2026 22:31:52 +0600 Subject: [PATCH 05/19] doc(asciidoc): Added missing types --- lua/markview/types/parsers/asciidoc.lua | 13 +++++++++++++ lua/markview/types/renderers/asciidoc.lua | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/lua/markview/types/parsers/asciidoc.lua b/lua/markview/types/parsers/asciidoc.lua index 756e86e..5e12778 100644 --- a/lua/markview/types/parsers/asciidoc.lua +++ b/lua/markview/types/parsers/asciidoc.lua @@ -1,5 +1,7 @@ ---@meta +------------------------------------------------------------------------------ + ---@class markview.parsed.asciidoc.document_titles --- ---@field class_name "asciidoc_document_title" @@ -7,3 +9,14 @@ ---@field text string[] ---@field range markview.parsed.range +------------------------------------------------------------------------------ + +---@alias markview.parsed.asciidoc +---| markview.config.asciidoc.document_titles + +------------------------------------------------------------------------------ + +---@class markview.parsed.asciidoc_sorted +--- +---@field document_titles markview.config.asciidoc.document_titles[] + diff --git a/lua/markview/types/renderers/asciidoc.lua b/lua/markview/types/renderers/asciidoc.lua index cd9b2c2..dc9f4d2 100644 --- a/lua/markview/types/renderers/asciidoc.lua +++ b/lua/markview/types/renderers/asciidoc.lua @@ -1,5 +1,7 @@ ---@meta +------------------------------------------------------------------------------ + ---@class markview.config.asciidoc.document_titles --- ---@field enable boolean @@ -11,3 +13,10 @@ ---@field icon_hl? string --- ---@field hl? string + +------------------------------------------------------------------------------ + +---@class markview.config.asciidoc +--- +---@field document_titles markview.config.asciidoc.document_titles + From 7da3aea514199505e13c83bbc3a84200c614cacb Mon Sep 17 00:00:00 2001 From: Shawon Date: Mon, 12 Jan 2026 22:48:46 +0600 Subject: [PATCH 06/19] feat(asciidoc): Added document attributes --- lua/markview/config/asciidoc.lua | 4 +++- lua/markview/parsers/asciidoc.lua | 18 +++++++++++++++--- lua/markview/renderers/asciidoc.lua | 19 ++++++++++++++++++- lua/markview/types/parsers/asciidoc.lua | 11 +++++++++++ lua/markview/types/renderers/asciidoc.lua | 7 +++++++ test/asciidoc.adoc | 5 +++++ 6 files changed, 59 insertions(+), 5 deletions(-) diff --git a/lua/markview/config/asciidoc.lua b/lua/markview/config/asciidoc.lua index 9e19db1..e75bb72 100644 --- a/lua/markview/config/asciidoc.lua +++ b/lua/markview/config/asciidoc.lua @@ -9,5 +9,7 @@ return { icon = "󰛓 ", hl = "MarkviewPalette7", }, - section_titles = {}, + document_attributes = { + enable = true, + }, }; diff --git a/lua/markview/parsers/asciidoc.lua b/lua/markview/parsers/asciidoc.lua index 2c6f98c..a87412d 100644 --- a/lua/markview/parsers/asciidoc.lua +++ b/lua/markview/parsers/asciidoc.lua @@ -25,11 +25,11 @@ asciidoc.insert = function (data) table.insert(asciidoc.sorted[data.class], data); end ----@param buffer integer ----@param TSNode table ---@param text string[] ---@param range markview.parsed.range -asciidoc.doc_title = function (buffer, TSNode, text, range) +asciidoc.doc_title = function (_, _, text, range) + asciidoc.data.document_title = string.match(text[1] or "", "=%s+(.*)$") + asciidoc.insert({ class = "asciidoc_document_title", @@ -38,6 +38,17 @@ asciidoc.doc_title = function (buffer, TSNode, text, range) }); end +---@param text string[] +---@param range markview.parsed.range +asciidoc.doc_attr = function (_, _, text, range) + asciidoc.insert({ + class = "asciidoc_document_attribute", + + text = text, + range = range + }); +end + --- HTML parser ---@param buffer integer ---@param TSTree table @@ -52,6 +63,7 @@ asciidoc.parse = function (buffer, TSTree, from, to) local scanned_queries = vim.treesitter.query.parse("asciidoc", [[ (document_title) @asciidoc.doc_title + (document_attr) @asciidoc.doc_attr ]]); for capture_id, capture_node, _, _ in scanned_queries:iter_captures(TSTree:root(), buffer, from, to) do diff --git a/lua/markview/renderers/asciidoc.lua b/lua/markview/renderers/asciidoc.lua index 9e49a3c..8790f55 100644 --- a/lua/markview/renderers/asciidoc.lua +++ b/lua/markview/renderers/asciidoc.lua @@ -5,7 +5,24 @@ local spec = require("markview.spec"); asciidoc.ns = vim.api.nvim_create_namespace("markview/asciidoc"); ---- Renders void elements +---@param buffer integer +---@param item markview.parsed.asciidoc.document_titles +asciidoc.document_attribute = function (buffer, item) + ---@type markview.config.asciidoc.document_titles? + local config = spec.get({ "asciidoc", "document_attributes" }, { eval_args = { buffer, item } }); + + if not config then + return; + end + + local range = item.range; + + utils.set_extmark(buffer, asciidoc.ns, range.row_start, range.col_start, { + end_row = range.row_end - 1, + conceal_lines = "", + }); +end + ---@param buffer integer ---@param item markview.parsed.asciidoc.document_titles asciidoc.document_title = function (buffer, item) diff --git a/lua/markview/types/parsers/asciidoc.lua b/lua/markview/types/parsers/asciidoc.lua index 5e12778..f8b80e2 100644 --- a/lua/markview/types/parsers/asciidoc.lua +++ b/lua/markview/types/parsers/asciidoc.lua @@ -2,6 +2,15 @@ ------------------------------------------------------------------------------ +---@class markview.parsed.asciidoc.document_attributes +--- +---@field class_name "asciidoc_document_attribute" +--- +---@field text string[] +---@field range markview.parsed.range + +------------------------------------------------------------------------------ + ---@class markview.parsed.asciidoc.document_titles --- ---@field class_name "asciidoc_document_title" @@ -12,11 +21,13 @@ ------------------------------------------------------------------------------ ---@alias markview.parsed.asciidoc +---| markview.config.asciidoc.document_attributes ---| markview.config.asciidoc.document_titles ------------------------------------------------------------------------------ ---@class markview.parsed.asciidoc_sorted --- +---@field document_attributes markview.config.asciidoc.document_attributes[] ---@field document_titles markview.config.asciidoc.document_titles[] diff --git a/lua/markview/types/renderers/asciidoc.lua b/lua/markview/types/renderers/asciidoc.lua index dc9f4d2..1626321 100644 --- a/lua/markview/types/renderers/asciidoc.lua +++ b/lua/markview/types/renderers/asciidoc.lua @@ -2,6 +2,12 @@ ------------------------------------------------------------------------------ +---@class markview.config.asciidoc.document_attributes +--- +---@field enable boolean + +------------------------------------------------------------------------------ + ---@class markview.config.asciidoc.document_titles --- ---@field enable boolean @@ -18,5 +24,6 @@ ---@class markview.config.asciidoc --- +---@field document_attributes markview.config.asciidoc.document_attributes ---@field document_titles markview.config.asciidoc.document_titles diff --git a/test/asciidoc.adoc b/test/asciidoc.adoc index 6c39ed8..91123af 100644 --- a/test/asciidoc.adoc +++ b/test/asciidoc.adoc @@ -1,3 +1,8 @@ = Hello world +:bye: +hi +:hi: 123 + + From 75ae830af19853de5dacdd84f440be711c9003e0 Mon Sep 17 00:00:00 2001 From: Shawon Date: Tue, 13 Jan 2026 17:22:52 +0600 Subject: [PATCH 07/19] feat(asciidoc_inline): Added parser template --- lua/markview/config/asciidoc_inline.lua | 5 + lua/markview/parser.lua | 1 + lua/markview/parsers/asciidoc.lua | 12 +- lua/markview/parsers/asciidoc_inline.lua | 123 +++++++++++++++++++++ lua/markview/renderer.lua | 4 + lua/markview/renderers/asciidoc.lua | 1 - lua/markview/renderers/asciidoc_inline.lua | 108 ++++++++++++++++++ lua/markview/spec.lua | 1 + test/asciidoc.adoc | 33 ++++++ 9 files changed, 281 insertions(+), 7 deletions(-) create mode 100644 lua/markview/config/asciidoc_inline.lua create mode 100644 lua/markview/parsers/asciidoc_inline.lua create mode 100644 lua/markview/renderers/asciidoc_inline.lua diff --git a/lua/markview/config/asciidoc_inline.lua b/lua/markview/config/asciidoc_inline.lua new file mode 100644 index 0000000..35be288 --- /dev/null +++ b/lua/markview/config/asciidoc_inline.lua @@ -0,0 +1,5 @@ +---@type markview.config.asciidoc +return { + bolds = { enable = true }, + italics = { enable = true }, +}; diff --git a/lua/markview/parser.lua b/lua/markview/parser.lua index c39533a..d9e75a1 100644 --- a/lua/markview/parser.lua +++ b/lua/markview/parser.lua @@ -100,6 +100,7 @@ parser.init = function (buffer, from, to, cache) local _parsers = { asciidoc = require("markview.parsers.asciidoc"), + asciidoc_inline = require("markview.parsers.asciidoc_inline"), comment = require("markview.parsers.comment"); markdown = require("markview.parsers.markdown"); markdown_inline = require("markview.parsers.markdown_inline"); diff --git a/lua/markview/parsers/asciidoc.lua b/lua/markview/parsers/asciidoc.lua index a87412d..96e8cae 100644 --- a/lua/markview/parsers/asciidoc.lua +++ b/lua/markview/parsers/asciidoc.lua @@ -27,11 +27,9 @@ end ---@param text string[] ---@param range markview.parsed.range -asciidoc.doc_title = function (_, _, text, range) - asciidoc.data.document_title = string.match(text[1] or "", "=%s+(.*)$") - +asciidoc.doc_attr = function (_, _, text, range) asciidoc.insert({ - class = "asciidoc_document_title", + class = "asciidoc_document_attribute", text = text, range = range @@ -40,9 +38,11 @@ end ---@param text string[] ---@param range markview.parsed.range -asciidoc.doc_attr = function (_, _, text, range) +asciidoc.doc_title = function (_, _, text, range) + asciidoc.data.document_title = string.match(text[1] or "", "=%s+(.*)$") + asciidoc.insert({ - class = "asciidoc_document_attribute", + class = "asciidoc_document_title", text = text, range = range diff --git a/lua/markview/parsers/asciidoc_inline.lua b/lua/markview/parsers/asciidoc_inline.lua new file mode 100644 index 0000000..c85f826 --- /dev/null +++ b/lua/markview/parsers/asciidoc_inline.lua @@ -0,0 +1,123 @@ +--- HTML parser for `markview.nvim`. +local asciidoc_inline = {}; + +--- Queried contents +---@type table[] +asciidoc_inline.content = {}; + +--- Queried contents, but sorted +---@type { [string]: table } +asciidoc_inline.sorted = {} + +--- Wrapper for `table.insert()`. +---@param data table +asciidoc_inline.insert = function (data) + table.insert(asciidoc_inline.content, data); + + if not asciidoc_inline.sorted[data.class] then + asciidoc_inline.sorted[data.class] = {}; + end + + table.insert(asciidoc_inline.sorted[data.class], data); +end + +---@param text string[] +---@param range markview.parsed.range +asciidoc_inline.bold = function (_, _, text, range) + asciidoc_inline.insert({ + class = "asciidoc_inline_bold", + + text = text, + range = range + }); +end + +---@param text string[] +---@param range markview.parsed.range +asciidoc_inline.italic = function (_, _, text, range) + asciidoc_inline.insert({ + class = "asciidoc_inline_italic", + + text = text, + range = range + }); +end + +--- HTML parser +---@param buffer integer +---@param TSTree table +---@param from integer? +---@param to integer? +---@return markview.parsed.asciidoc_inline[] +---@return markview.parsed.asciidoc_inline_sorted +asciidoc_inline.parse = function (buffer, TSTree, from, to) + -- Clear the previous contents + asciidoc_inline.sorted = {}; + asciidoc_inline.content = {}; + + local scanned_queries = vim.treesitter.query.parse("asciidoc_inline", [[ + (emphasis) @asciidoc_inline.bold + (ltalic) @asciidoc_inline.italic + ]]); + + for capture_id, capture_node, _, _ in scanned_queries:iter_captures(TSTree:root(), buffer, from, to) do + local capture_name = scanned_queries.captures[capture_id]; + + if not capture_name:match("^asciidoc_inline%.") then + goto continue; + end + + ---@type string? + local capture_text = vim.treesitter.get_node_text(capture_node, buffer); + local r_start, c_start, r_end, c_end = capture_node:range(); + + if capture_text == nil then + goto continue; + end + + if not capture_text:match("\n$") then + capture_text = capture_text .. "\n"; + end + + local lines = {}; + + for line in capture_text:gmatch("(.-)\n") do + table.insert(lines, line); + end + + ---@type boolean, string + local success, error = pcall( + asciidoc_inline[capture_name:gsub("^asciidoc_inline%.", "")], + + buffer, + capture_node, + lines, + { + row_start = r_start, + col_start = c_start, + + row_end = r_end, + col_end = c_end + } + ); + + if success == false then + require("markview.health").print({ + kind = "ERR", + + from = "parsers/asciidoc_inline.lua", + fn = "parse()", + + message = { + { tostring(error), "DiagnosticError" } + } + }); + end + + ::continue:: + end + + return asciidoc_inline.content, asciidoc_inline.sorted; +end + +return asciidoc_inline; diff --git a/lua/markview/renderer.lua b/lua/markview/renderer.lua index a7a62d3..d6c73e1 100644 --- a/lua/markview/renderer.lua +++ b/lua/markview/renderer.lua @@ -18,6 +18,8 @@ renderer.option_maps = { asciidoc = { document_title = { "asciidoc_document_title" }, }, + asciidoc_inline = { + }, comment = { autolinks = { "comment_autolink" }, code_blocks = { "comment_code_block" }, @@ -370,6 +372,7 @@ renderer.render = function (buffer, parsed_content) local _renderers = { asciidoc = require("markview.renderers.asciidoc"), + asciidoc_inline = require("markview.renderers.asciidoc_inline"), comment = require("markview.renderers.comment"), html = require("markview.renderers.html"), markdown = require("markview.renderers.markdown"), @@ -475,6 +478,7 @@ renderer.clear = function (buffer, from, to, hybrid_mode) local _renderers = { asciidoc = require("markview.renderers.asciidoc"), + asciidoc_inline = require("markview.renderers.asciidoc_inline"), comment = require("markview.renderers.comment"); html = require("markview.renderers.html"); markdown = require("markview.renderers.markdown"); diff --git a/lua/markview/renderers/asciidoc.lua b/lua/markview/renderers/asciidoc.lua index 8790f55..b29788c 100644 --- a/lua/markview/renderers/asciidoc.lua +++ b/lua/markview/renderers/asciidoc.lua @@ -50,7 +50,6 @@ asciidoc.document_title = function (buffer, item) }); end ---- Renders HTML elements ---@param buffer integer ---@param content markview.parsed.asciidoc[] asciidoc.render = function (buffer, content) diff --git a/lua/markview/renderers/asciidoc_inline.lua b/lua/markview/renderers/asciidoc_inline.lua new file mode 100644 index 0000000..9c4cff5 --- /dev/null +++ b/lua/markview/renderers/asciidoc_inline.lua @@ -0,0 +1,108 @@ +local asciidoc_inline = {}; + +local utils = require("markview.utils"); +local spec = require("markview.spec"); + +asciidoc_inline.ns = vim.api.nvim_create_namespace("markview/asciidoc_inline"); + +---@param buffer integer +---@param item markview.parsed.asciidoc_inline.document_titles +asciidoc_inline.bold = function (buffer, item) + ---@type markview.config.asciidoc_inline.bolds? + local config = spec.get({ "asciidoc_inline", "bolds" }, { eval_args = { buffer, item } }); + + if not config then + return; + end + + local range = item.range; + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_start, range.col_start, { + end_col = range.col_start + 2, + conceal = "", + }); + + if not string.match(item.text[#item.text] or "", "%*%*$") then + return; + end + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_end, range.col_end - 2, { + end_col = range.col_end, + conceal = "", + }); +end + +---@param buffer integer +---@param item markview.parsed.asciidoc_inline.document_titles +asciidoc_inline.italic = function (buffer, item) + ---@type markview.config.asciidoc_inline.document_titles? + local config = spec.get({ "asciidoc_inline", "italics" }, { eval_args = { buffer, item } }); + + if not config then + return; + end + + local range = item.range; + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_start, range.col_start, { + end_col = range.col_start + 1, + conceal = "", + }); + + if not string.match(item.text[#item.text] or "", "%*$") then + return; + end + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_end, range.col_end - 1, { + end_col = range.col_end, + conceal = "", + }); +end + +--- Renders HTML elements +---@param buffer integer +---@param content markview.parsed.asciidoc_inline[] +asciidoc_inline.render = function (buffer, content) + asciidoc_inline.cache = { + font_regions = {}, + style_regions = { + superscripts = {}, + subscripts = {} + }, + }; + + local custom = spec.get({ "renderers" }, { fallback = {} }); + + for _, item in ipairs(content or {}) do + local success, err; + + if custom[item.class] then + success, err = pcall(custom[item.class], asciidoc_inline.ns, buffer, item); + else + success, err = pcall(asciidoc_inline[item.class:gsub("^asciidoc_inline_", "")], buffer, item); + end + + if success == false then + require("markview.health").print({ + kind = "ERR", + + from = "renderers/asciidoc_inline.lua", + fn = "render() -> " .. item.class, + + message = { + { tostring(err), "DiagnosticError" } + } + }); + end + end +end + +--- Clears decorations of HTML elements +---@param buffer integer +---@param from integer +---@param to integer +asciidoc_inline.clear = function (buffer, from, to) + vim.api.nvim_buf_clear_namespace(buffer, asciidoc_inline.ns, from or 0, to or -1); +end + +return asciidoc_inline; diff --git a/lua/markview/spec.lua b/lua/markview/spec.lua index a287bca..91ed55f 100644 --- a/lua/markview/spec.lua +++ b/lua/markview/spec.lua @@ -228,6 +228,7 @@ spec.default = { ---@type string[] Properties that should be sourced *externally*. spec.__external_config = { "asciidoc", + "asciidoc_inline", "comment", "html", "markdown", diff --git a/test/asciidoc.adoc b/test/asciidoc.adoc index 91123af..1c5472a 100644 --- a/test/asciidoc.adoc +++ b/test/asciidoc.adoc @@ -4,5 +4,38 @@ hi :hi: 123 +It has *strong* significance to me. +I _cannot_ stress this enough. + +Type `OK` to accept. + +That *_really_* has to go. + +Can't pick one? Let's use them `*_all_*`. + + +**C**reate, **R**ead, **U**pdate, and **D**elete (CRUD) + +That's fan__freakin__tastic! + +Don't pass generic ``Object``s to methods that accept ``String``s! + +It was Beatle**__mania__**! + + +Mark my words, #automation is essential#. + +##Mark##up refers to text that contains formatting ##mark##s. + +Where did all the [.underline]#cores# go? + +We need [.line-through]#ten# twenty VMs. + +A [.myrole]#custom role# must be fulfilled by the theme. + + +^super^script + +~sub~script From 97a024bd37b8e0b13419b676971e9b7723d36be1 Mon Sep 17 00:00:00 2001 From: Shawon Date: Tue, 13 Jan 2026 23:35:47 +0600 Subject: [PATCH 08/19] fix(renderers, asciidoc_inline): Fixed conceal for `bold` & `italic`s --- lua/markview/parsers/asciidoc_inline.lua | 34 ++++++++++++++++++++-- lua/markview/renderers/asciidoc_inline.lua | 22 +++++--------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/lua/markview/parsers/asciidoc_inline.lua b/lua/markview/parsers/asciidoc_inline.lua index c85f826..77e6c83 100644 --- a/lua/markview/parsers/asciidoc_inline.lua +++ b/lua/markview/parsers/asciidoc_inline.lua @@ -21,22 +21,52 @@ asciidoc_inline.insert = function (data) table.insert(asciidoc_inline.sorted[data.class], data); end +---@param buffer integer +---@param TSNode TSNode ---@param text string[] ---@param range markview.parsed.range -asciidoc_inline.bold = function (_, _, text, range) +asciidoc_inline.bold = function (buffer, TSNode, text, range) + local delimiters = {}; + + for child in TSNode:iter_children() do + if child:named() == false then + if delimiters[1] then + delimiters[2] = vim.treesitter.get_node_text(child, buffer, {}); + else + delimiters[1] = vim.treesitter.get_node_text(child, buffer, {}); + end + end + end + asciidoc_inline.insert({ class = "asciidoc_inline_bold", + delimiters = delimiters, text = text, range = range }); end +---@param buffer integer +---@param TSNode TSNode ---@param text string[] ---@param range markview.parsed.range -asciidoc_inline.italic = function (_, _, text, range) +asciidoc_inline.italic = function (buffer, TSNode, text, range) + local delimiters = {}; + + for child in TSNode:iter_children() do + if child:named() == false then + if delimiters[1] then + delimiters[2] = vim.treesitter.get_node_text(child, buffer, {}); + else + delimiters[1] = vim.treesitter.get_node_text(child, buffer, {}); + end + end + end + asciidoc_inline.insert({ class = "asciidoc_inline_italic", + delimiters = delimiters, text = text, range = range diff --git a/lua/markview/renderers/asciidoc_inline.lua b/lua/markview/renderers/asciidoc_inline.lua index 9c4cff5..10da342 100644 --- a/lua/markview/renderers/asciidoc_inline.lua +++ b/lua/markview/renderers/asciidoc_inline.lua @@ -6,7 +6,7 @@ local spec = require("markview.spec"); asciidoc_inline.ns = vim.api.nvim_create_namespace("markview/asciidoc_inline"); ---@param buffer integer ----@param item markview.parsed.asciidoc_inline.document_titles +---@param item markview.parsed.asciidoc_inline.bolds asciidoc_inline.bold = function (buffer, item) ---@type markview.config.asciidoc_inline.bolds? local config = spec.get({ "asciidoc_inline", "bolds" }, { eval_args = { buffer, item } }); @@ -18,24 +18,20 @@ asciidoc_inline.bold = function (buffer, item) local range = item.range; utils.set_extmark(buffer, asciidoc_inline.ns, range.row_start, range.col_start, { - end_col = range.col_start + 2, + end_col = range.col_start + #(item.delimiters[1] or ""), conceal = "", }); - if not string.match(item.text[#item.text] or "", "%*%*$") then - return; - end - - utils.set_extmark(buffer, asciidoc_inline.ns, range.row_end, range.col_end - 2, { + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_end, range.col_end - #(item.delimiters[2] or ""), { end_col = range.col_end, conceal = "", }); end ---@param buffer integer ----@param item markview.parsed.asciidoc_inline.document_titles +---@param item markview.parsed.asciidoc_inline.italics asciidoc_inline.italic = function (buffer, item) - ---@type markview.config.asciidoc_inline.document_titles? + ---@type markview.config.asciidoc_inline.italics? local config = spec.get({ "asciidoc_inline", "italics" }, { eval_args = { buffer, item } }); if not config then @@ -45,15 +41,11 @@ asciidoc_inline.italic = function (buffer, item) local range = item.range; utils.set_extmark(buffer, asciidoc_inline.ns, range.row_start, range.col_start, { - end_col = range.col_start + 1, + end_col = range.col_start + #(item.delimiters[1] or ""), conceal = "", }); - if not string.match(item.text[#item.text] or "", "%*$") then - return; - end - - utils.set_extmark(buffer, asciidoc_inline.ns, range.row_end, range.col_end - 1, { + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_end, range.col_end - #(item.delimiters[2] or ""), { end_col = range.col_end, conceal = "", }); From aecd13821effb3328ebd853336ecb8cfcec837a7 Mon Sep 17 00:00:00 2001 From: Shawon Date: Tue, 13 Jan 2026 23:36:15 +0600 Subject: [PATCH 09/19] doc(asciidoc_inline): Added missing types for bold & italic --- lua/markview/config/asciidoc_inline.lua | 2 +- .../types/parsers/asciidoc_inline.lua | 31 +++++++++++++++++++ .../types/renderers/asciidoc_inline.lua | 18 +++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 lua/markview/types/parsers/asciidoc_inline.lua create mode 100644 lua/markview/types/renderers/asciidoc_inline.lua diff --git a/lua/markview/config/asciidoc_inline.lua b/lua/markview/config/asciidoc_inline.lua index 35be288..c979f09 100644 --- a/lua/markview/config/asciidoc_inline.lua +++ b/lua/markview/config/asciidoc_inline.lua @@ -1,4 +1,4 @@ ----@type markview.config.asciidoc +---@type markview.config.asciidoc_inline return { bolds = { enable = true }, italics = { enable = true }, diff --git a/lua/markview/types/parsers/asciidoc_inline.lua b/lua/markview/types/parsers/asciidoc_inline.lua new file mode 100644 index 0000000..1a342d1 --- /dev/null +++ b/lua/markview/types/parsers/asciidoc_inline.lua @@ -0,0 +1,31 @@ +---@meta + + +---@class markview.parsed.asciidoc_inline.bolds +--- +---@field class "asciidoc_inline_bold" +---@field delimiters [ string?, string? ] Delimiters +--- +---@field text string[] +---@field range markview.parsed.range + + +---@class markview.parsed.asciidoc_inline.italics +--- +---@field class "asciidoc_inline_italic" +---@field delimiters [ string?, string? ] Delimiters +--- +---@field text string[] +---@field range markview.parsed.range + + +---@alias markview.parsed.asciidoc_inline +---| markview.parsed.asciidoc_inline.bolds +---| markview.parsed.asciidoc_inline.italics + + +---@class markview.parsed.asciidoc_inline_sorted +--- +---@field bolds markview.parsed.asciidoc_inline.bolds +---@field italics markview.parsed.asciidoc_inline.italics + diff --git a/lua/markview/types/renderers/asciidoc_inline.lua b/lua/markview/types/renderers/asciidoc_inline.lua new file mode 100644 index 0000000..402cf8f --- /dev/null +++ b/lua/markview/types/renderers/asciidoc_inline.lua @@ -0,0 +1,18 @@ +---@meta + + +---@class markview.config.asciidoc_inline.bolds +--- +---@field enable boolean + + +---@class markview.config.asciidoc_inline.italics +--- +---@field enable boolean + + +---@class markview.config.asciidoc_inline +--- +---@field bolds markview.config.asciidoc_inline.bolds +---@field italics markview.config.asciidoc_inline.italics + From c81472c16e2ac93d14517d59b9da386adafd4a15 Mon Sep 17 00:00:00 2001 From: Shawon Date: Wed, 14 Jan 2026 00:46:34 +0600 Subject: [PATCH 10/19] fix(parsers, asciidoc_inline): Reduce unnecessary recursion when parsing Fixes duplication of rendered output. --- lua/markview/parser.lua | 12 ++++++++++++ lua/markview/parsers/asciidoc_inline.lua | 15 ++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lua/markview/parser.lua b/lua/markview/parser.lua index d9e75a1..3a42be8 100644 --- a/lua/markview/parser.lua +++ b/lua/markview/parser.lua @@ -128,6 +128,18 @@ parser.init = function (buffer, from, to, cache) return parser.content, parser.sorted; end + --[[ + WARN: Recursion when parsing `asciidoc_inline` trees + + `cathaysia/tree-sitter-asciidoc` uses `#injection.include-children` for it's inline parser. + This causes the same text to be parsed multiple times, + + FIX(asciidoc_inline): Check parse range + + Check if a parser range has been parsed before. If it has, do not parse again. + ]] + _parsers.asciidoc_inline.parsed_ranges = {}; + ---|fS "chore: Announce start of parsing" ---@type integer Start time local start = vim.uv.hrtime(); diff --git a/lua/markview/parsers/asciidoc_inline.lua b/lua/markview/parsers/asciidoc_inline.lua index 77e6c83..a5fd29b 100644 --- a/lua/markview/parsers/asciidoc_inline.lua +++ b/lua/markview/parsers/asciidoc_inline.lua @@ -1,6 +1,8 @@ --- HTML parser for `markview.nvim`. local asciidoc_inline = {}; +asciidoc_inline.parsed_ranges = {}; + --- Queried contents ---@type table[] asciidoc_inline.content = {}; @@ -75,7 +77,7 @@ end --- HTML parser ---@param buffer integer ----@param TSTree table +---@param TSTree TSTree ---@param from integer? ---@param to integer? ---@return markview.parsed.asciidoc_inline[] @@ -85,6 +87,17 @@ asciidoc_inline.parse = function (buffer, TSTree, from, to) asciidoc_inline.sorted = {}; asciidoc_inline.content = {}; + local root = TSTree:root(); + local r_range = { root:range() }; + + for _, entry in ipairs(asciidoc_inline.parsed_ranges) do + if vim.deep_equal(entry, r_range) then + return asciidoc_inline.content, asciidoc_inline.sorted; + end + end + + table.insert(asciidoc_inline.parsed_ranges, r_range); + local scanned_queries = vim.treesitter.query.parse("asciidoc_inline", [[ (emphasis) @asciidoc_inline.bold (ltalic) @asciidoc_inline.italic From a324d09c407232ed15c9b59e73293ae154efcdd4 Mon Sep 17 00:00:00 2001 From: Shawon Date: Wed, 14 Jan 2026 00:51:02 +0600 Subject: [PATCH 11/19] feat(asciidoc_inline): Added `monospce`s support --- lua/markview/config/asciidoc_inline.lua | 8 +++ lua/markview/parsers/asciidoc_inline.lua | 27 +++++++++ lua/markview/renderers/asciidoc_inline.lua | 55 ++++++++++++++++--- .../types/parsers/asciidoc_inline.lua | 11 ++++ .../types/renderers/asciidoc_inline.lua | 4 ++ 5 files changed, 96 insertions(+), 9 deletions(-) diff --git a/lua/markview/config/asciidoc_inline.lua b/lua/markview/config/asciidoc_inline.lua index c979f09..1a09ca0 100644 --- a/lua/markview/config/asciidoc_inline.lua +++ b/lua/markview/config/asciidoc_inline.lua @@ -2,4 +2,12 @@ return { bolds = { enable = true }, italics = { enable = true }, + + monospaces = { + enable = true, + hl = "MarkviewInlineCode", + + padding_left = " ", + padding_right = " " + }, }; diff --git a/lua/markview/parsers/asciidoc_inline.lua b/lua/markview/parsers/asciidoc_inline.lua index a5fd29b..e0c436f 100644 --- a/lua/markview/parsers/asciidoc_inline.lua +++ b/lua/markview/parsers/asciidoc_inline.lua @@ -75,6 +75,32 @@ asciidoc_inline.italic = function (buffer, TSNode, text, range) }); end +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.range +asciidoc_inline.monospace = function (buffer, TSNode, text, range) + local delimiters = {}; + + for child in TSNode:iter_children() do + if child:named() == false then + if delimiters[1] then + delimiters[2] = vim.treesitter.get_node_text(child, buffer, {}); + else + delimiters[1] = vim.treesitter.get_node_text(child, buffer, {}); + end + end + end + + asciidoc_inline.insert({ + class = "asciidoc_inline_monospace", + delimiters = delimiters, + + text = text, + range = range + }); +end + --- HTML parser ---@param buffer integer ---@param TSTree TSTree @@ -101,6 +127,7 @@ asciidoc_inline.parse = function (buffer, TSTree, from, to) local scanned_queries = vim.treesitter.query.parse("asciidoc_inline", [[ (emphasis) @asciidoc_inline.bold (ltalic) @asciidoc_inline.italic + (monospace) @asciidoc_inline.monospace ]]); for capture_id, capture_node, _, _ in scanned_queries:iter_captures(TSTree:root(), buffer, from, to) do diff --git a/lua/markview/renderers/asciidoc_inline.lua b/lua/markview/renderers/asciidoc_inline.lua index 10da342..ddec291 100644 --- a/lua/markview/renderers/asciidoc_inline.lua +++ b/lua/markview/renderers/asciidoc_inline.lua @@ -51,18 +51,55 @@ asciidoc_inline.italic = function (buffer, item) }); end ---- Renders HTML elements ---@param buffer integer ----@param content markview.parsed.asciidoc_inline[] -asciidoc_inline.render = function (buffer, content) - asciidoc_inline.cache = { - font_regions = {}, - style_regions = { - superscripts = {}, - subscripts = {} +---@param item markview.parsed.asciidoc_inline.monospaces +asciidoc_inline.monospace = function (buffer, item) + ---@type markview.config.asciidoc_inline.monospaces? + local config = spec.get({ "asciidoc_inline", "monospaces" }, { eval_args = { buffer, item } }); + + if not config then + return; + end + + local range = item.range; + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_start, range.col_start, { + end_col = range.col_start + #(item.delimiters[1] or ""), + conceal = "", + + virt_text_pos = "inline", + virt_text = { + { config.corner_left or "", utils.set_hl(config.corner_left_hl or config.hl) }, + { config.padding_left or "", utils.set_hl(config.padding_left_hl or config.hl) } }, - }; + hl_mode = "combine" + }); + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_start, range.col_start, { + end_col = range.col_end, end_row = range.row_end, + + hl_group = utils.set_hl(config.hl), + hl_mode = "combine" + }); + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_end, range.col_end - #(item.delimiters[2] or ""), { + end_col = range.col_end, + conceal = "", + + virt_text_pos = "inline", + virt_text = { + { config.corner_right or "", utils.set_hl(config.corner_right_hl or config.hl) }, + { config.padding_right or "", utils.set_hl(config.padding_right_hl or config.hl) } + }, + + hl_mode = "combine" + }); +end + +---@param buffer integer +---@param content markview.parsed.asciidoc_inline[] +asciidoc_inline.render = function (buffer, content) local custom = spec.get({ "renderers" }, { fallback = {} }); for _, item in ipairs(content or {}) do diff --git a/lua/markview/types/parsers/asciidoc_inline.lua b/lua/markview/types/parsers/asciidoc_inline.lua index 1a342d1..bfa7396 100644 --- a/lua/markview/types/parsers/asciidoc_inline.lua +++ b/lua/markview/types/parsers/asciidoc_inline.lua @@ -19,13 +19,24 @@ ---@field range markview.parsed.range +---@class markview.parsed.asciidoc_inline.monospaces +--- +---@field class "asciidoc_inline_monospace" +---@field delimiters [ string?, string? ] Delimiters +--- +---@field text string[] +---@field range markview.parsed.range + + ---@alias markview.parsed.asciidoc_inline ---| markview.parsed.asciidoc_inline.bolds ---| markview.parsed.asciidoc_inline.italics +---| markview.parsed.asciidoc_inline.monospaces ---@class markview.parsed.asciidoc_inline_sorted --- ---@field bolds markview.parsed.asciidoc_inline.bolds ---@field italics markview.parsed.asciidoc_inline.italics +---@field monospaces markview.parsed.asciidoc_inline.monospaces diff --git a/lua/markview/types/renderers/asciidoc_inline.lua b/lua/markview/types/renderers/asciidoc_inline.lua index 402cf8f..7b108f4 100644 --- a/lua/markview/types/renderers/asciidoc_inline.lua +++ b/lua/markview/types/renderers/asciidoc_inline.lua @@ -11,8 +11,12 @@ ---@field enable boolean +---@alias markview.config.asciidoc_inline.monospaces markview.config.__inline + + ---@class markview.config.asciidoc_inline --- ---@field bolds markview.config.asciidoc_inline.bolds ---@field italics markview.config.asciidoc_inline.italics +---@field monospaces markview.config.asciidoc_inline.monospaces From 32c0ddd390ed3111b2ee18f38d3fc416ab9915db Mon Sep 17 00:00:00 2001 From: Shawon Date: Wed, 14 Jan 2026 20:32:40 +0600 Subject: [PATCH 12/19] feat(asciidoc_inline): Added `#highlight#` support --- lua/markview/config/asciidoc_inline.lua | 11 ++++ lua/markview/parsers/asciidoc_inline.lua | 27 +++++++++ lua/markview/renderers/asciidoc_inline.lua | 58 +++++++++++++++++++ .../types/parsers/asciidoc_inline.lua | 11 ++++ .../types/renderers/asciidoc_inline.lua | 15 +++++ 5 files changed, 122 insertions(+) diff --git a/lua/markview/config/asciidoc_inline.lua b/lua/markview/config/asciidoc_inline.lua index 1a09ca0..4796a31 100644 --- a/lua/markview/config/asciidoc_inline.lua +++ b/lua/markview/config/asciidoc_inline.lua @@ -10,4 +10,15 @@ return { padding_left = " ", padding_right = " " }, + + highlights = { + enable = true, + + default = { + padding_left = " ", + padding_right = " ", + + hl = "MarkviewPalette3" + } + }, }; diff --git a/lua/markview/parsers/asciidoc_inline.lua b/lua/markview/parsers/asciidoc_inline.lua index e0c436f..3b90f4a 100644 --- a/lua/markview/parsers/asciidoc_inline.lua +++ b/lua/markview/parsers/asciidoc_inline.lua @@ -49,6 +49,32 @@ asciidoc_inline.bold = function (buffer, TSNode, text, range) }); end +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.range +asciidoc_inline.highlight = function (buffer, TSNode, text, range) + local delimiters = {}; + + for child in TSNode:iter_children() do + if child:named() == false then + if delimiters[1] then + delimiters[2] = vim.treesitter.get_node_text(child, buffer, {}); + else + delimiters[1] = vim.treesitter.get_node_text(child, buffer, {}); + end + end + end + + asciidoc_inline.insert({ + class = "asciidoc_inline_highlight", + delimiters = delimiters, + + text = text, + range = range + }); +end + ---@param buffer integer ---@param TSNode TSNode ---@param text string[] @@ -128,6 +154,7 @@ asciidoc_inline.parse = function (buffer, TSTree, from, to) (emphasis) @asciidoc_inline.bold (ltalic) @asciidoc_inline.italic (monospace) @asciidoc_inline.monospace + (highlight) @asciidoc_inline.highlight ]]); for capture_id, capture_node, _, _ in scanned_queries:iter_captures(TSTree:root(), buffer, from, to) do diff --git a/lua/markview/renderers/asciidoc_inline.lua b/lua/markview/renderers/asciidoc_inline.lua index ddec291..f05eab9 100644 --- a/lua/markview/renderers/asciidoc_inline.lua +++ b/lua/markview/renderers/asciidoc_inline.lua @@ -28,6 +28,64 @@ asciidoc_inline.bold = function (buffer, item) }); end +---@param buffer integer +---@param item markview.parsed.asciidoc_inline.highlights +asciidoc_inline.highlight = function (buffer, item) + ---@type markview.config.asciidoc_inline.highlights? + local main_config = spec.get({ "asciidoc_inline", "highlights" }, { fallback = nil }); + local range = item.range; + + if not main_config then + return; + end + + ---@type markview.config.__inline? + local config = utils.match( + main_config, + table.concat(item.text, "\n"), + { + eval_args = { buffer, item } + } + ); + + if config == nil then + return; + end + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_start, range.col_start, { + end_col = range.col_start + #(item.delimiters[1] or ""), + conceal = "", + + virt_text_pos = "inline", + virt_text = { + { config.corner_left or "", utils.set_hl(config.corner_left_hl or config.hl) }, + { config.padding_left or "", utils.set_hl(config.padding_left_hl or config.hl) } + }, + + hl_mode = "combine" + }); + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_start, range.col_start, { + end_col = range.col_end, end_row = range.row_end, + + hl_group = utils.set_hl(config.hl), + hl_mode = "combine" + }); + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_end, range.col_end - #(item.delimiters[2] or ""), { + end_col = range.col_end, + conceal = "", + + virt_text_pos = "inline", + virt_text = { + { config.corner_right or "", utils.set_hl(config.corner_right_hl or config.hl) }, + { config.padding_right or "", utils.set_hl(config.padding_right_hl or config.hl) } + }, + + hl_mode = "combine" + }); +end + ---@param buffer integer ---@param item markview.parsed.asciidoc_inline.italics asciidoc_inline.italic = function (buffer, item) diff --git a/lua/markview/types/parsers/asciidoc_inline.lua b/lua/markview/types/parsers/asciidoc_inline.lua index bfa7396..b7bf23e 100644 --- a/lua/markview/types/parsers/asciidoc_inline.lua +++ b/lua/markview/types/parsers/asciidoc_inline.lua @@ -10,6 +10,15 @@ ---@field range markview.parsed.range +---@class markview.parsed.asciidoc_inline.highlights +--- +---@field class "asciidoc_inline_highlight" +---@field delimiters [ string?, string? ] Delimiters +--- +---@field text string[] +---@field range markview.parsed.range + + ---@class markview.parsed.asciidoc_inline.italics --- ---@field class "asciidoc_inline_italic" @@ -30,6 +39,7 @@ ---@alias markview.parsed.asciidoc_inline ---| markview.parsed.asciidoc_inline.bolds +---| markview.parsed.asciidoc_inline.highlights ---| markview.parsed.asciidoc_inline.italics ---| markview.parsed.asciidoc_inline.monospaces @@ -39,4 +49,5 @@ ---@field bolds markview.parsed.asciidoc_inline.bolds ---@field italics markview.parsed.asciidoc_inline.italics ---@field monospaces markview.parsed.asciidoc_inline.monospaces +---@field highlights markview.parsed.asciidoc_inline.highlights diff --git a/lua/markview/types/renderers/asciidoc_inline.lua b/lua/markview/types/renderers/asciidoc_inline.lua index 7b108f4..6160635 100644 --- a/lua/markview/types/renderers/asciidoc_inline.lua +++ b/lua/markview/types/renderers/asciidoc_inline.lua @@ -5,6 +5,20 @@ --- ---@field enable boolean +------------------------------------------------------------------------------ + +--- Configuration for Obsidian-style highlighted texts. +---@class markview.config.asciidoc_inline.highlights +--- +---@field enable boolean Enable rendering of highlighted text. +--- +---@field default markview.config.asciidoc_inline.highlights.opts Default configuration for highlighted text. +---@field [string] markview.config.asciidoc_inline.highlights.opts Configuration for highlighted text that matches `string`. + + +--[[ Options for a specific footnote type. ]] +---@alias markview.config.asciidoc_inline.highlights.opts markview.config.__inline + ---@class markview.config.asciidoc_inline.italics --- @@ -17,6 +31,7 @@ ---@class markview.config.asciidoc_inline --- ---@field bolds markview.config.asciidoc_inline.bolds +---@field highlights markview.config.asciidoc_inline.highlights Highlighted text configuration. ---@field italics markview.config.asciidoc_inline.italics ---@field monospaces markview.config.asciidoc_inline.monospaces From cead72e29b26c2502e4d3074bec9c7ef977189d4 Mon Sep 17 00:00:00 2001 From: Shawon Date: Thu, 15 Jan 2026 00:26:47 +0600 Subject: [PATCH 13/19] fix(renderers, asciidoc_inline): Fixed bug with icon not showing --- lua/markview/renderers/asciidoc_inline.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lua/markview/renderers/asciidoc_inline.lua b/lua/markview/renderers/asciidoc_inline.lua index f05eab9..75f757a 100644 --- a/lua/markview/renderers/asciidoc_inline.lua +++ b/lua/markview/renderers/asciidoc_inline.lua @@ -59,7 +59,8 @@ asciidoc_inline.highlight = function (buffer, item) virt_text_pos = "inline", virt_text = { { config.corner_left or "", utils.set_hl(config.corner_left_hl or config.hl) }, - { config.padding_left or "", utils.set_hl(config.padding_left_hl or config.hl) } + { config.padding_left or "", utils.set_hl(config.padding_left_hl or config.hl) }, + { config.icon or "", utils.set_hl(config.icon_hl or config.hl) } }, hl_mode = "combine" @@ -128,7 +129,8 @@ asciidoc_inline.monospace = function (buffer, item) virt_text_pos = "inline", virt_text = { { config.corner_left or "", utils.set_hl(config.corner_left_hl or config.hl) }, - { config.padding_left or "", utils.set_hl(config.padding_left_hl or config.hl) } + { config.padding_left or "", utils.set_hl(config.padding_left_hl or config.hl) }, + { config.icon or "", utils.set_hl(config.icon_hl or config.hl) } }, hl_mode = "combine" From 0c463e307cedb27b7135f33db200d941a8d3d26c Mon Sep 17 00:00:00 2001 From: Shawon Date: Thu, 15 Jan 2026 00:36:10 +0600 Subject: [PATCH 14/19] feat(asciidoc_inline): Added support for unlabeled URIs --- lua/markview/config/asciidoc_inline.lua | 320 ++++++++++++++++++ lua/markview/parsers/asciidoc_inline.lua | 36 ++ lua/markview/renderers/asciidoc_inline.lua | 71 ++++ .../types/parsers/asciidoc_inline.lua | 10 + .../types/renderers/asciidoc_inline.lua | 40 +++ test/asciidoc.adoc | 10 + 6 files changed, 487 insertions(+) diff --git a/lua/markview/config/asciidoc_inline.lua b/lua/markview/config/asciidoc_inline.lua index 4796a31..c4ca688 100644 --- a/lua/markview/config/asciidoc_inline.lua +++ b/lua/markview/config/asciidoc_inline.lua @@ -1,3 +1,11 @@ +local function normalize_str(str) + if type(str) ~= "string" then + return ""; + end + + return string.lower(str):gsub("^%l", string.upper); +end + ---@type markview.config.asciidoc_inline return { bolds = { enable = true }, @@ -21,4 +29,316 @@ return { hl = "MarkviewPalette3" } }, + + + uris = { + enable = true, + + default = { + icon = "󰌷 ", + hl = "MarkviewHyperlink", + }, + + ---|fS + + --NOTE(@OXY2DEV): Github sites. + + ["github%.com/[%a%d%-%_%.]+%/?$"] = { + --- github.com/ + icon = " ", + hl = "MarkviewPalette0Fg", + + text = function (_, item) + return string.match(item.destination, "github%.com/([%a%d%-%_%.]+)%/?$"); + end + }, + ["github%.com/[%a%d%-%_%.]+/[%a%d%-%_%.]+%/?$"] = { + --- github.com// + icon = "󰳐 ", + hl = "MarkviewPalette0Fg", + + text = function (_, item) + return string.match(item.destination, "github%.com/([%a%d%-%_%.]+/[%a%d%-%_%.]+)%/?$"); + end + }, + ["github%.com/[%a%d%-%_%.]+/[%a%d%-%_%.]+/tree/[%a%d%-%_%.]+%/?$"] = { + --- github.com///tree/ + icon = " ", + hl = "MarkviewPalette0Fg", + + text = function (_, item) + local repo, branch = string.match(item.destination, "github%.com/([%a%d%-%_%.]+/[%a%d%-%_%.]+)/tree/([%a%d%-%_%.]+)%/?$"); + return repo .. " at " .. branch; + end + }, + ["github%.com/[%a%d%-%_%.]+/[%a%d%-%_%.]+/commits/[%a%d%-%_%.]+%/?$"] = { + --- github.com///commits/ + icon = " ", + hl = "MarkviewPalette0Fg", + + text = function (_, item) + return string.match(item.destination, "github%.com/([%a%d%-%_%.]+/[%a%d%-%_%.]+/commits/[%a%d%-%_%.]+)%/?$"); + end + }, + + ["github%.com/[%a%d%-%_%.]+/[%a%d%-%_%.]+%/releases$"] = { + --- github.com///releases + icon = " ", + hl = "MarkviewPalette0Fg", + + text = function (_, item) + return "Releases • " .. string.match(item.destination, "github%.com/([%a%d%-%_%.]+/[%a%d%-%_%.]+)%/releases$"); + end + }, + ["github%.com/[%a%d%-%_%.]+/[%a%d%-%_%.]+%/tags$"] = { + --- github.com///tags + icon = " ", + hl = "MarkviewPalette0Fg", + + text = function (_, item) + return "Tags • " .. string.match(item.destination, "github%.com/([%a%d%-%_%.]+/[%a%d%-%_%.]+)%/tags$"); + end + }, + ["github%.com/[%a%d%-%_%.]+/[%a%d%-%_%.]+%/issues$"] = { + --- github.com///issues + icon = " ", + hl = "MarkviewPalette0Fg", + + text = function (_, item) + return "Issues • " .. string.match(item.destination, "github%.com/([%a%d%-%_%.]+/[%a%d%-%_%.]+)%/issues$"); + end + }, + ["github%.com/[%a%d%-%_%.]+/[%a%d%-%_%.]+%/pulls$"] = { + --- github.com///pulls + icon = " ", + hl = "MarkviewPalette0Fg", + + text = function (_, item) + return "Pull requests • " .. string.match(item.destination, "github%.com/([%a%d%-%_%.]+/[%a%d%-%_%.]+)%/pulls$"); + end + }, + + ["github%.com/[%a%d%-%_%.]+/[%a%d%-%_%.]+%/wiki$"] = { + --- github.com///wiki + icon = " ", + hl = "MarkviewPalette0Fg", + + text = function (_, item) + return "Wiki • " .. string.match(item.destination, "github%.com/([%a%d%-%_%.]+/[%a%d%-%_%.]+)%/wiki$"); + end + }, + + --- NOTE(@OXY2DEV): Commonly used sites by programmers. + + ["developer%.mozilla%.org"] = { + priority = -9999, + + icon = "󰖟 ", + hl = "MarkviewPalette5Fg" + }, + + ["w3schools%.com"] = { + priority = -9999, + + icon = " ", + hl = "MarkviewPalette4Fg" + }, + + ["stackoverflow%.com"] = { + priority = -9999, + + icon = "󰓌 ", + hl = "MarkviewPalette2Fg" + }, + + ["reddit%.com"] = { + priority = -9999, + + icon = " ", + hl = "MarkviewPalette2Fg" + }, + + ["github%.com"] = { + priority = -9999, + + icon = " ", + hl = "MarkviewPalette6Fg" + }, + + ["gitlab%.com"] = { + priority = -9999, + + icon = " ", + hl = "MarkviewPalette2Fg" + }, + + ["dev%.to"] = { + priority = -9999, + + icon = "󱁴 ", + hl = "MarkviewPalette0Fg" + }, + + ["codepen%.io"] = { + priority = -9999, + + icon = " ", + hl = "MarkviewPalette6Fg" + }, + + ["replit%.com"] = { + priority = -9999, + + icon = " ", + hl = "MarkviewPalette2Fg" + }, + + ["jsfiddle%.net"] = { + priority = -9999, + + icon = " ", + hl = "MarkviewPalette5Fg" + }, + + ["npmjs%.com"] = { + priority = -9999, + + icon = " ", + hl = "MarkviewPalette0Fg" + }, + + ["pypi%.org"] = { + priority = -9999, + + icon = "󰆦 ", + hl = "MarkviewPalette0Fg" + }, + + ["mvnrepository%.com"] = { + priority = -9999, + + icon = " ", + hl = "MarkviewPalette1Fg" + }, + + ["medium%.com"] = { + priority = -9999, + + icon = " ", + hl = "MarkviewPalette6Fg" + }, + + ["linkedin%.com"] = { + priority = -9999, + + icon = "󰌻 ", + hl = "MarkviewPalette5Fg" + }, + + ["news%.ycombinator%.com"] = { + priority = -9999, + + icon = " ", + hl = "MarkviewPalette2Fg" + }, + + ["neovim%.io/doc/user/.*#%_?.*$"] = { + icon = " ", + hl = "MarkviewPalette4Fg", + + text = function (_, item) + local file, tag = string.match(item.destination, "neovim%.io/doc/user/(.*)#%_?(.*)$"); + --- The actual website seems to show + --- _ in the site name so, we won't + --- be replacing `_`s with ` `s. + file = string.gsub(file, "%.html$", ""); + + return string.format("%s(%s) - Neovim docs", normalize_str(file), tag); + end + }, + ["neovim%.io/doc/user/.*$"] = { + icon = " ", + hl = "MarkviewPalette4Fg", + + text = function (_, item) + local file = string.match(item.destination, "neovim%.io/doc/user/(.*)$"); + file = string.gsub(file, "%.html$", ""); + + return string.format("%s - Neovim docs", normalize_str(file)); + end + }, + + ["github%.com/vim/vim"] = { + priority = -100, + + icon = " ", + hl = "MarkviewPalette4Fg", + }, + + ["github%.com/neovim/neovim"] = { + priority = -100, + + icon = " ", + hl = "MarkviewPalette4Fg", + }, + + ["vim%.org"] = { + icon = " ", + hl = "MarkviewPalette4Fg", + }, + + ["luals%.github%.io/wiki/?.*$"] = { + icon = " ", + hl = "MarkviewPalette5Fg", + + text = function (_, item) + if string.match(item.destination, "luals%.github%.io/wiki/(.-)/#(.+)$") then + local page_mappings = { + annotations = { + ["as"] = "@as", + ["alias"] = "@alias", + ["async"] = "@async", + ["cast"] = "@cast", + ["class"] = "@class", + ["deprecated"] = "@deprecated", + ["diagnostic"] = "@diagnostic", + ["enum"] = "@enum", + ["field"] = "@field", + ["generic"] = "@generic", + ["meta"] = "@meta", + ["module"] = "@module", + ["nodiscard"] = "@nodiscard", + ["operator"] = "@operator", + ["overload"] = "@overload", + ["package"] = "@package", + ["param"] = "@param", + ["see"] = "@see", + ["source"] = "@source", + ["type"] = "@type", + ["vaarg"] = "@vaarg", + ["version"] = "@version" + } + }; + + local page, section = string.match(item.destination, "luals%.github%.io/wiki/(.-)/#(.+)$"); + + if page_mappings[page] and page_mappings[page][section] then + section = page_mappings[page][section]; + else + section = normalize_str(string.gsub(section, "%-", " ")); + end + + return string.format("%s(%s) | Lua Language Server", normalize_str(page), section); + elseif string.match(item.destination, "") then + local page = string.match(item.destination, "luals%.github%.io/wiki/(.-)/?$"); + + return string.format("%s | Lua Language Server", normalize_str(page)); + else + return item.destination; + end + end + }, + + ---|fE + }, }; diff --git a/lua/markview/parsers/asciidoc_inline.lua b/lua/markview/parsers/asciidoc_inline.lua index 3b90f4a..8c35345 100644 --- a/lua/markview/parsers/asciidoc_inline.lua +++ b/lua/markview/parsers/asciidoc_inline.lua @@ -127,6 +127,36 @@ asciidoc_inline.monospace = function (buffer, TSNode, text, range) }); end +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.range +asciidoc_inline.uri = function (buffer, TSNode, text, range) + local delimiters = {}; + local destination; + + for child in TSNode:iter_children() do + if child:named() == false then + if delimiters[1] then + delimiters[2] = vim.treesitter.get_node_text(child, buffer, {}); + else + delimiters[1] = vim.treesitter.get_node_text(child, buffer, {}); + end + else + destination = vim.treesitter.get_node_text(child, buffer, {}); + end + end + + asciidoc_inline.insert({ + class = "asciidoc_inline_uri", + delimiters = delimiters, + destination = destination, + + text = text, + range = range + }); +end + --- HTML parser ---@param buffer integer ---@param TSTree TSTree @@ -155,6 +185,12 @@ asciidoc_inline.parse = function (buffer, TSTree, from, to) (ltalic) @asciidoc_inline.italic (monospace) @asciidoc_inline.monospace (highlight) @asciidoc_inline.highlight + + (autolink + (uri)) @asciidoc_inline.uri + ; + ; (autolink + ; (labeled_uri)) @asciidoc_inline.labeled_uri ]]); for capture_id, capture_node, _, _ in scanned_queries:iter_captures(TSTree:root(), buffer, from, to) do diff --git a/lua/markview/renderers/asciidoc_inline.lua b/lua/markview/renderers/asciidoc_inline.lua index 75f757a..9bfc813 100644 --- a/lua/markview/renderers/asciidoc_inline.lua +++ b/lua/markview/renderers/asciidoc_inline.lua @@ -157,6 +157,77 @@ asciidoc_inline.monospace = function (buffer, item) }); end +---@param buffer integer +---@param item markview.parsed.asciidoc_inline.uris +asciidoc_inline.uri = function (buffer, item) + ---@type markview.config.asciidoc_inline.uris? + local main_config = spec.get({ "asciidoc_inline", "uris" }, { fallback = nil }); + local range = item.range; + + if not main_config then + return; + end + + ---@type markview.config.asciidoc_inline.uris.opts? + local config = utils.match( + main_config, + item.destination or "", + { + eval_args = { buffer, item } + } + ); + + if config == nil then + return; + end + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_start, range.col_start, { + end_col = range.col_start + #(item.delimiters[1] or ""), + conceal = "", + + virt_text_pos = "inline", + virt_text = { + { config.corner_left or "", utils.set_hl(config.corner_left_hl or config.hl) }, + { config.padding_left or "", utils.set_hl(config.padding_left_hl or config.hl) }, + { config.icon or "", utils.set_hl(config.icon_hl or config.hl) } + }, + + hl_mode = "combine" + }); + + if config.text then + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_start, range.col_start + #(item.delimiters[1] or ""), { + end_col = range.col_end - #(item.delimiters[2] or ""), end_row = range.row_end, + + virt_text = { + { config.text or "", utils.set_hl(config.text_hl or config.hl) } + }, + + hl_mode = "combine" + }); + else + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_start, range.col_start, { + end_col = range.col_end, end_row = range.row_end, + + hl_group = utils.set_hl(config.hl), + hl_mode = "combine" + }); + end + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_end, range.col_end - #(item.delimiters[2] or ""), { + end_col = range.col_end, + conceal = "", + + virt_text_pos = "inline", + virt_text = { + { config.corner_right or "", utils.set_hl(config.corner_right_hl or config.hl) }, + { config.padding_right or "", utils.set_hl(config.padding_right_hl or config.hl) } + }, + + hl_mode = "combine" + }); +end + ---@param buffer integer ---@param content markview.parsed.asciidoc_inline[] asciidoc_inline.render = function (buffer, content) diff --git a/lua/markview/types/parsers/asciidoc_inline.lua b/lua/markview/types/parsers/asciidoc_inline.lua index b7bf23e..396847b 100644 --- a/lua/markview/types/parsers/asciidoc_inline.lua +++ b/lua/markview/types/parsers/asciidoc_inline.lua @@ -37,6 +37,16 @@ ---@field range markview.parsed.range +---@class markview.parsed.asciidoc_inline.uris +--- +---@field class "asciidoc_inline_uri" +---@field delimiters [ string?, string? ] Delimiters +---@field destination string URL the node is pointing to. +--- +---@field text string[] +---@field range markview.parsed.range + + ---@alias markview.parsed.asciidoc_inline ---| markview.parsed.asciidoc_inline.bolds ---| markview.parsed.asciidoc_inline.highlights diff --git a/lua/markview/types/renderers/asciidoc_inline.lua b/lua/markview/types/renderers/asciidoc_inline.lua index 6160635..f761df8 100644 --- a/lua/markview/types/renderers/asciidoc_inline.lua +++ b/lua/markview/types/renderers/asciidoc_inline.lua @@ -15,23 +15,63 @@ ---@field default markview.config.asciidoc_inline.highlights.opts Default configuration for highlighted text. ---@field [string] markview.config.asciidoc_inline.highlights.opts Configuration for highlighted text that matches `string`. +------------------------------------------------------------------------------ --[[ Options for a specific footnote type. ]] ---@alias markview.config.asciidoc_inline.highlights.opts markview.config.__inline +------------------------------------------------------------------------------ ---@class markview.config.asciidoc_inline.italics --- ---@field enable boolean +------------------------------------------------------------------------------ ---@alias markview.config.asciidoc_inline.monospaces markview.config.__inline +------------------------------------------------------------------------------ + +---@class markview.config.asciidoc_inline.uris +--- +---@field enable boolean Enable rendering of unlabeled URIs. +--- +---@field default markview.config.asciidoc_inline.uris.opts Default configuration for URIs. +---@field [string] markview.config.asciidoc_inline.uris.opts Configuration for URIs that matches `string`. + + +---@class markview.config.asciidoc_inline.uris.opts +--- +---@field corner_left? string Left corner. +---@field corner_left_hl? string Highlight group for the left corner. +--- +---@field padding_left? string Left padding(added after `corner_left`). +---@field padding_left_hl? string Highlight group for the left padding. +--- +---@field icon? string Icon(added after `padding_left`). +---@field icon_hl? string Highlight group for the icon. +--- +--[[ Text to show instead of the `URL`.]] +---@field text? +---| string +---| function(buffer: integer, item: markview.parsed.asciidoc_inline.uris): string +---@field text_hl? string Highlight group for the text. +--- +---@field hl? string Default highlight group(used by `*_hl` options when they are not set). +--- +---@field padding_right? string Right padding. +---@field padding_right_hl? string Highlight group for the right padding. +--- +---@field corner_right? string Right corner(added after `padding_right`). +---@field corner_right_hl? string Highlight group for the right corner. + +------------------------------------------------------------------------------ ---@class markview.config.asciidoc_inline --- ---@field bolds markview.config.asciidoc_inline.bolds ---@field highlights markview.config.asciidoc_inline.highlights Highlighted text configuration. ---@field italics markview.config.asciidoc_inline.italics ---@field monospaces markview.config.asciidoc_inline.monospaces +---@field uris markview.config.asciidoc_inline.uris diff --git a/test/asciidoc.adoc b/test/asciidoc.adoc index 1c5472a..fdc1274 100644 --- a/test/asciidoc.adoc +++ b/test/asciidoc.adoc @@ -39,3 +39,13 @@ A [.myrole]#custom role# must be fulfilled by the theme. ~sub~script +https://asciidoctor.org - automatic! +"https://asciidoctor.org" - automatic! + +https://asciidoctor.org[Asciidoctor] + +devel@discuss.example.org + +mailto:devel@discuss.example.org[Discuss] + +mailto:join@discuss.example.org[Subscribe,Subscribe me,I want to join!] From b4a4eb2e8f34b375101f3337a64aadef298e6314 Mon Sep 17 00:00:00 2001 From: Shawon Date: Thu, 15 Jan 2026 18:43:46 +0600 Subject: [PATCH 15/19] feat(asciidoc_inline): Added `labeled uri` support --- lua/markview/parsers/asciidoc_inline.lua | 48 +++++++++++++-- lua/markview/renderers/asciidoc_inline.lua | 59 +++++++++++++++++++ .../types/parsers/asciidoc_inline.lua | 25 ++++++++ 3 files changed, 127 insertions(+), 5 deletions(-) diff --git a/lua/markview/parsers/asciidoc_inline.lua b/lua/markview/parsers/asciidoc_inline.lua index 8c35345..1bd4753 100644 --- a/lua/markview/parsers/asciidoc_inline.lua +++ b/lua/markview/parsers/asciidoc_inline.lua @@ -127,6 +127,30 @@ asciidoc_inline.monospace = function (buffer, TSNode, text, range) }); end +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.range +asciidoc_inline.labeled_uri = function (buffer, TSNode, text, range) + local destination; + + for child in TSNode:iter_children() do + if child:type() == "uri_label" then + _, range.label_col_start, _, range.label_col_end = child:range(); + elseif child:type() == "uri" then + destination = vim.treesitter.get_node_text(child, buffer, {}); + end + end + + asciidoc_inline.insert({ + class = "asciidoc_inline_labeled_uri", + destination = destination, + + text = text, + range = range + }); +end + ---@param buffer integer ---@param TSNode TSNode ---@param text string[] @@ -157,7 +181,6 @@ asciidoc_inline.uri = function (buffer, TSNode, text, range) }); end ---- HTML parser ---@param buffer integer ---@param TSTree TSTree ---@param from integer? @@ -180,7 +203,7 @@ asciidoc_inline.parse = function (buffer, TSTree, from, to) table.insert(asciidoc_inline.parsed_ranges, r_range); - local scanned_queries = vim.treesitter.query.parse("asciidoc_inline", [[ + local can_scan, scanned_queries = pcall(vim.treesitter.query.parse, "asciidoc_inline", [[ (emphasis) @asciidoc_inline.bold (ltalic) @asciidoc_inline.italic (monospace) @asciidoc_inline.monospace @@ -188,11 +211,26 @@ asciidoc_inline.parse = function (buffer, TSTree, from, to) (autolink (uri)) @asciidoc_inline.uri - ; - ; (autolink - ; (labeled_uri)) @asciidoc_inline.labeled_uri + + (labled_uri + (uri)) @asciidoc_inline.labeled_uri ]]); + if not can_scan then + require("markview.health").print({ + kind = "ERR", + + from = "parsers/asciidoc_inline.lua", + fn = "parse() -> query", + + message = { + { tostring(error), "DiagnosticError" } + } + }); + + return asciidoc_inline.content, asciidoc_inline.sorted; + end + for capture_id, capture_node, _, _ in scanned_queries:iter_captures(TSTree:root(), buffer, from, to) do local capture_name = scanned_queries.captures[capture_id]; diff --git a/lua/markview/renderers/asciidoc_inline.lua b/lua/markview/renderers/asciidoc_inline.lua index 9bfc813..8fae022 100644 --- a/lua/markview/renderers/asciidoc_inline.lua +++ b/lua/markview/renderers/asciidoc_inline.lua @@ -157,6 +157,65 @@ asciidoc_inline.monospace = function (buffer, item) }); end +---@param buffer integer +---@param item markview.parsed.asciidoc_inline.labeled_uris +asciidoc_inline.labeled_uri = function (buffer, item) + ---@type markview.config.asciidoc_inline.uris? + local main_config = spec.get({ "asciidoc_inline", "uris" }, { fallback = nil }); + local range = item.range; + + if not main_config then + return; + end + + ---@type markview.config.asciidoc_inline.uris.opts? + local config = utils.match( + main_config, + item.destination or "", + { + eval_args = { buffer, item } + } + ); + + if config == nil then + return; + end + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_start, range.col_start, { + end_col = range.label_col_start or range.col_start, + conceal = "", + + virt_text_pos = "inline", + virt_text = { + { config.corner_left or "", utils.set_hl(config.corner_left_hl or config.hl) }, + { config.padding_left or "", utils.set_hl(config.padding_left_hl or config.hl) }, + { config.icon or "", utils.set_hl(config.icon_hl or config.hl) } + }, + + hl_mode = "combine" + }); + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_start, range.label_col_start or range.col_start, { + end_col = range.label_col_end or range.col_end, end_row = range.row_end, + + hl_group = utils.set_hl(config.hl), + hl_mode = "combine" + }); + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_end, range.label_col_end or range.col_end, { + end_col = range.col_end, + conceal = "", + + virt_text_pos = "inline", + virt_text = { + { config.corner_right or "", utils.set_hl(config.corner_right_hl or config.hl) }, + { config.padding_right or "", utils.set_hl(config.padding_right_hl or config.hl) } + }, + + hl_mode = "combine" + }); +end + ---@param buffer integer ---@param item markview.parsed.asciidoc_inline.uris asciidoc_inline.uri = function (buffer, item) diff --git a/lua/markview/types/parsers/asciidoc_inline.lua b/lua/markview/types/parsers/asciidoc_inline.lua index 396847b..02a5d08 100644 --- a/lua/markview/types/parsers/asciidoc_inline.lua +++ b/lua/markview/types/parsers/asciidoc_inline.lua @@ -37,6 +37,27 @@ ---@field range markview.parsed.range +---@class markview.parsed.asciidoc_inline.labeled_uris +--- +---@field class "asciidoc_inline_labeled_uri" +---@field destination string URL the node is pointing to. +--- +---@field text string[] +---@field range markview.parsed.asciidoc_inline.labeled_uris.range + + +---@class markview.parsed.asciidoc_inline.labeled_uris.range +--- +---@field row_start integer +---@field col_start integer +--- +---@field row_end integer +---@field col_end integer +--- +---@field label_col_start? integer Start column of the **label** of an URI(e.g. `foo` in `https://example.com[foo]`). +---@field label_col_end? integer End column of the **label** of an URI. + + ---@class markview.parsed.asciidoc_inline.uris --- ---@field class "asciidoc_inline_uri" @@ -52,6 +73,8 @@ ---| markview.parsed.asciidoc_inline.highlights ---| markview.parsed.asciidoc_inline.italics ---| markview.parsed.asciidoc_inline.monospaces +---| markview.parsed.asciidoc_inline.labeled_uris +---| markview.parsed.asciidoc_inline.uris ---@class markview.parsed.asciidoc_inline_sorted @@ -60,4 +83,6 @@ ---@field italics markview.parsed.asciidoc_inline.italics ---@field monospaces markview.parsed.asciidoc_inline.monospaces ---@field highlights markview.parsed.asciidoc_inline.highlights +---@field labeled_uris markview.parsed.asciidoc_inline.labeled_uris +---@field uris markview.parsed.asciidoc_inline.uris From f1f11678988fec1ba86122208bb9c2bf75a3fad2 Mon Sep 17 00:00:00 2001 From: Shawon Date: Thu, 15 Jan 2026 20:13:36 +0600 Subject: [PATCH 16/19] feat(asciidoc_inline): Added `uri macro` support --- lua/markview/parsers/asciidoc_inline.lua | 48 ++++++++++++++++++- .../types/parsers/asciidoc_inline.lua | 3 +- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/lua/markview/parsers/asciidoc_inline.lua b/lua/markview/parsers/asciidoc_inline.lua index 1bd4753..18cd718 100644 --- a/lua/markview/parsers/asciidoc_inline.lua +++ b/lua/markview/parsers/asciidoc_inline.lua @@ -130,7 +130,7 @@ end ---@param buffer integer ---@param TSNode TSNode ---@param text string[] ----@param range markview.parsed.range +---@param range markview.parsed.asciidoc_inline.labeled_uris.range asciidoc_inline.labeled_uri = function (buffer, TSNode, text, range) local destination; @@ -181,6 +181,46 @@ asciidoc_inline.uri = function (buffer, TSNode, text, range) }); end +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.asciidoc_inline.labeled_uris.range +asciidoc_inline.uri_macro = function (buffer, TSNode, text, range) + local kind, destination; + + for child in TSNode:iter_children() do + if child:type() == "macro_name" then + kind = vim.treesitter.get_node_text(child, buffer, {}); + elseif child:type() == "target" then + destination = vim.treesitter.get_node_text(child, buffer, {}); + elseif child:type() == "attr" then + local attr = vim.treesitter.get_node_text(child, buffer, {}); + + if string.match(attr, '^".*"$') or not string.match(attr, "[,=]") then + _, range.label_col_start, _, range.label_col_end = child:range(); + else + local R = { child:range() }; + local target = string.match(attr, '^"[^"]*"') or string.match(attr, "^[^,]*") or ""; + target = string.gsub(target, "%s+$", ""); + + local spaces_before = #string.match(target, "^%s*"); + + range.label_col_start = R[2] + spaces_before; + range.label_col_end = R[2] + spaces_before + #target; + end + end + end + + asciidoc_inline.insert({ + class = "asciidoc_inline_labeled_uri", + kind = kind, + destination = destination, + + text = text, + range = range + }); +end + ---@param buffer integer ---@param TSTree TSTree ---@param from integer? @@ -214,6 +254,12 @@ asciidoc_inline.parse = function (buffer, TSTree, from, to) (labled_uri (uri)) @asciidoc_inline.labeled_uri + + (inline_macro + ((macro_name) @uri_macro_name + (#any-of? @uri_macro_name "link" "mailto")) + (target) + (attr)) @asciidoc_inline.uri_macro ]]); if not can_scan then diff --git a/lua/markview/types/parsers/asciidoc_inline.lua b/lua/markview/types/parsers/asciidoc_inline.lua index 02a5d08..0f0ef30 100644 --- a/lua/markview/types/parsers/asciidoc_inline.lua +++ b/lua/markview/types/parsers/asciidoc_inline.lua @@ -40,6 +40,7 @@ ---@class markview.parsed.asciidoc_inline.labeled_uris --- ---@field class "asciidoc_inline_labeled_uri" +---@field kind? string URI type(e.g. `mailto`). Only if the node is a **macro**. ---@field destination string URL the node is pointing to. --- ---@field text string[] @@ -54,7 +55,7 @@ ---@field row_end integer ---@field col_end integer --- ----@field label_col_start? integer Start column of the **label** of an URI(e.g. `foo` in `https://example.com[foo]`). +---@field label_col_start? integer Start column of the **label** of an URI(e.g. `foo` in `https://example.com[foo]` or `https://example.com[foo,bar]`). ---@field label_col_end? integer End column of the **label** of an URI. From 6e228298c72a3b5f813c342a0f171fe2ac8d4450 Mon Sep 17 00:00:00 2001 From: Shawon Date: Fri, 16 Jan 2026 00:24:16 +0600 Subject: [PATCH 17/19] fix(asciidoc): Fixed incorrect type definitions --- lua/markview/types/parsers/asciidoc.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lua/markview/types/parsers/asciidoc.lua b/lua/markview/types/parsers/asciidoc.lua index f8b80e2..7e788d9 100644 --- a/lua/markview/types/parsers/asciidoc.lua +++ b/lua/markview/types/parsers/asciidoc.lua @@ -4,7 +4,7 @@ ---@class markview.parsed.asciidoc.document_attributes --- ----@field class_name "asciidoc_document_attribute" +---@field class "asciidoc_document_attribute" --- ---@field text string[] ---@field range markview.parsed.range @@ -13,7 +13,7 @@ ---@class markview.parsed.asciidoc.document_titles --- ----@field class_name "asciidoc_document_title" +---@field class "asciidoc_document_title" --- ---@field text string[] ---@field range markview.parsed.range @@ -21,13 +21,13 @@ ------------------------------------------------------------------------------ ---@alias markview.parsed.asciidoc ----| markview.config.asciidoc.document_attributes ----| markview.config.asciidoc.document_titles +---| markview.parsed.asciidoc.document_attributes +---| markview.parsed.asciidoc.document_titles ------------------------------------------------------------------------------ ---@class markview.parsed.asciidoc_sorted --- ----@field document_attributes markview.config.asciidoc.document_attributes[] ----@field document_titles markview.config.asciidoc.document_titles[] +---@field document_attributes markview.parsed.asciidoc.document_attributes[] +---@field document_titles markview.parsed.asciidoc.document_titles[] From 99f66416f46db565c4061e3141102100c3315aa5 Mon Sep 17 00:00:00 2001 From: Shawon Date: Fri, 16 Jan 2026 00:25:13 +0600 Subject: [PATCH 18/19] feat(asciidoc): Added `section_title` support --- lua/markview/config/asciidoc.lua | 33 +++++++++++++++++ lua/markview/parsers/asciidoc.lua | 45 ++++++++++++++++++++++- lua/markview/renderers/asciidoc.lua | 37 +++++++++++++++++++ lua/markview/types/parsers/asciidoc.lua | 12 ++++++ lua/markview/types/renderers/asciidoc.lua | 25 +++++++++++++ test/asciidoc.adoc | 2 + 6 files changed, 153 insertions(+), 1 deletion(-) diff --git a/lua/markview/config/asciidoc.lua b/lua/markview/config/asciidoc.lua index e75bb72..2717a98 100644 --- a/lua/markview/config/asciidoc.lua +++ b/lua/markview/config/asciidoc.lua @@ -9,6 +9,39 @@ return { icon = "󰛓 ", hl = "MarkviewPalette7", }, + section_titles = { + enable = true, + + title_1 = { + sign = "󰌕 ", sign_hl = "MarkviewHeading1Sign", + + icon = "󰼏 ", hl = "MarkviewHeading1", + }, + title_2 = { + sign = "󰌖 ", sign_hl = "MarkviewHeading2Sign", + + icon = "󰎨 ", hl = "MarkviewHeading2", + }, + title_3 = { + + icon = "󰼑 ", hl = "MarkviewHeading3", + }, + title_4 = { + + icon = "󰎲 ", hl = "MarkviewHeading4", + }, + title_5 = { + + icon = "󰼓 ", hl = "MarkviewHeading5", + }, + title_6 = { + + icon = "󰎴 ", hl = "MarkviewHeading6", + }, + + shift_width = 1, + }, + document_attributes = { enable = true, }, diff --git a/lua/markview/parsers/asciidoc.lua b/lua/markview/parsers/asciidoc.lua index 96e8cae..eb6a05f 100644 --- a/lua/markview/parsers/asciidoc.lua +++ b/lua/markview/parsers/asciidoc.lua @@ -49,6 +49,26 @@ asciidoc.doc_title = function (_, _, text, range) }); end +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.range +asciidoc.section_title = function (buffer, TSNode, text, range) + local marker = TSNode:child(0); + + if not marker then + return; + end + + asciidoc.insert({ + class = "asciidoc_section_title", + marker = vim.treesitter.get_node_text(marker, buffer, {}), + + text = text, + range = range + }); +end + --- HTML parser ---@param buffer integer ---@param TSTree table @@ -61,11 +81,34 @@ asciidoc.parse = function (buffer, TSTree, from, to) asciidoc.sorted = {}; asciidoc.content = {}; - local scanned_queries = vim.treesitter.query.parse("asciidoc", [[ + local can_scan, scanned_queries = pcall(vim.treesitter.query.parse, "asciidoc", [[ (document_title) @asciidoc.doc_title (document_attr) @asciidoc.doc_attr + + [ + (title1) + (title2) + (title3) + (title4) + (title5) + ] @asciidoc.section_title ]]); + if not can_scan then + require("markview.health").print({ + kind = "ERR", + + from = "parsers/asciidoc.lua", + fn = "parse() -> query", + + message = { + { tostring(error), "DiagnosticError" } + } + }); + + return asciidoc.content, asciidoc.sorted; + end + for capture_id, capture_node, _, _ in scanned_queries:iter_captures(TSTree:root(), buffer, from, to) do local capture_name = scanned_queries.captures[capture_id]; diff --git a/lua/markview/renderers/asciidoc.lua b/lua/markview/renderers/asciidoc.lua index b29788c..71df630 100644 --- a/lua/markview/renderers/asciidoc.lua +++ b/lua/markview/renderers/asciidoc.lua @@ -50,6 +50,43 @@ asciidoc.document_title = function (buffer, item) }); end +--- Renders atx headings. +---@param buffer integer +---@param item markview.parsed.asciidoc.section_titles +asciidoc.section_title = function (buffer, item) + ---@type markview.config.asciidoc.section_titles? + local main_config = spec.get({ "asciidoc", "section_titles" }, { fallback = nil, eval_args = { buffer, item } }); + + if not main_config then + return; + end + + ---@type markview.config.asciidoc.section_titles.opts? + local config = spec.get({ "title_" .. (#item.marker - 1) }, { source = main_config, eval_args = { buffer, item } }); + + if not config then + return; + end + + local shift_width = spec.get({ "shift_width" }, { source = main_config, fallback = 1, eval_args = { buffer, item } }); + + local range = item.range; + + utils.set_extmark(buffer, asciidoc.ns, range.row_start, range.col_start, { + end_col = range.col_start + #item.marker, + conceal = "", + + sign_text = tostring(config.sign or ""), + sign_hl_group = utils.set_hl(config.sign_hl), + + virt_text = { + { string.rep(" ", (#item.marker - 1) * shift_width) }, + { config.icon, config.icon_hl or config.hl }, + }, + line_hl_group = utils.set_hl(config.hl), + }); +end + ---@param buffer integer ---@param content markview.parsed.asciidoc[] asciidoc.render = function (buffer, content) diff --git a/lua/markview/types/parsers/asciidoc.lua b/lua/markview/types/parsers/asciidoc.lua index 7e788d9..3991e69 100644 --- a/lua/markview/types/parsers/asciidoc.lua +++ b/lua/markview/types/parsers/asciidoc.lua @@ -20,9 +20,20 @@ ------------------------------------------------------------------------------ +---@class markview.parsed.asciidoc.section_titles +--- +---@field class "asciidoc_section_title" +---@field marker string The `=` part of the title. +--- +---@field text string[] +---@field range markview.parsed.range + +------------------------------------------------------------------------------ + ---@alias markview.parsed.asciidoc ---| markview.parsed.asciidoc.document_attributes ---| markview.parsed.asciidoc.document_titles +---| markview.parsed.asciidoc.section_titles ------------------------------------------------------------------------------ @@ -30,4 +41,5 @@ --- ---@field document_attributes markview.parsed.asciidoc.document_attributes[] ---@field document_titles markview.parsed.asciidoc.document_titles[] +---@field section_titles markview.parsed.asciidoc.section_titles[] diff --git a/lua/markview/types/renderers/asciidoc.lua b/lua/markview/types/renderers/asciidoc.lua index 1626321..af0838f 100644 --- a/lua/markview/types/renderers/asciidoc.lua +++ b/lua/markview/types/renderers/asciidoc.lua @@ -22,8 +22,33 @@ ------------------------------------------------------------------------------ +---@class markview.config.asciidoc.section_titles +--- +---@field enable boolean +---@field shift_width integer +--- +---@field title_1 markview.config.asciidoc.section_titles.opts +---@field title_2 markview.config.asciidoc.section_titles.opts +---@field title_3 markview.config.asciidoc.section_titles.opts +---@field title_4 markview.config.asciidoc.section_titles.opts +---@field title_5 markview.config.asciidoc.section_titles.opts + + +---@class markview.config.asciidoc.section_titles.opts +--- +---@field icon? string +---@field icon_hl? string +--- +---@field sign? string +---@field sign_hl? string +--- +---@field hl? string + +------------------------------------------------------------------------------ + ---@class markview.config.asciidoc --- ---@field document_attributes markview.config.asciidoc.document_attributes ---@field document_titles markview.config.asciidoc.document_titles +---@field section_titles markview.config.asciidoc.section_titles diff --git a/test/asciidoc.adoc b/test/asciidoc.adoc index fdc1274..b08f801 100644 --- a/test/asciidoc.adoc +++ b/test/asciidoc.adoc @@ -1,5 +1,7 @@ = Hello world +== hii + :bye: hi :hi: 123 From 69226e424188728e15c5504746f18bbdc20704a8 Mon Sep 17 00:00:00 2001 From: Shawon Date: Fri, 16 Jan 2026 14:21:57 +0600 Subject: [PATCH 19/19] feat(asciidoc): Added `TOC` support --- lua/markview/config/asciidoc.lua | 39 +++ lua/markview/parsers/asciidoc.lua | 213 ++++++++++++---- lua/markview/renderers/asciidoc.lua | 80 ++++++ lua/markview/renderers/asciidoc/tostring.lua | 241 +++++++++++++++++++ lua/markview/types/parsers/asciidoc.lua | 41 ++++ lua/markview/types/renderers/asciidoc.lua | 30 +++ test/asciidoc.adoc | 11 + 7 files changed, 607 insertions(+), 48 deletions(-) create mode 100644 lua/markview/renderers/asciidoc/tostring.lua diff --git a/lua/markview/config/asciidoc.lua b/lua/markview/config/asciidoc.lua index 2717a98..4fdd7e8 100644 --- a/lua/markview/config/asciidoc.lua +++ b/lua/markview/config/asciidoc.lua @@ -45,4 +45,43 @@ return { document_attributes = { enable = true, }, + + tocs = { + shift_width = 2, + hl = "MarkviewPalette2Fg", + + sign = "󰙅 ", + sign_hl = "MarkviewPalette2Sign", + + depth_1 = { + icon = "◆ ", + icon_hl = "Comment", + + hl = "MarkviewPalette5Fg", + }, + depth_2 = { + icon = "◇ ", + icon_hl = "Comment", + + hl = "MarkviewPalette5Fg", + }, + depth_3 = { + icon = "◆ ", + icon_hl = "Comment", + + hl = "MarkviewPalette5Fg", + }, + depth_4 = { + icon = "◇ ", + icon_hl = "Comment", + + hl = "MarkviewPalette5Fg", + }, + depth_5 = { + icon = "◆ ", + icon_hl = "Comment", + + hl = "MarkviewPalette5Fg", + }, + }, }; diff --git a/lua/markview/parsers/asciidoc.lua b/lua/markview/parsers/asciidoc.lua index eb6a05f..3aafe9f 100644 --- a/lua/markview/parsers/asciidoc.lua +++ b/lua/markview/parsers/asciidoc.lua @@ -1,9 +1,8 @@ --- HTML parser for `markview.nvim`. local asciidoc = {}; -asciidoc.data = { - document_title = nil, -}; +---@type markview.parser.asciidoc.data +asciidoc.data = {}; --- Queried contents ---@type table[] @@ -25,9 +24,32 @@ asciidoc.insert = function (data) table.insert(asciidoc.sorted[data.class], data); end +---@param buffer integer +---@param TSNode TSNode ---@param text string[] ---@param range markview.parsed.range -asciidoc.doc_attr = function (_, _, text, range) +asciidoc.doc_attr = function (buffer, TSNode, text, range) + local _name = TSNode:named_child(1) --[[@as TSNode]]; + local name = vim.treesitter.get_node_text(_name, buffer, {}); + + local _value = TSNode:named_child(3); + + if name == "toc" then + return; + elseif name == "toc-title" and _value then + asciidoc.data.toc_title = vim.treesitter.get_node_text(_value, buffer, {}); + elseif name == "toclevels" and _value then + asciidoc.data.toc_max_depth = math.max( + math.min( + tonumber( + vim.treesitter.get_node_text(_value, buffer, {}) + ) or 0, + 5 + ), + 0 + ); + end + asciidoc.insert({ class = "asciidoc_document_attribute", @@ -54,15 +76,72 @@ end ---@param text string[] ---@param range markview.parsed.range asciidoc.section_title = function (buffer, TSNode, text, range) - local marker = TSNode:child(0); + local _marker = TSNode:child(0); - if not marker then + if not _marker then return; end + local marker = vim.treesitter.get_node_text(_marker, buffer, {}); + local prev = TSNode:prev_named_sibling(); + + if prev then + local prev_text = vim.treesitter.get_node_text(prev, buffer, {}); + + if prev:type() == "element_attr" and prev_text == "[discrete]" then + goto dont_add_to_toc; + end + end + + if not asciidoc.data.toc_entries then + asciidoc.data.toc_entries = {}; + end + + table.insert(asciidoc.data.toc_entries, { + depth = (#marker or 1) - 1, + text = string.gsub(text[1] or "", "^[=%s]+", ""), + + range = vim.deepcopy(range, true), + } --[[@as markview.parser.asciidoc.data.toc_entry]]); + + ::dont_add_to_toc:: + asciidoc.insert({ class = "asciidoc_section_title", - marker = vim.treesitter.get_node_text(marker, buffer, {}), + marker = marker, + + text = text, + range = range + }); +end + +---@param text string[] +---@param range markview.parsed.range +asciidoc.toc_pos = function (_, _, text, range) + range.col_end = range.col_start + #(text[1] or ""); + asciidoc.data.toc_pos = range; +end + +---@param text string[] +---@param range markview.parsed.asciidoc.tocs.range +asciidoc.toc = function (_, _, text, range) + local validated = {}; + + for _, entry in ipairs(asciidoc.data.toc_entries or {}) do + if entry.depth < (asciidoc.data.toc_max_depth or 5) then + table.insert(validated, entry); + end + end + + range.col_end = range.col_start + #(text[1] or ""); + range.position = asciidoc.data.toc_pos; + + asciidoc.insert({ + class = "asciidoc_toc", + + title = asciidoc.data.toc_title, + max_depth = asciidoc.data.toc_max_depth, + entries = validated, text = text, range = range @@ -78,6 +157,7 @@ end ---@return markview.parsed.asciidoc_sorted asciidoc.parse = function (buffer, TSTree, from, to) -- Clear the previous contents + asciidoc.data = {}; asciidoc.sorted = {}; asciidoc.content = {}; @@ -92,6 +172,12 @@ asciidoc.parse = function (buffer, TSTree, from, to) (title4) (title5) ] @asciidoc.section_title + + (block_macro + ( + (block_macro_name) @toc_pos_name + (#eq? @toc_pos_name "toc") + )) @asciidoc.toc_pos ]]); if not can_scan then @@ -109,61 +195,92 @@ asciidoc.parse = function (buffer, TSTree, from, to) return asciidoc.content, asciidoc.sorted; end - for capture_id, capture_node, _, _ in scanned_queries:iter_captures(TSTree:root(), buffer, from, to) do - local capture_name = scanned_queries.captures[capture_id]; + local function iter (queries) + ---|fS - if not capture_name:match("^asciidoc%.") then - goto continue; - end + for capture_id, capture_node, _, _ in queries:iter_captures(TSTree:root(), buffer, from, to) do + local capture_name = queries.captures[capture_id]; - ---@type string? - local capture_text = vim.treesitter.get_node_text(capture_node, buffer); - local r_start, c_start, r_end, c_end = capture_node:range(); + if not capture_name:match("^asciidoc%.") then + goto continue; + end - if capture_text == nil then - goto continue; - end + ---@type string? + local capture_text = vim.treesitter.get_node_text(capture_node, buffer); + local r_start, c_start, r_end, c_end = capture_node:range(); - if not capture_text:match("\n$") then - capture_text = capture_text .. "\n"; - end + if capture_text == nil then + goto continue; + end - local lines = {}; + if not capture_text:match("\n$") then + capture_text = capture_text .. "\n"; + end - for line in capture_text:gmatch("(.-)\n") do - table.insert(lines, line); - end + local lines = {}; - ---@type boolean, string - local success, error = pcall( - asciidoc[capture_name:gsub("^asciidoc%.", "")], + for line in capture_text:gmatch("(.-)\n") do + table.insert(lines, line); + end - buffer, - capture_node, - lines, - { - row_start = r_start, - col_start = c_start, + ---@type boolean, string + local success, error = pcall( + asciidoc[capture_name:gsub("^asciidoc%.", "")], - row_end = r_end, - col_end = c_end - } - ); + buffer, + capture_node, + lines, + { + row_start = r_start, + col_start = c_start, - if success == false then - require("markview.health").print({ - kind = "ERR", + row_end = r_end, + col_end = c_end + } + ); - from = "parsers/asciidoc.lua", - fn = "parse()", + if success == false then + require("markview.health").print({ + kind = "ERR", - message = { - { tostring(error), "DiagnosticError" } - } - }); + from = "parsers/asciidoc.lua", + fn = "parse()", + + message = { + { tostring(error), "DiagnosticError" } + } + }); + end + + ::continue:: end - ::continue:: + ---|fE + end + + iter(scanned_queries); + + local can_scan_tquery, scanned_tqueries = pcall(vim.treesitter.query.parse, "asciidoc", [[ + (document_attr + ( + (attr_name) @toc_attr + (#eq? @toc_attr "toc") + )) @asciidoc.toc + ]]); + + if not can_scan_tquery then + require("markview.health").print({ + kind = "ERR", + + from = "parsers/asciidoc.lua", + fn = "parse() -> toc_query", + + message = { + { tostring(error), "DiagnosticError" } + } + }); + else + iter(scanned_tqueries); end return asciidoc.content, asciidoc.sorted; diff --git a/lua/markview/renderers/asciidoc.lua b/lua/markview/renderers/asciidoc.lua index 71df630..8445087 100644 --- a/lua/markview/renderers/asciidoc.lua +++ b/lua/markview/renderers/asciidoc.lua @@ -87,6 +87,86 @@ asciidoc.section_title = function (buffer, item) }); end +---@param buffer integer +---@param item markview.parsed.asciidoc.tocs +asciidoc.toc = function (buffer, item) + ---@type markview.config.asciidoc.tocs? + local main_config = spec.get({ "asciidoc", "tocs" }, { fallback = nil, eval_args = { buffer, item } }); + + if not main_config then + return; + end + + local range = item.range; + local lines = {}; + + table.insert(lines, { + { main_config.icon or "", main_config.icon_hl or main_config.hl }, + { item.title or "Table of contents", main_config.hl }, + }); + + if item.entries and #item.entries > 0 then + table.insert(lines, { { "" } }); + end + + for _, entry in ipairs(item.entries or {}) do + ---@type markview.config.asciidoc.tocs.opts? + local config = spec.get({ "depth_" .. (entry.depth or 1) }, { source = main_config, eval_args = { buffer, item } }); + + if config then + local text = require("markview.renderers.asciidoc.tostring").tostring(buffer, entry.text, config.hl); + local shift_by = (main_config.shift_width or 1) * ( (entry.depth or 1) - 1 ); + + local line = { + { string.rep(config.shift_char or " ", shift_by), config.hl }, + { config.icon or "", config.icon_hl or config.hl }, + }; + + vim.list_extend(line, text); + table.insert(lines, line); + end + end + + local title = table.remove(lines, 1); + + if range.position then + utils.set_extmark(buffer, asciidoc.ns, range.row_start, range.col_start, { + end_row = range.row_end - 1, + conceal_lines = "", + }); + + local r_pos = range.position --[[@as markview.parsed.range]]; + + utils.set_extmark(buffer, asciidoc.ns, r_pos.row_start, r_pos.col_start, { + end_col = r_pos.col_end, + conceal = "", + + virt_text = title, + virt_text_pos = "inline", + + sign_text = main_config.sign or "", + sign_hl_group = utils.set_hl(main_config.sign_hl), + + virt_lines = lines, + hl_mode = "combine", + }); + else + utils.set_extmark(buffer, asciidoc.ns, range.row_start, range.col_start, { + end_col = range.col_end, + conceal = "", + + virt_text = title, + virt_text_pos = "inline", + + sign_text = main_config.sign or "", + sign_hl_group = utils.set_hl(main_config.sign_hl), + + virt_lines = lines, + hl_mode = "combine", + }); + end +end + ---@param buffer integer ---@param content markview.parsed.asciidoc[] asciidoc.render = function (buffer, content) diff --git a/lua/markview/renderers/asciidoc/tostring.lua b/lua/markview/renderers/asciidoc/tostring.lua new file mode 100644 index 0000000..510d214 --- /dev/null +++ b/lua/markview/renderers/asciidoc/tostring.lua @@ -0,0 +1,241 @@ +local adoc_str = {}; + +local utils = require("markview.utils"); +local spec = require("markview.spec"); + +adoc_str.buffer = -1; + +---@param match string +adoc_str.bold = function (match) + ---|fS + + ---@type markview.config.asciidoc_inline.bolds? + local config = spec.get({ + "asciidoc_inline", "bolds" + }, { + eval_args = { + adoc_str.buffer, { + class = "asciidoc_inline_bold", + delimiters = string.match(match, "^%*%*") and { "**", "**" } or { "*", "*" }, + + text = { match }, + range = { + row_start = -1, + col_start = -1, + + row_end = -1, + col_end = -1, + } + } --[[@as markview.parsed.asciidoc_inline.bolds]] + } + }); + + if not config then + return { match }; + else + local removed = string.gsub(match, "^%*+", ""):gsub("%*+$", ""); + return { removed }; + end + + ---|fE +end + +---@param match string +adoc_str.italic = function (match) + ---|fS + + ---@type markview.config.asciidoc_inline.italics? + local config = spec.get({ + "asciidoc_inline", "italics" + }, { + eval_args = { + adoc_str.buffer, { + class = "asciidoc_inline_italic", + delimiters = string.match(match, "^__") and { "__", "__" } or { "_", "_" }, + + text = { match }, + range = { + row_start = -1, + col_start = -1, + + row_end = -1, + col_end = -1, + } + } --[[@as markview.parsed.asciidoc_inline.italics]] + } + }); + + if not config then + return { match }; + else + local removed = string.gsub(match, "^[%*_]+", ""):gsub("[%*_]+$", ""); + return { removed }; + end + + ---|fE +end + +---@param match string +adoc_str.monospace = function (match) + ---|fS + + local delimiter = string.match(match, "^`+"); + + ---@type markview.config.asciidoc_inline.monospaces? + local config = spec.get({ + "asciidoc_inline", "monospaces" + }, { + eval_args = { + adoc_str.buffer, { + class = "asciidoc_inline_monospace", + delimiters = { delimiter, delimiter }, + + text = { match }, + range = { + row_start = -1, + col_start = -1, + + row_end = -1, + col_end = -1, + } + } --[[@as markview.parsed.asciidoc_inline.monospaces]] + } + }); + + if not config then + return { match }; + else + local removed = string.gsub(match, "^`+", ""):gsub("`+$", ""); + local output = {}; + + if config.corner_left then table.insert(output, { config.corner_left, config.corner_left_hl or config.hl }) end + if config.padding_left then table.insert(output, { config.padding_left, config.padding_left_hl or config.hl }) end + if config.icon then table.insert(output, { config.icon, config.icon_hl or config.hl }) end + + table.insert(output, { removed, config.hl }); + + if config.padding_right then table.insert(output, { config.padding_right, config.padding_right_hl or config.hl }) end + if config.corner_right then table.insert(output, { config.corner_right, config.corner_right_hl or config.hl }) end + + return output; + end + + ---|fE +end + +---@param match string +adoc_str.highlight = function (match) + ---|fS + + local delimiter = string.match(match, "#+"); + + ---@type markview.config.asciidoc_inline.highlights? + local main_config = spec.get({ "asciidoc_inline", "highlights" }, { fallback = nil }); + + if not main_config then + return { match }; + end + + ---@type markview.parsed.asciidoc_inline.highlights + local item = { + class = "asciidoc_inline_highlight", + delimiters = { delimiter, delimiter }, + + text = { match }, + range = { + row_start = -1, + col_start = -1, + + row_end = -1, + col_end = -1, + } + }; + + if not main_config then + return; + end + + ---@type markview.config.__inline? + local config = utils.match( + main_config, + table.concat(item.text, "\n"), + { + eval_args = { adoc_str.buffer, item } + } + ); + + if not config then + return { match }; + else + local removed = string.gsub(match, "^.-#+", ""):gsub("#+$", ""); + local output = {}; + + if config.corner_left then table.insert(output, { config.corner_left, config.corner_left_hl or config.hl }) end + if config.padding_left then table.insert(output, { config.padding_left, config.padding_left_hl or config.hl }) end + if config.icon then table.insert(output, { config.icon, config.icon_hl or config.hl }) end + + table.insert(output, { removed, config.hl }); + + if config.padding_right then table.insert(output, { config.padding_right, config.padding_right_hl or config.hl }) end + if config.corner_right then table.insert(output, { config.corner_right, config.corner_right_hl or config.hl }) end + + return output; + end + + ---|fE +end + +---@param char string +adoc_str.char = function (char) + return { char }; +end + +---@param buffer integer +---@param text string +---@param base_hl string +---@return [ string, string ][] +adoc_str.tostring = function (buffer, text, base_hl) + ---|fS + + local lpeg = vim.lpeg; + adoc_str.buffer = buffer; + + local strong_content = ( 1 - lpeg.P("*") ) + lpeg.P("\\*"); + local strong = lpeg.C( lpeg.P("*") * strong_content^1 * lpeg.P("*") ) / adoc_str.bold; + local ustrong = lpeg.C( lpeg.P("**") * strong_content^1 * lpeg.P("**") ) / adoc_str.bold; + + local italic_content = ( 1 - lpeg.P("_") ) + lpeg.P("\\_"); + local italic = lpeg.C( lpeg.P("_") * italic_content^1 * lpeg.P("_") ) / adoc_str.italic; + local uitalic = lpeg.C( lpeg.P("__") * italic_content^1 * lpeg.P("__") ) / adoc_str.italic; + + local mono_content = ( 1 - lpeg.P("`") ) + lpeg.P("\\`"); + local mono = lpeg.C( lpeg.P("`")^1 * mono_content^1 * lpeg.P("`")^1 ) / adoc_str.monospace; + + local hl_content = ( 1 - lpeg.P("##") ) + lpeg.P("\\#"); + local hl = lpeg.C( lpeg.P("##") * hl_content^1 * lpeg.P("##") ) / adoc_str.highlight; + local role_content = 1 - lpeg.P("]"); + local chl = lpeg.C( lpeg.P("[") * role_content^1 * lpeg.P("]") * lpeg.P("##") * hl_content^1 * lpeg.P("##") ) / adoc_str.highlight; + + local any = lpeg.C( lpeg.P(1) ) / adoc_str.char; + local token = ustrong + strong + uitalic + italic + mono + chl + hl + any; + + local inline = lpeg.Ct( token^0 ); + local result = {}; + + for _, item in ipairs(lpeg.match(inline, text or "")) do + local last = result[#result]; + item[2] = item[2] or base_hl; + + if (not item[2] or item[2] == base_hl) and last and (not last[2] or last[2] == base_hl) then + last[1] = last[1] .. item[1]; + else + table.insert(result, item) + end + end + + return result; + + ---|fE +end + +return adoc_str; diff --git a/lua/markview/types/parsers/asciidoc.lua b/lua/markview/types/parsers/asciidoc.lua index 3991e69..2e4854c 100644 --- a/lua/markview/types/parsers/asciidoc.lua +++ b/lua/markview/types/parsers/asciidoc.lua @@ -30,6 +30,29 @@ ------------------------------------------------------------------------------ +---@class markview.parsed.asciidoc.tocs +--- +---@field class "asciidoc_toc" +---@field title? string +---@field max_depth? integer +---@field entries markview.parser.asciidoc.data.toc_entry[] +--- +---@field text string[] +---@field range markview.parsed.asciidoc.tocs.range + + +---@class markview.parsed.asciidoc.tocs.range +--- +---@field row_start integer +---@field col_start integer +--- +---@field row_end integer +---@field col_end integer +--- +---@field position? markview.parsed.range + +------------------------------------------------------------------------------ + ---@alias markview.parsed.asciidoc ---| markview.parsed.asciidoc.document_attributes ---| markview.parsed.asciidoc.document_titles @@ -43,3 +66,21 @@ ---@field document_titles markview.parsed.asciidoc.document_titles[] ---@field section_titles markview.parsed.asciidoc.section_titles[] +------------------------------------------------------------------------------ + +---@class markview.parser.asciidoc.data +--- +---@field document_title? string +---@field toc_title? string +---@field toc_max_depth? integer +---@field toc_entries? markview.parser.asciidoc.data.toc_entry[] +---@field toc_pos? markview.parsed.range + + +---@class markview.parser.asciidoc.data.toc_entry +--- +---@field depth integer +--- +---@field text string +---@field range markview.parsed.range + diff --git a/lua/markview/types/renderers/asciidoc.lua b/lua/markview/types/renderers/asciidoc.lua index af0838f..1b5ce87 100644 --- a/lua/markview/types/renderers/asciidoc.lua +++ b/lua/markview/types/renderers/asciidoc.lua @@ -46,6 +46,36 @@ ------------------------------------------------------------------------------ +---@class markview.config.asciidoc.tocs +--- +---@field enable boolean +---@field shift_width integer +--- +---@field icon? string Icon for the TOC title. +---@field icon_hl? string Highlight group for `icon`. +--- +---@field sign? string Sign for the TOC title. +---@field sign_hl? string Highlight group for `sign`. +--- +---@field hl? string +--- +---@field depth_1 markview.config.asciidoc.tocs.opts +---@field depth_2 markview.config.asciidoc.tocs.opts +---@field depth_3 markview.config.asciidoc.tocs.opts +---@field depth_4 markview.config.asciidoc.tocs.opts +---@field depth_5 markview.config.asciidoc.tocs.opts + + +---@class markview.config.asciidoc.tocs.opts +--- +---@field shift_char? string +---@field hl? string +--- +---@field icon? string Icon for the TOC title. +---@field icon_hl? string Highlight group for `icon`. + +------------------------------------------------------------------------------ + ---@class markview.config.asciidoc --- ---@field document_attributes markview.config.asciidoc.document_attributes diff --git a/test/asciidoc.adoc b/test/asciidoc.adoc index b08f801..9f93ea3 100644 --- a/test/asciidoc.adoc +++ b/test/asciidoc.adoc @@ -1,11 +1,22 @@ = Hello world +:toc: +:toc-title: Hello + +[discrete] +== hii + + == hii +=== bye + :bye: hi :hi: 123 +toc::[] + It has *strong* significance to me. I _cannot_ stress this enough.