diff --git a/README.md b/README.md index c07afa7c..a75166e3 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ +
@@ -74,7 +75,7 @@ Core features, -+ Preview `Markdown`, HTML, $LaTeX$, `Typst` & `YAML` within Neovim. ++ Preview `Markdown`, HTML, $LaTeX$, `Typst` & `Asciidoc`(See [integrations#Asciidoc](https://github.com/OXY2DEV/markview.nvim/wiki/Usage#Asciidoc)) within Neovim. + *Hybrid* editing mode! Allowing *editing* & *previewing* at the same time. + *Splitview*! Allows editing & previewing *side-by-side*. + `Wrap` support(markdown only, at the moment)! Allows using text wrapping while not losing *most* rendering features! See [integrations#wrap](https://github.com/OXY2DEV/markview.nvim/wiki/Integrations#-wrap) for fixing visual glitches or [integrations#nowrap](https://github.com/OXY2DEV/markview.nvim/wiki/Integrations#-nowrap) for disabling it. @@ -106,6 +107,32 @@ Also see, Expand to see complete feature list +#### Asciidoc + + + +Supported syntax, + ++ Admonitions ++ Checkboxes(also supports custom checkbox states). ++ Horizontal rules ++ Literal blocks ++ Hiding document attributes ++ Image macros ++ Keycode macros ++ List items(ordered & unordered) ++ Automated TOC(Table of Contents) + +#### Asciidoc inline + +Supported syntax, + ++ Bold ++ Highlights ++ Italic ++ Monospace ++ URI + #### Fancy comments, @@ -405,6 +432,8 @@ Parsers, - `latex`(optional) - `typst`(optional) - `yaml`(optional) +- [tree-sitter-asciidoc](https://github.com/cathaysia/tree-sitter-asciidoc)(optional) + See [integrations#Asciidoc](https://github.com/OXY2DEV/markview.nvim/wiki/Usage#Asciidoc) to see how to get started. Fonts, diff --git a/doc/markview.nvim-asciidoc.txt b/doc/markview.nvim-asciidoc.txt new file mode 100644 index 00000000..e59d22ab --- /dev/null +++ b/doc/markview.nvim-asciidoc.txt @@ -0,0 +1,480 @@ +*markview.nvim-asciidoc* 🧩 Asciidoc options for `markview.nvim` + +>lua + ---@class markview.config.asciidoc + --- + ---@field enable boolean Enable rendering of Asciidoc files? + --- + ---@field admonitions markview.config.asciidoc.admonitions + ---@field checkboxes markview.config.asciidoc.checkboxes + ---@field document_attributes markview.config.asciidoc.document_attributes + ---@field document_titles markview.config.asciidoc.document_titles + ---@field horizontal_rules markview.config.asciidoc.hrs + ---@field list_items markview.config.asciidoc.list_items + ---@field section_titles markview.config.asciidoc.section_titles + ---@field tocs markview.config.asciidoc.tocs +< + +-------------------------------------------------------------------------------- +enable + +Enables previewing asciidoc files. + +>lua + enable = true +< + +-------------------------------------------------------------------------------- +admonitions + +>lua + --- Configuration for admonitions. + ---@class markview.config.asciidoc.admonitions + --- + ---@field enable boolean Enable rendering of admonitions. + --- + ---@field default markview.config.asciidoc.admonitions.opts Default configuration for admonitions. + ---@field [string] markview.config.asciidoc.admonitions.opts Configuration for `[string]` admonitions. +< + +Changes how admonitions are shown. + +>lua + admonitions = { + enable = true, + + default = { + padding_left = " ", + padding_right = " ", + + icon = "ξ©΄ ", + + hl = "MarkviewPalette5", + }, + + important = { + padding_left = " ", + padding_right = " ", + + icon = "ξ©΄ ", + + hl = "MarkviewPalette1", + }, + + tip = { + padding_left = " ", + padding_right = " ", + + icon = " ", + + hl = "MarkviewPalette4", + }, + + caution = { + padding_left = " ", + padding_right = " ", + + icon = " ", + + hl = "MarkviewPalette7", + }, + + warn = { + padding_left = " ", + padding_right = " ", + + icon = " ", + + hl = "MarkviewPalette3", + }, + }, +< + +Each admonition type has the following options. + +β–‹ ξ­‚ Important +β–‹ The key names are case-insensitive. So, `foo` & `FOO` refer to the same type. +β–‹ However, modifying `FOO` instead of `foo` can lead to undefined behavior. Use +β–‹ lower case for type names! + +>lua + ---@class markview.config.asciidoc.admonitions.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. + --- + ---@field hl? string Default highlight group(used by `*_hl` options when they are not set). + ---@field desc_hl? string Highlight group for the `description`. + --- + ---@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. +< + +-------------------------------------------------------------------------------- +document_attributes + +>lua + --[[ Options for document attributes. ]] + ---@class markview.config.asciidoc.document_attributes + --- + ---@field enable boolean Enable concealing of document attributes? Requires `conceal_lines` support. +< + +Hides document attributes + +>lua + document_attributes = { + enable = true, + }, +< + +-------------------------------------------------------------------------------- +hrs + +>lua + --- Configuration for horizontal rules. + ---@class markview.config.asciidoc.hrs + --- + ---@field enable boolean Enable preview of horizontal rules. + ---@field parts markview.config.asciidoc.hrs.part[] Parts for the horizontal rules. +< + +Changes how horizontal rules/thematic breaks are shown. + +>lua + horizontal_rules = { + enable = true, + + parts = { + { + type = "repeating", + direction = "left", + + repeat_amount = function (buffer) + local utils = require("markview.utils"); + local window = utils.buf_getwin(buffer) + + local width = vim.api.nvim_win_get_width(window) + local textoff = vim.fn.getwininfo(window)[1].textoff; + + return math.floor((width - textoff - 3) / 2); + end, + + text = "─", + + hl = { + "MarkviewGradient1", "MarkviewGradient1", + "MarkviewGradient2", "MarkviewGradient2", + "MarkviewGradient3", "MarkviewGradient3", + "MarkviewGradient4", "MarkviewGradient4", + "MarkviewGradient5", "MarkviewGradient5", + "MarkviewGradient6", "MarkviewGradient6", + "MarkviewGradient7", "MarkviewGradient7", + "MarkviewGradient8", "MarkviewGradient8", + "MarkviewGradient9", "MarkviewGradient9" + } + }, + { + type = "text", + + text = " ξͺͺ ", + hl = "MarkviewIcon3Fg" + }, + { + type = "repeating", + direction = "right", + + repeat_amount = function (buffer) --[[@as function]] + local utils = require("markview.utils"); + local window = utils.buf_getwin(buffer) + + local width = vim.api.nvim_win_get_width(window) + local textoff = vim.fn.getwininfo(window)[1].textoff; + + return math.ceil((width - textoff - 3) / 2); + end, + + text = "─", + hl = { + "MarkviewGradient1", "MarkviewGradient1", + "MarkviewGradient2", "MarkviewGradient2", + "MarkviewGradient3", "MarkviewGradient3", + "MarkviewGradient4", "MarkviewGradient4", + "MarkviewGradient5", "MarkviewGradient5", + "MarkviewGradient6", "MarkviewGradient6", + "MarkviewGradient7", "MarkviewGradient7", + "MarkviewGradient8", "MarkviewGradient8", + "MarkviewGradient9", "MarkviewGradient9" + } + } + } + }, +< + +parts ~ + +You can have any of the following parts. + +β–‹ Text + +Shows some text literally. + +>lua + ---@class markview.config.asciidoc.hrs.text + --- + ---@field type "text" Part name. + --- + ---@field hl? string Highlight group for this part. + ---@field text string Text to show. +< + +β–‹ Repeating + +Repeats given text by an amount. + +>lua + ---@class markview.config.asciidoc.hrs.repeating + --- + ---@field type "repeating" Part name. + --- + ---@field direction "left" | "right" Direction from which the highlight groups are applied from. + --- + ---@field repeat_amount integer | fun(buffer: integer, item: markview.parsed.asciidoc.hrs): integer How many times to repeat the text. + ---@field repeat_hl? boolean Whether to repeat the highlight groups. + ---@field repeat_text? boolean Whether to repeat the text. + --- + ---@field text string | string[] Text to repeat. + ---@field hl? string | string[] Highlight group for the text. +< + +-------------------------------------------------------------------------------- +list_items + +>lua + --- Configuration for list items. + ---@class markview.config.asciidoc.list_items + --- + ---@field enable boolean + ---@field shift_width integer | fun(buffer: integer, item: markview.parsed.markdown.list_items): integer Virtual indentation size for previewed list items. + --- + ---@field marker_dot markview.config.asciidoc.list_items.opts Configuration for `.` list items. + ---@field marker_minus markview.config.asciidoc.list_items.opts Configuration for `-` list items. + ---@field marker_star markview.config.asciidoc.list_items.opts Configuration for `*` list items. + --- + ---@field wrap? boolean Enables wrap support. +< + +Changes how list items are shown. + +>lua + list_items = { + enable = true, + shift_width = 4, + + marker_dot = { + add_padding = true, + conceal_on_checkboxes = true, + + text = function (_, item) + return string.format("%d.", item.n); + end, + hl = "@markup.list.markdown", + }, + + marker_minus = { + add_padding = true, + conceal_on_checkboxes = true, + + text = "●", + hl = "MarkviewListItemMinus" + }, + + marker_star = { + add_padding = true, + conceal_on_checkboxes = true, + + text = "β—‡", + hl = "MarkviewListItemStar" + }, + }, +< + +Each list item type has the following options. + +>lua + ---@class markview.config.asciidoc.list_items.opts + --- + ---@field enable? boolean Enable rendering of this list item type? + --- + ---@field add_padding boolean When `true`, Add padding before the list item. + ---@field conceal_on_checkboxes? boolean Should the list item marker be hidden if the item contains a `checkbox`? + --- + ---[[ Text used to replace the list item marker. ]] + ---@field text? + ---| string + ---| fun(buffer: integer, item: markview.parsed.asciidoc.list_items): string Dynamic marker. Used for stuff like adding list index to `ordered list items`. + --- + ---@field hl? string Highlight group for the `text`. +< + +-------------------------------------------------------------------------------- +section_titles + +>lua + --- Configuration for section titles. + ---@class markview.config.asciidoc.section_titles + --- + ---@field enable boolean Enable rendering of section titles. + ---@field shift_width integer Amount of spaces to add to the start for each title level. Useful to visualize nesting of sections. + --- + ---@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 +< + +Changes how section titles are shown. + +>lua + 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, + }, +< + +Each section title level has the following options. + +>lua + ---@class markview.config.asciidoc.section_titles.opts + --- + ---@field icon? string Icon added before the title. + ---@field icon_hl? string Highlight group for `icon`. + --- + ---@field sign? string Sign to show in the sign column. + ---@field sign_hl? string Highlight group for the `sign`. + --- + ---@field hl? string Fallback highlight group for the `*_hl` options. +< + +-------------------------------------------------------------------------------- +tocs + +>lua + --- Configuration for generated Table of contents section.. + ---@class markview.config.asciidoc.tocs + --- + ---@field enable boolean Enable rendering of automated TOC. + ---@field shift_width integer Amount if `shift_char` to add per item depth level. + --- + ---@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 Highlight group for the TOC title. + --- + ---@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 +< + +Changes how automatic TOCs are shown. + +>lua + tocs = { + enable = true, + + 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", + }, + }, +< + +Each item depth has the following options. + +>lua + --- Options for a specific item depth. + ---@class markview.config.asciidoc.tocs.opts + --- + ---@field shift_char? string The character used to shift the entry(helps visualize nesting, document structure). + ---@field hl? string Highlight group for the text. + --- + ---@field icon? string Icon for the TOC title. + ---@field icon_hl? string Highlight group for `icon`. +< + +vim:ft=help:textwidth=80:tabstop=4:noexpandtab: diff --git a/doc/markview.nvim-asciidoc_inline.txt b/doc/markview.nvim-asciidoc_inline.txt new file mode 100644 index 00000000..25632de6 --- /dev/null +++ b/doc/markview.nvim-asciidoc_inline.txt @@ -0,0 +1,444 @@ +*markview.nvim-asciidoc_inline* 🧩 Inline asciidoc options for `markview.nvim` + +>lua + ---@class markview.config.asciidoc_inline + --- + ---@field enable boolean Enable rendering of inline asciidoc. + --- + ---@field bolds markview.config.asciidoc_inline.bolds + ---@field highlights markview.config.asciidoc_inline.highlights + ---@field italics markview.config.asciidoc_inline.italics + ---@field monospaces markview.config.asciidoc_inline.monospaces + ---@field uris markview.config.asciidoc_inline.uris +< + +-------------------------------------------------------------------------------- +enable + +Enables previewing inline asciidoc. + +>lua + enable = true +< + +-------------------------------------------------------------------------------- +bolds + +>lua + ---@class markview.config.asciidoc_inline.bolds + --- + ---@field enable boolean +< + +Hides the delimiters surrounding bold text. + +>lua + bolds = { enable = true }, +< + +-------------------------------------------------------------------------------- +highlights + +>lua + --- 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`. +< + +Changes how specific highlights are shown. + +>lua + highlights = { + enable = true, + + default = { + padding_left = " ", + padding_right = " ", + + hl = "MarkviewPalette3" + } + }, +< + +Highlights can be made to look different based on lua patterns matching the +content(`default` also works the same way but acts as the default style). These +can have the following options. + +>lua + --[[ Options for a specific highlight type. ]] + ---@alias markview.config.asciidoc_inline.highlights.opts markview.config.__inline +< + +-------------------------------------------------------------------------------- +italics + +>lua + ---@class markview.config.asciidoc_inline.italics + --- + ---@field enable boolean +< + +Hides the delimiters surrounding italic text. + +>lua + italics = { enable = true }, +< + +-------------------------------------------------------------------------------- +monospaces + +>lua + ---@alias markview.config.asciidoc_inline.monospaces markview.config.__inline +< + +Hides the delimiters surrounding `monospace text`. + +>lua + monospaces = { + enable = true, + hl = "MarkviewInlineCode", + + padding_left = " ", + padding_right = " " + }, +< + +-------------------------------------------------------------------------------- +uris + +>lua + ---@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`. +< + +Changes how specific uris are shown. + +>lua + 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 + }, +< + +Each uri type has the following options. + +>lua + --[[ Options for a specific highlight type. ]] + ---@alias markview.config.asciidoc_inline.uris.opts markview.config.__inline +< + +vim:ft=help:textwidth=80:tabstop=4:noexpandtab: diff --git a/doc/markview.nvim-integrations.txt b/doc/markview.nvim-integrations.txt index 0dfd29db..399d5ed0 100644 --- a/doc/markview.nvim-integrations.txt +++ b/doc/markview.nvim-integrations.txt @@ -130,6 +130,51 @@ For this you would need a `comment` parser. You can use either, β—† OXY2DEV/tree-sitter-comment {10}, which also supports a subset of `markdown` & `vimdoc`. See installation {9}. +-------------------------------------------------------------------------------- +πŸ“¦ Asciidoc *markview.nvim-integrations.nvim-asciidoc* + +`markview.nvim` provides support for `asciidoc` via a third party tree-sitter +parser. + +To get started, add this to your configuration table for `nvim-treesitter`. + +β–‹ σ°‹½ Note +β–‹ This is only for the `main` branch of `nvim-treesitter`. `master` branch isn't +β–‹ supported. + +>lua + vim.api.nvim_create_autocmd("User", { + pattern = "TSUpdate", + callback = function () + require("nvim-treesitter.parsers").asciidoc = { + install_info = { + branch = "master", + location = "tree-sitter-asciidoc", + queries = "queries/asciidoc/", + requires = { "asciidoc_inline" }, + url = "https://github.com/cathaysia/tree-sitter-asciidoc" + } + }; + require("nvim-treesitter.parsers").asciidoc_inline = { + install_info = { + branch = "master", + location = "tree-sitter-asciidoc_inline", + queries = "queries/asciidoc_inline", + url = "https://github.com/cathaysia/tree-sitter-asciidoc" + } + }; + end + }); +< + +Exit and open `Neovim` and run, + +>vim + :TSInstall asciidoc asciidoc_inline +< + +Markview should now preview `asciidoc` files. + Links ~ 1: https://github.com/OXY2DEV/markview.nvim/wiki/Integrations diff --git a/doc/tags b/doc/tags index 959bcade..5e1ce036 100644 --- a/doc/tags +++ b/doc/tags @@ -1,6 +1,8 @@ !_TAG_FILE_ENCODING utf-8 // markview.nvim markview.nvim.txt /*markview.nvim* markview.nvim-advanced markview.nvim-advanced.txt /*markview.nvim-advanced* +markview.nvim-asciidoc markview.nvim-asciidoc.txt /*markview.nvim-asciidoc* +markview.nvim-asciidoc_inline markview.nvim-asciidoc_inline.txt /*markview.nvim-asciidoc_inline* markview.nvim-autocmds markview.nvim-autocmds.txt /*markview.nvim-autocmds* markview.nvim-commands markview.nvim.txt /*markview.nvim-commands* markview.nvim-comment markview.nvim-comment.txt /*markview.nvim-comment* @@ -42,6 +44,7 @@ markview.nvim-integrations.blink-cmp markview.nvim-integrations.txt /*markview.n markview.nvim-integrations.color_blending markview.nvim-integrations.txt /*markview.nvim-integrations.color_blending* markview.nvim-integrations.gx markview.nvim-integrations.txt /*markview.nvim-integrations.gx* markview.nvim-integrations.nowrap markview.nvim-integrations.txt /*markview.nvim-integrations.nowrap* +markview.nvim-integrations.nvim-asciidoc markview.nvim-integrations.txt /*markview.nvim-integrations.nvim-asciidoc* markview.nvim-integrations.nvim-fancy_comments markview.nvim-integrations.txt /*markview.nvim-integrations.nvim-fancy_comments* markview.nvim-integrations.transparent_colorschemes markview.nvim-integrations.txt /*markview.nvim-integrations.transparent_colorschemes* markview.nvim-integrations.wrap markview.nvim-integrations.txt /*markview.nvim-integrations.wrap* diff --git a/lua/markview/config/asciidoc.lua b/lua/markview/config/asciidoc.lua new file mode 100644 index 00000000..8fafbb62 --- /dev/null +++ b/lua/markview/config/asciidoc.lua @@ -0,0 +1,389 @@ +---@type [ string, markview.config.asciidoc.keycodes ] +local keycodes = { + ---|fS + + default = { + padding_left = " ", + padding_right = " ", + + icon = "󰌌 ", + hl = "MarkviewPalette6", + }, + + ctrl = { + icon = "ξ­± ", + hl = "MarkviewPalette3", + }, + + shift = { + icon = "󰘢 ", + hl = "MarkviewPalette4", + }, + + meta = { + icon = "󰘡 ", + hl = "MarkviewPalette2", + }, + + super = { + icon = "󰌽 ", + hl = "MarkviewPalette5", + }, + + command = { + icon = "󰘳 ", + hl = "MarkviewPalette5", + }, + + caps_lock = { + icon = "󰘲 ", + hl = "MarkviewPalette1", + }, + + space = { + icon = "󱁐 ", + hl = "MarkviewPalette5", + }, + + enter = { + icon = "σ°Œ‘ ", + hl = "MarkviewPalette2", + }, + + tab = { + icon = "σ°Œ’ ", + hl = "MarkviewPalette3", + }, + + ---|fE +}; + + +---@type markview.config.asciidoc +return { + enable = true, + + admonitions = { + enable = true, + + default = { + padding_left = " ", + padding_right = " ", + + icon = "ξ©΄ ", + + hl = "MarkviewPalette5", + }, + + important = { + padding_left = " ", + padding_right = " ", + + icon = "ξ©΄ ", + + hl = "MarkviewPalette1", + }, + + tip = { + padding_left = " ", + padding_right = " ", + + icon = " ", + + hl = "MarkviewPalette4", + }, + + caution = { + padding_left = " ", + padding_right = " ", + + icon = " ", + + hl = "MarkviewPalette7", + }, + + warn = { + padding_left = " ", + padding_right = " ", + + icon = " ", + + hl = "MarkviewPalette3", + }, + }, + + checkboxes = { + enable = true, + + checked = { text = "σ°— ", hl = "MarkviewCheckboxChecked", scope_hl = "MarkviewCheckboxChecked" }, + unchecked = { text = "σ°„°", hl = "MarkviewCheckboxUnchecked", scope_hl = "MarkviewCheckboxUnchecked" }, + + ["/"] = { text = "σ±Ž–", hl = "MarkviewCheckboxPending" }, + [">"] = { text = "ο‡˜", hl = "MarkviewCheckboxCancelled" }, + ["<"] = { text = "σ°ƒ–", hl = "MarkviewCheckboxCancelled" }, + ["-"] = { text = "󰍢", hl = "MarkviewCheckboxCancelled", scope_hl = "MarkviewCheckboxStriked" }, + + ["?"] = { text = "σ°‹—", hl = "MarkviewCheckboxPending" }, + ["!"] = { text = "󰀦", hl = "MarkviewCheckboxUnchecked" }, + ['"'] = { text = "σ°Έ₯", hl = "MarkviewCheckboxCancelled" }, + ["l"] = { text = "󰆋", hl = "MarkviewCheckboxProgress" }, + ["b"] = { text = "󰃀", hl = "MarkviewCheckboxProgress" }, + ["i"] = { text = "σ°°„", hl = "MarkviewCheckboxChecked" }, + ["S"] = { text = "", hl = "MarkviewCheckboxChecked" }, + ["I"] = { text = "󰛨", hl = "MarkviewCheckboxPending" }, + ["p"] = { text = "ο…€", hl = "MarkviewCheckboxChecked" }, + ["c"] = { text = "ο…₯", hl = "MarkviewCheckboxUnchecked" }, + ["f"] = { text = "σ± ‡", hl = "MarkviewCheckboxUnchecked" }, + ["k"] = { text = "ο‚„", hl = "MarkviewCheckboxPending" }, + ["w"] = { text = "", hl = "MarkviewCheckboxProgress" }, + ["u"] = { text = "σ°”΅", hl = "MarkviewCheckboxChecked" }, + ["d"] = { text = "σ°”³", hl = "MarkviewCheckboxUnchecked" }, + }, + + horizontal_rules = { + enable = true, + + parts = { + { + type = "repeating", + direction = "left", + + repeat_amount = function (buffer) + local utils = require("markview.utils"); + local window = utils.buf_getwin(buffer) + + local width = vim.api.nvim_win_get_width(window) + local textoff = vim.fn.getwininfo(window)[1].textoff; + + return math.floor((width - textoff - 3) / 2); + end, + + text = "─", + + hl = { + "MarkviewGradient1", "MarkviewGradient1", + "MarkviewGradient2", "MarkviewGradient2", + "MarkviewGradient3", "MarkviewGradient3", + "MarkviewGradient4", "MarkviewGradient4", + "MarkviewGradient5", "MarkviewGradient5", + "MarkviewGradient6", "MarkviewGradient6", + "MarkviewGradient7", "MarkviewGradient7", + "MarkviewGradient8", "MarkviewGradient8", + "MarkviewGradient9", "MarkviewGradient9" + } + }, + { + type = "text", + + text = " ξͺͺ ", + hl = "MarkviewIcon3Fg" + }, + { + type = "repeating", + direction = "right", + + repeat_amount = function (buffer) --[[@as function]] + local utils = require("markview.utils"); + local window = utils.buf_getwin(buffer) + + local width = vim.api.nvim_win_get_width(window) + local textoff = vim.fn.getwininfo(window)[1].textoff; + + return math.ceil((width - textoff - 3) / 2); + end, + + text = "─", + hl = { + "MarkviewGradient1", "MarkviewGradient1", + "MarkviewGradient2", "MarkviewGradient2", + "MarkviewGradient3", "MarkviewGradient3", + "MarkviewGradient4", "MarkviewGradient4", + "MarkviewGradient5", "MarkviewGradient5", + "MarkviewGradient6", "MarkviewGradient6", + "MarkviewGradient7", "MarkviewGradient7", + "MarkviewGradient8", "MarkviewGradient8", + "MarkviewGradient9", "MarkviewGradient9" + } + } + } + }, + + literal_blocks = { + enable = true, + + hl = "MarkviewCode", + + sign = "󱨏 ", + sign_hl = "MarkviewPalette6Sign", + + label = "󱨏 Raw ", + label_direction = "right", + label_hl = "MarkviewIcon6", + + min_width = 60, + pad_amount = 2, + pad_char = " ", + + style = "block", + }, + + document_titles = { + enable = true, + + sign = "σ°›“ ", + + 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, + }, + + images = { + enable = true, + + default = { + icon = "σ°₯Ά ", + hl = "MarkviewImage", + }, + + ["%.svg$"] = { icon = "󰜑 " }, + ["%.png$"] = { icon = "σ°Έ­ " }, + ["%.jpg$"] = { icon = "σ°ˆ₯ " }, + ["%.gif$"] = { icon = "σ°΅Έ " }, + ["%.pdf$"] = { icon = "ξ™½ " } + }, + + keycodes = { + enable = true, + + default = keycodes.default, + + ["^CTRL"] = keycodes.ctrl, + ["^%<[cC]"] = keycodes.ctrl, + + ["^SHIFT"] = keycodes.shift, + ["^%<[sS]"] = keycodes.shift, + + ["^META"] = keycodes.meta, + ["^OPT"] = keycodes.meta, + ["^ALT"] = keycodes.meta, + ["^%<[mM]"] = keycodes.meta, + + ["^SUPER"] = keycodes.super, + ["^%<[dD]"] = keycodes.super, + + ["^CMD"] = keycodes.command, + ["^COMMAND"] = keycodes.command, + + ["CAPS.LOCK"] = keycodes.caps_lock, + ["SPACE"] = keycodes.space, + ["TAB"] = keycodes.tab, + ["ENTER"] = keycodes.enter, + }, + + list_items = { + enable = true, + shift_width = 4, + + marker_dot = { + add_padding = true, + conceal_on_checkboxes = true, + + text = function (_, item) + return string.format("%d.", item.n); + end, + hl = "@markup.list.markdown", + }, + + marker_minus = { + add_padding = true, + conceal_on_checkboxes = true, + + text = "●", + hl = "MarkviewListItemMinus" + }, + + marker_star = { + add_padding = true, + conceal_on_checkboxes = true, + + text = "β—‡", + hl = "MarkviewListItemStar" + }, + }, + + tocs = { + enable = true, + + 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/config/asciidoc_inline.lua b/lua/markview/config/asciidoc_inline.lua new file mode 100644 index 00000000..af85e431 --- /dev/null +++ b/lua/markview/config/asciidoc_inline.lua @@ -0,0 +1,346 @@ +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 { + enable = true, + + bolds = { enable = true }, + + highlights = { + enable = true, + + default = { + padding_left = " ", + padding_right = " ", + + hl = "MarkviewPalette3" + } + }, + + italics = { enable = true }, + + monospaces = { + enable = true, + hl = "MarkviewInlineCode", + + padding_left = " ", + padding_right = " " + }, + + 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/parser.lua b/lua/markview/parser.lua index aa7a82be..3a42be88 100644 --- a/lua/markview/parser.lua +++ b/lua/markview/parser.lua @@ -99,6 +99,8 @@ parser.init = function (buffer, from, to, cache) ---|fS 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"); @@ -126,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.lua b/lua/markview/parsers/asciidoc.lua new file mode 100644 index 00000000..8e0cd967 --- /dev/null +++ b/lua/markview/parsers/asciidoc.lua @@ -0,0 +1,689 @@ +--- HTML parser for `markview.nvim`. +local asciidoc = {}; + +---@type markview.parser.asciidoc.data +asciidoc.data = {}; + +--- Queried contents +---@type markview.parsed.asciidoc[] +asciidoc.content = {}; + +--- Queried contents, but sorted +---@type markview.parsed.asciidoc_sorted +---@diagnostic disable-next-line: missing-fields +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 + +--[[ +Admonitions. + +```asciidoc +NOTE: Some note. +``` +]] +---@param buffer integer +---@param _ TSNode +---@param text string[] +---@param range markview.parsed.asciidoc.admonitions.range +asciidoc.admonition = function (buffer, _, text, range) + ---|fS + + local before = vim.api.nvim_buf_get_text(buffer, range.row_start, 0, range.row_start, range.col_start, {})[1] or ""; + local kind = string.match(before, "[A-Z]+$"); + + range.kind = { + range.row_start, + range.col_start - #(kind or ""), + + range.row_start, + range.col_start + 1, + }; + range.col_start = range.kind[2]; + + asciidoc.insert({ + class = "asciidoc_admonition", + kind = kind, + + text = text, + range = range + }); + + ---|fE +end + +--[[ +Document attributes. + +```asciidoc +:toc-title: Some title +``` +]] +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.range +asciidoc.doc_attr = function (buffer, TSNode, text, range) + ---|fS + + local _name = TSNode:named_child(1) --[[@as TSNode]]; + local name = vim.treesitter.get_node_text(_name, buffer, {}); + + local _value = TSNode:named_child(3); + + -- NOTE: Handle special attributes first. + 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", + + text = text, + range = range + }); + + ---|fE +end + +--[[ +Document title. + +```asciidoc += Some title +``` +]] +---@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.insert({ + class = "asciidoc_document_title", + + text = text, + range = range + }); +end + +--[[ +Horizontal rule or thematic break + +```asciidoc +''' +``` +]] +---@param text string[] +---@param range markview.parsed.range +asciidoc.hr = function (_, _, text, range) + asciidoc.insert({ + class = "asciidoc_hr", + + text = text, + range = range + }); +end + +--[[ +Image. + +NOTE: Images aren't handled by the `asciidoc_inline` parser due to it being a **block macro**. + +```asciidoc +image::markview.jpg[] +``` +]] +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.asciidoc.images.range +asciidoc.image = function (buffer, TSNode, text, range) + ---|fS + + local _destination = TSNode:named_child(1); + + if not _destination then + return; + end + + local destination = vim.treesitter.get_node_text(_destination, buffer, {}); + range.destination = { _destination:range() }; + + --[[ + refactor: Range correction + + Block macros end at the start of the next line. + So, we correct the end column & end row. + ]] + range.row_end = range.row_end - 1; + range.col_end = range.col_start + #(text[#text] or ""); + + asciidoc.insert({ + class = "asciidoc_image", + destination = destination, + + text = text, + range = range + }); + + ---|fE +end + +--[[ +Keycodes. + +NOTE: Keycodes are **block macro**. +NIT: Should a separate function be used for `menus`? + +```asciidoc +kbd::space[] +``` +]] +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.asciidoc.keycodes.range +asciidoc.keycode = function (buffer, TSNode, text, range) + ---|fS + + local _content = TSNode:named_child(1); + + if not _content then + return; + end + + local content = vim.treesitter.get_node_text(_content, buffer, {}); + range.content = { _content:range() }; + + --[[ + refactor: Range correction + + Block macros end at the start of the next line. + So, we correct the end column & end row. + ]] + range.row_end = range.row_end - 1; + range.col_end = range.col_start + #(text[#text] or ""); + + asciidoc.insert({ + class = "asciidoc_keycode", + content = content, + + text = text, + range = range + }); + + ---|fE +end + +--[[ Is the given `list item marker` on the same level as the **current** list item? ]] +---@param buffer integer +---@param now string Current marker. +---@param last TSNode +---@return boolean +local function is_on_same_level(buffer, now, last) + ---|fS + + local _marker = last:child(0); + + if not _marker then + return false; + end + + local marker = vim.treesitter.get_node_text(_marker, buffer, {}); + return marker == now; + + ---|fE +end + +--[[ +List item(`ordered` & `unordered`) + +```asciidoc +* Unordered +* Unordered 2 + +. Ordered +. Ordered 2 +``` +]] +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.asciidoc.list_items.range +asciidoc.list_item = function (buffer, TSNode, text, range) + ---|fS + + ---@type integer List item index + local N = 1; + local prev = TSNode:prev_named_sibling(); + + local marker; + + local checkbox; + + for child in TSNode:iter_children() do + if child:type() == "checked_list_marker" then + local _marker = child:named_child(0):named_child(0) --[[@as TSNode]]; + local _checkbox = child:named_child(1) --[[@as TSNode]]; + + marker = vim.treesitter.get_node_text(_marker, buffer, {}) + _, _, _, range.marker_end = _marker:range(); + + if _checkbox then + if _checkbox:type() == "checked_list_marker_checked" then + checkbox = "*"; + _, range.checkbox_start, _, range.checkbox_end = _checkbox:range(); + + break; + elseif _checkbox:type() == "checked_list_marker_unchecked" then + checkbox = " "; + _, range.checkbox_start, _, range.checkbox_end = _checkbox:range(); + + break; + end + end + elseif vim.list_contains({ "ordered_list_marker", "unordered_list_marker" }, child:type()) then + local _marker = child:named_child(0) --[[@as TSNode]]; + + marker = vim.treesitter.get_node_text(_marker, buffer, {}) + _, _, _, range.marker_end = _marker:range(); + elseif child:type() == "line" then + local _text = vim.treesitter.get_node_text(child, buffer, {}); + checkbox = string.match(_text, "^%[(.)%]"); + + if checkbox then + local _, tmp = child:range(); + + range.checkbox_start = tmp; + range.checkbox_end = tmp + 3; + + break; + end + end + end + + -- NOTE: Check list index after getting the list marker. + while prev do + if prev:type() == "ordered_list_item" then + if is_on_same_level(buffer, marker, prev) then + N = N + 1; + else + break; + end + end + + prev = prev:prev_named_sibling(); + end + + asciidoc.insert({ + class = "asciidoc_list_item", + + checkbox = checkbox, + marker = marker, + n = N, + + text = text, + range = range + }); + + ---|fE +end + +--[[ +Literal block. + +```asciidoc +... +Some text literally +... +``` +]] +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.asciidoc.literal_blocks.range +asciidoc.literal_block = function (buffer, TSNode, text, range) + ---|fS + + local _delimiters = { + TSNode:named_child(0), + TSNode:named_child(2), + }; + + if _delimiters[1] then + range.start_delim = { _delimiters[1]:range(); }; + end + + if _delimiters[1] then + range.end_delim = { _delimiters[2]:range(); }; + end + + local uses_tab = false; + + for _, line in ipairs(text) do + if string.match(line, "\t") then + uses_tab = true; + break; + end + end + + asciidoc.insert({ + class = "asciidoc_literal_block", + delimiters = { + _delimiters[1] and vim.treesitter.get_node_text(_delimiters[1], buffer, {}) or "", + _delimiters[2] and vim.treesitter.get_node_text(_delimiters[2], buffer, {}) or "", + }, + uses_tab = uses_tab, + + text = text, + range = range + }); + + ---|fE +end + +--[[ +Section tiles. + +```asciidoc +... +== Section + +=== Sub-section + +=== Sub-section 2 +... +``` +]] +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.range +asciidoc.section_title = function (buffer, TSNode, text, range) + ---|fS + + local _marker = TSNode:child(0); + + 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 = marker, + + text = text, + range = range + }); + + ---|fE +end + +--[[ +Specified automated TOC position. + +NOTE: This needs to be parsed before parsing any TOC nodes! +]] +---@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 + +--[[ +Automated Table of Content. + +```asciidoc +:toc: +``` +]] +---@param text string[] +---@param range markview.parsed.asciidoc.tocs.range +asciidoc.toc = function (_, _, text, range) + ---|fS + + ---@type markview.parser.asciidoc.data.toc_entry[] Validated TOC entries(matches heading depth). + 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 + }); + + ---|fE +end + +--- Asciidoc 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) + ---|fS + + -- Clear the previous contents + asciidoc.data = {}; + ---@diagnostic disable-next-line: missing-fields + asciidoc.sorted = {}; + asciidoc.content = {}; + + 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 + + (block_macro + ( + (block_macro_name) @toc_pos_name + (#eq? @toc_pos_name "toc") + )) @asciidoc.toc_pos + + (unordered_list_item) @asciidoc.list_item + (ordered_list_item) @asciidoc.list_item + + (checked_list_item) @asciidoc.list_item + + (block_macro + ( + (block_macro_name) @image_keyword + (#eq? @image_keyword "image") + )) @asciidoc.image + + (block_macro + ( + (block_macro_name) @kbd_keyword + (#eq? @kbd_keyword "kbd") + )) @asciidoc.keycode + + (literal_block) @asciidoc.literal_block + + (admonition) @asciidoc.admonition + (breaks) @asciidoc.hr + ]]); + + 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 + + local function iter (queries) + ---|fS + + for capture_id, capture_node, _, _ in queries:iter_captures(TSTree:root(), buffer, from, to) do + local capture_name = 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 + + ---|fE + end + + iter(scanned_queries); + + --[[ + NOTE: We need to parse TOC nodes separately because certain document attributes changes the TOC + + Parsing them together will require manually finding TOC nodes every time a change needs to be applied. + ]] + + 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; + + ---|fE +end + +return asciidoc; diff --git a/lua/markview/parsers/asciidoc_inline.lua b/lua/markview/parsers/asciidoc_inline.lua new file mode 100644 index 00000000..4215446c --- /dev/null +++ b/lua/markview/parsers/asciidoc_inline.lua @@ -0,0 +1,430 @@ +--- HTML parser for `markview.nvim`. +local asciidoc_inline = {}; + +---@type integer[][] Already parsed ranges. Used to prevent re-parsing already parsed regions. +asciidoc_inline.parsed_ranges = {}; + +--- Queried contents +---@type markview.parsed.asciidoc_inline[] +asciidoc_inline.content = {}; + +--- Queried contents, but sorted +---@type markview.parsed.asciidoc_inline_sorted +---@diagnostic disable-next-line: missing-fields +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 + +--[[ +Bold text. + +```asciidoc +*bold* +``` +]] +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.range +asciidoc_inline.bold = function (buffer, TSNode, text, range) + ---|fS + + 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 + }); + + ---|fE +end + +--[[ +Highlighted text. + +```asciidoc +#highlighted# +``` +]] +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.range +asciidoc_inline.highlight = function (buffer, TSNode, text, range) + ---|fS + + 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 + }); + + ---|fE +end + +--[[ +Italic. + +```asciidoc +__italic__ +``` +]] +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.range +asciidoc_inline.italic = function (buffer, TSNode, text, range) + ---|fS + + 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 + }); + + ---|fE +end + +--[[ +Labeled URI. + +```asciidoc +www.example.com[Example] +``` +]] +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.asciidoc_inline.labeled_uris.range +asciidoc_inline.labeled_uri = function (buffer, TSNode, text, range) + ---|fS + + 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 + }); + + ---|fE +end + +--[[ +Inline macro for labeled URI. + +```asciidoc +link:www.example.com[Example] +``` +]] +---@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) + ---|fS + + 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 + }); + + ---|fE +end + +--[[ +Monospace text. + +```asciidoc +`mono` +``` +]] +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.range +asciidoc_inline.monospace = function (buffer, TSNode, text, range) + ---|fS + + 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 + }); + + ---|fE +end + +--[[ +Unlabeled URI. + +```asciidoc +www.example.com +``` +]] +---@param buffer integer +---@param TSNode TSNode +---@param text string[] +---@param range markview.parsed.range +asciidoc_inline.uri = function (buffer, TSNode, text, range) + ---|fS + + local before = vim.api.nvim_buf_get_text(buffer, range.row_start, 0, range.row_start, range.col_start, {})[1]; + + -- NOTE: Do not parse a URI if it's part of an image macro. + if string.match(before, "image::$") then + return; + end + + 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 + }); + + ---|fE +end + +---@param buffer integer +---@param TSTree TSTree +---@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) + ---|fS + + ---@diagnostic disable-next-line: missing-fields + 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 can_scan, scanned_queries = pcall(vim.treesitter.query.parse, "asciidoc_inline", [[ + (emphasis) @asciidoc_inline.bold + (ltalic) @asciidoc_inline.italic + (monospace) @asciidoc_inline.monospace + (highlight) @asciidoc_inline.highlight + + (autolink + (uri)) @asciidoc_inline.uri + + (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 + 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]; + + 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; + + ---|fE +end + +return asciidoc_inline; diff --git a/lua/markview/renderer.lua b/lua/markview/renderer.lua index 3beaf54f..c6634fbd 100644 --- a/lua/markview/renderer.lua +++ b/lua/markview/renderer.lua @@ -15,6 +15,26 @@ renderer.__filter_cache = { renderer.option_maps = { ---|fS + asciidoc = { + admonitions = { "asciidoc_admonitiono" }, + document_attribute = { "asciidoc_document_attribute" }, + document_title = { "asciidoc_document_title" }, + horizontal_rules = { "asciidoc_hr" }, + images = { "asciidoc_image" }, + keycode = { "asciidoc_keycode" }, + list_item = { "asciidoc_list_item" }, + literal_block = { "asciidoc_literal_block" }, + section_title = { "asciidoc_section_title" }, + toc = { "asciidoc_toc" }, + }, + asciidoc_inline = { + bold = { "asciidoc_inline_bold" }, + highlight = { "asciidoc_inline_highlight" }, + italic = { "asciidoc_inline_italic" }, + labeled_uri = { "asciidoc_inline_labeled_uri" }, + monospace = { "asciidoc_inline_monospace" }, + uri = { "asciidoc_inline_uri" }, + }, comment = { autolinks = { "comment_autolink" }, code_blocks = { "comment_code_block" }, @@ -366,6 +386,8 @@ renderer.render = function (buffer, parsed_content) ---|fS 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"), @@ -470,6 +492,8 @@ renderer.clear = function (buffer, from, to, hybrid_mode) ---|fS 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 new file mode 100644 index 00000000..a4d1a176 --- /dev/null +++ b/lua/markview/renderers/asciidoc.lua @@ -0,0 +1,906 @@ +local asciidoc = {}; + +local utils = require("markview.utils"); +local spec = require("markview.spec"); + +asciidoc.ns = vim.api.nvim_create_namespace("markview/asciidoc"); + +---@param buffer integer +---@param item markview.parsed.asciidoc.admonitions +asciidoc.admonition = function (buffer, item) + ---|fS + + ---@type markview.config.asciidoc.admonitions? + local main_config = spec.get({ "asciidoc", "admonitions" }, { fallback = nil }); + + if not main_config then + return; + end + + ---@type markview.config.asciidoc.admonitions.opts? + local config = utils.match(main_config, item.kind, { + case_insensitive = true, + + ignore_keys = { "enable" }, + eval_args = { buffer, item } + }); + + if not config then + return; + end + + local range = item.range; + local row_end = range.kind[3]; + local col_end = range.kind[4]; + + utils.set_extmark(buffer, asciidoc.ns, range.row_start, range.col_start, { + 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.ns, range.kind[1], range.kind[2], { + end_row = row_end, + end_col = col_end, + + hl_group = utils.set_hl(config.hl) + }); + + utils.set_extmark(buffer, asciidoc.ns, row_end, col_end - 1, { + end_col = col_end, + conceal = "", + + virt_text_pos = "inline", + virt_text = { + { config.padding_right or "", utils.set_hl(config.padding_right_hl or config.hl) }, + { config.corner_right or "", utils.set_hl(config.corner_right_hl or config.hl) } + }, + + hl_mode = "combine" + }); + + if config.desc_hl then + utils.set_extmark(buffer, asciidoc.ns, row_end, col_end, { + end_row = range.row_end, + end_col = range.col_end, + + hl_group = utils.set_hl(config.desc_hl) + }); + end + + ---|fE +end + +---@param buffer integer +---@param item markview.parsed.asciidoc.document_titles +asciidoc.document_attribute = function (buffer, item) + ---|fS + + ---@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 = "", + }); + + ---|fE +end + +---@param buffer integer +---@param item markview.parsed.asciidoc.document_titles +asciidoc.document_title = function (buffer, item) + ---|fS + + ---@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 + #string.match(item.text[1] or "", "=+%s*"), + conceal = "", + + sign_text = tostring(config.sign or ""), + sign_hl_group = utils.set_hl(config.sign_hl or config.hl), + + virt_text = { + { config.icon, utils.set_hl(config.icon_hl or config.hl) }, + }, + line_hl_group = utils.set_hl(config.hl), + }); + + ---|fE +end + +--- Renders horizontal rules/line breaks. +---@param buffer integer +---@param item markview.parsed.asciidoc.hrs +asciidoc.hr = function (buffer, item) + ---|fS + + ---@type markview.config.asciidoc.hrs? + local config = spec.get({ "asciidoc", "horizontal_rules" }, { fallback = nil, eval_args = { buffer, item } }); + local range = item.range; + + if not config then + return; + end + + local virt_text = {}; + local function val(opt, index, wrap) + if vim.islist(opt) == false then + return opt; + elseif #opt < index then + if wrap == true then + local mod = index % #opt; + return mod == 0 and opt[#opt] or opt[mod]; + else + return opt[#opt]; + end + elseif index < 0 then + return opt[1]; + end + + return opt[index]; + end + + for _, part in ipairs(config.parts) do + if part.type == "text" then + table.insert(virt_text, { part.text, utils.set_hl(part.hl --[[ @as string ]]) }); + elseif part.type == "repeating" then + local rep = spec.get({ "repeat_amount" }, { source = part, fallback = 1, eval_args = { buffer, item } }); + local hl_rep = spec.get({ "repeat_hl" }, { source = part, fallback = false, eval_args = { buffer, item } }); + local txt_rep = spec.get({ "repeat_text" }, { source = part, fallback = false, eval_args = { buffer, item } }); + + for r = 1, rep, 1 do + if part.direction == "right" then + table.insert(virt_text, { + val(part.text, (rep - r) + 1, txt_rep), + val(part.hl, (rep - r) + 1, hl_rep) + }); + else + table.insert(virt_text, { + val(part.text, r, txt_rep), + val(part.hl, r, hl_rep) + }); + end + end + end + end + + utils.set_extmark(buffer, asciidoc.ns, range.row_start, 0, { + virt_text_pos = "overlay", + virt_text = virt_text, + + hl_mode = "combine" + }); + + ---|fE +end + +---@param buffer integer +---@param item markview.parsed.asciidoc.images +asciidoc.image = function (buffer, item) + ---|fS + + ---@type markview.config.asciidoc.images? + local main_config = spec.get({ "asciidoc", "images" }, { fallback = nil }); + local range = item.range; + + if not main_config then + return; + end + + ---@type markview.config.asciidoc.images.opts? + local config = utils.match( + main_config, + item.destination, + { + eval_args = { buffer, item } + } + ); + + if config == nil then + return; + end + + utils.set_extmark(buffer, asciidoc.ns, range.row_start, range.col_start, { + end_col = range.destination[2], + 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.ns, range.destination[1], range.destination[2], { + end_col = range.destination[4], end_row = range.destination[3], + + virt_text = { + { config.text or "", utils.set_hl(config.text_hl or config.hl) } + }, + + hl_mode = "combine" + }); + else + utils.set_extmark(buffer, asciidoc.ns, range.destination[1], range.destination[2], { + end_col = range.destination[4], end_row = range.destination[3], + + hl_group = utils.set_hl(config.hl), + hl_mode = "combine" + }); + end + + utils.set_extmark(buffer, asciidoc.ns, range.row_end, range.destination[4], { + end_col = range.col_end, + conceal = "", + + virt_text_pos = "inline", + virt_text = { + { config.padding_right or "", utils.set_hl(config.padding_right_hl or config.hl) }, + { config.corner_right or "", utils.set_hl(config.corner_right_hl or config.hl) } + }, + + hl_mode = "combine" + }); + + ---|fE +end + +---@param buffer integer +---@param item markview.parsed.asciidoc.keycodes +asciidoc.keycode = function (buffer, item) + ---|fS + + ---@type markview.config.asciidoc.keycodes? + local main_config = spec.get({ "asciidoc", "keycodes" }, { fallback = nil }); + local range = item.range; + + if not main_config then + return; + end + + ---@type markview.config.asciidoc.keycodes.opts? + local config = utils.match( + main_config, + string.upper(item.content or ""), + { + eval_args = { buffer, item } + } + ); + + if config == nil then + return; + end + + utils.set_extmark(buffer, asciidoc.ns, range.row_start, range.col_start, { + end_col = range.content[2], + 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.ns, range.content[1], range.content[2], { + end_col = range.content[4], end_row = range.content[3], + + hl_group = utils.set_hl(config.hl), + hl_mode = "combine" + }); + + utils.set_extmark(buffer, asciidoc.ns, range.row_end, range.content[4], { + end_col = range.col_end, + conceal = "", + + virt_text_pos = "inline", + virt_text = { + { config.padding_right or "", utils.set_hl(config.padding_right_hl or config.hl) }, + { config.corner_right or "", utils.set_hl(config.corner_right_hl or config.hl) } + }, + + hl_mode = "combine" + }); + + ---|fE +end + +---@param buffer integer +---@param item markview.parsed.asciidoc.list_items +asciidoc.list_item = function (buffer, item) + ---|fS + + ---@type markview.config.asciidoc.list_items? + local main_config = spec.get({ "asciidoc", "list_items" }, { fallback = nil, eval_args = { buffer, item } }); + + if not main_config then + return; + end + + ---@type markview.config.asciidoc.list_items.opts? + local config; + + if string.match(item.marker, "%*") then + config = spec.get({ "marker_star" }, { source = main_config, eval_args = { buffer, item } }); + elseif string.match(item.marker, "%-") then + config = spec.get({ "marker_minus" }, { source = main_config, eval_args = { buffer, item } }); + else + config = spec.get({ "marker_dot" }, { source = main_config, eval_args = { buffer, item } }); + end + + if not config then + return; + end + + ---@cast config markview.config.asciidoc.list_items.opts + + ---@type markview.config.asciidoc.checkboxes.opts? + local checkbox_config; + + if item.checkbox == "*" then + checkbox_config = spec.get({ "asciidoc", "checkboxes", "checked" }, { eval_args = { buffer, item } }); + elseif item.checkbox == " " then + checkbox_config = spec.get({ "asciidoc", "checkboxes", "unchecked" }, { eval_args = { buffer, item } }); + elseif item.checkbox then + local checkboxes = spec.get({ "asciidoc", "checkboxes" }, { eval_args = { buffer, item } }); + local _state = vim.pesc(tostring(item.checkbox)); + + checkbox_config = utils.match(checkboxes, "^" .. _state .. "$", { default = false, ignore_keys = { "checked", "unchecked", "enable" }, eval_args = { buffer, item } }); + end + + local shift_width = main_config.shift_width or 2; + local range = item.range; + + local scope_hl = checkbox_config and checkbox_config.scope_hl or nil; + + for r = range.row_start, range.row_end - 1, 1 do + local line = item.text[(r - range.row_start) + 1]; + + if r == range.row_start then + if checkbox_config and not vim.tbl_isempty(checkbox_config) then + utils.set_extmark(buffer, asciidoc.ns, r, range.col_start, { + end_col = config.conceal_on_checkboxes and range.checkbox_start or range.marker_end, + conceal = "", + + virt_text = { + { config.add_padding and string.rep(" ", #item.marker * shift_width) or "" }, + { not config.conceal_on_checkboxes and config.text or "", utils.set_hl(config.hl) }, + }, + hl_mode = "combine", + }); + + utils.set_extmark(buffer, asciidoc.ns, r, range.checkbox_start, { + end_col = range.checkbox_end, + conceal = "", + + virt_text = { + { checkbox_config.text or "", utils.set_hl(checkbox_config.hl) }, + }, + hl_mode = "combine", + }); + else + utils.set_extmark(buffer, asciidoc.ns, r, range.col_start, { + end_col = range.marker_end, + conceal = "", + + virt_text = { + { config.add_padding and string.rep(" ", #item.marker * shift_width) or "" }, + { config.text or "", utils.set_hl(config.hl) }, + }, + hl_mode = "combine", + }); + end + elseif config.add_padding then + utils.set_extmark(buffer, asciidoc.ns, r, 0, { + virt_text = { + { string.rep(" ", #item.marker * shift_width) }, + }, + hl_mode = "combine", + }); + end + + if scope_hl then + if r == range.row_start then + utils.set_extmark(buffer, asciidoc.ns, r, range.col_start, { + end_col = #item.text[1], + + hl_group = utils.set_hl(scope_hl) + }); + elseif line ~= "" then + local spaces = line:match("^([%>%s]*)"); + + utils.set_extmark(buffer, asciidoc.ns, range.row_start + (r - 1), #spaces, { + end_col = #line, + + hl_group = utils.set_hl(scope_hl) + }); + end + end + end + + ---|fE +end + +---@param buffer integer +---@param item markview.parsed.asciidoc.literal_blocks +asciidoc.literal_block = function (buffer, item) + ---|fS + + ---@type markview.config.asciidoc.literal_blocks? + local config = spec.get({ "asciidoc", "literal_blocks" }, { fallback = nil, eval_args = { buffer, item } }); + + local delims = item.delimiters; + local range = item.range; + + if not config then + return; + end + + local label = { config.label, utils.set_hl(config.label_hl or config.hl) }; + local win = utils.buf_getwin(buffer); + + --[[ *Basic* rendering of `code blocks`. ]] + local function render_simple() + ---|fS + + local conceal_from = range.start_delim[2] + #string.match(item.delimiters[1], "^%s*"); + local conceal_to = #delims[1]; + + if config.label_direction == nil or config.label_direction == "left" then + utils.set_extmark(buffer, asciidoc.ns, range.row_start, conceal_from, { + end_col = conceal_to, + conceal = "", + + sign_text = config.sign, + sign_hl_group = utils.set_hl(config.sign_hl), + + virt_text_pos = "inline", + virt_text = { label }, + + line_hl_group = utils.set_hl(config.hl) + }); + else + utils.set_extmark(buffer, asciidoc.ns, range.row_start, conceal_from, { + end_col = conceal_to, + conceal = "", + + sign_text = config.sign, + sign_hl_group = utils.set_hl(config.sign_hl), + + virt_text_pos = "right_align", + virt_text = { label }, + + line_hl_group = utils.set_hl(config.hl) + }); + end + + --- Background + for l = range.row_start + 1, range.row_end - 1 do + utils.set_extmark(buffer, asciidoc.ns, l, 0, { + end_row = l, + + line_hl_group = utils.set_hl(config.hl) + }); + end + + utils.set_extmark(buffer, asciidoc.ns, range.row_end, (range.col_start + #item.text[#item.text]) - #delims[2], { + end_col = range.col_start + #item.text[#item.text], + conceal = "", + + line_hl_group = utils.set_hl(config.hl) + }); + + ---|fE + end + + --- Renders block style code blocks. + local function render_block () + ---|fS + + ---|fS "chunk: Calculate various widths" + + local pad_amount = config.pad_amount or 0; + local block_width = config.min_width or 60; + + local pad_char = config.pad_char or " "; + + ---@type integer[] Visual width of lines. + local line_widths = {}; + + for l, line in ipairs(item.text) do + if l ~= 1 and l ~= #item.text then + table.insert(line_widths, vim.fn.strdisplaywidth(line)); + + if vim.fn.strdisplaywidth(line) > (block_width - (2 * pad_amount)) then + block_width = vim.fn.strdisplaywidth(line) + (2 * pad_amount); + end + end + end + + local label_width = utils.virt_len({ label }); + + ---|fE + + local delim_conceal_from = range.start_delim[2] + #string.match(item.delimiters[1], "^%s*"); + local conceal_to = #delims[1]; + + ---|fS "chunk: Top border" + + local visible_info = string.sub(item.text[1], (conceal_to + 1) - range.col_start); + local left_padding = visible_info ~= "" and 1 or pad_amount; + + local pad_width = vim.fn.strdisplaywidth( + string.rep(pad_char, left_padding) + ); + + -- Hide the leading `dot`s. + utils.set_extmark(buffer, asciidoc.ns, range.row_start, delim_conceal_from, { + end_col = conceal_to, + conceal = "", + + sign_text = config.sign, + sign_hl_group = utils.set_hl(config.sign_hl), + }); + + if config.label_direction == "right" then + utils.set_extmark(buffer, asciidoc.ns, range.row_start, delim_conceal_from, { + virt_text_pos = "inline", + virt_text = { + { + string.rep(" " or pad_char, left_padding), + utils.set_hl(config.hl) + } + } + }); + else + utils.set_extmark(buffer, asciidoc.ns, range.row_start, range.col_start + #item.text[1], { + virt_text_pos = "inline", + virt_text = { + { + string.rep(" " or pad_char, left_padding), + utils.set_hl(config.hl) + } + } + }); + end + + ---|fS "chunk: Prettify info" + + -- Calculating the amount of spacing to add, + -- 1. Used space = label width(`label_width`) + padding size(`pad_width`). + -- 2. Total block width - Used space + local spacing = block_width - (label_width + pad_width); + + utils.set_extmark(buffer, asciidoc.ns, range.row_start, range.col_start + #item.text[1], { + right_gravity = config.label_direction ~= "right", + + virt_text_pos = "inline", + virt_text = { + { + string.rep(pad_char, spacing), + utils.set_hl(config.hl) + }, + } + }); + + ---|fE + + ---|fS "chunk: Place label" + + local top_border = { + }; + + if config.label_direction == "right" then + top_border.col_start = range.start_delim[2] + #item.text[1]; + else + top_border.col_start = range.start_delim[4]; + end + + utils.set_extmark(buffer, asciidoc.ns, range.row_start, top_border.col_start, { + right_gravity = config.label_direction == "right", + + virt_text_pos = "inline", + virt_text = { label } + }); + + ---|fE + + ---|fE + + --- Line padding + for l, width in ipairs(line_widths) do + local line = item.text[l + 1]; + + if width ~= 0 then + utils.set_extmark(buffer, asciidoc.ns, range.row_start + l, line ~= "" and range.col_start or 0, { + virt_text_pos = "inline", + virt_text = { + { + string.rep(" ", pad_amount), + utils.set_hl(config.hl --[[ @as string ]]) + } + }, + }); + + utils.set_extmark(buffer, asciidoc.ns, range.row_start + l, range.col_start + #line, { + virt_text_pos = "inline", + virt_text = { + { + string.rep(" ", math.max(0, block_width - (( 2 * pad_amount) + width))), + utils.set_hl(config.hl --[[ @as string ]]) + }, + { + string.rep(" ", pad_amount), + utils.set_hl(config.hl --[[ @as string ]]) + } + }, + }); + + --- Background + utils.set_extmark(buffer, asciidoc.ns, range.row_start + l, range.col_start, { + end_col = range.col_start + #line, + + hl_group = utils.set_hl(config.hl --[[ @as string ]]) + }); + else + local buf_line = vim.api.nvim_buf_get_lines(buffer, range.row_start + l, range.row_start + l + 1, false)[1]; + + utils.set_extmark(buffer, asciidoc.ns, range.row_start + l, #buf_line, { + virt_text_pos = "inline", + virt_text = { + { + string.rep(" ", math.max(0, range.col_start - #buf_line)) + }, + { + string.rep(" ", pad_amount), + utils.set_hl(config.hl --[[ @as string ]]) + }, + { + string.rep(" ", math.max(0, block_width - (2 * pad_amount))), + utils.set_hl(config.hl --[[ @as string ]]) + }, + { + string.rep(" ", pad_amount), + utils.set_hl(config.hl --[[ @as string ]]) + }, + }, + }); + end + end + + --- Render bottom + if item.delimiters[2] then + local end_delim_conceal_from = range.end_delim[2] + #string.match(item.delimiters[2], "^%s*"); + + utils.set_extmark(buffer, asciidoc.ns, range.row_end, end_delim_conceal_from, { + end_col = range.col_start + #item.text[#item.text], + conceal = "" + }); + + utils.set_extmark(buffer, asciidoc.ns, range.row_end, end_delim_conceal_from, { + virt_text_pos = "inline", + virt_text = { + { + string.rep(" ", block_width), + utils.set_hl(config.hl) + } + } + }); + else + utils.set_extmark(buffer, asciidoc.ns, range.row_end, range.col_start, { + virt_text_pos = "inline", + virt_text = { + { + string.rep(" ", block_width), + utils.set_hl(config.hl) + } + } + }); + end + + ---|fE + end + + if not win or config.style == "simple" or item.uses_tab or ( vim.o.wrap == true or vim.wo[win].wrap == true ) then + render_simple(); + elseif config.style == "block" then + render_block() + end + + ---|fE +end + +---@param buffer integer +---@param item markview.parsed.asciidoc.section_titles +asciidoc.section_title = function (buffer, item) + ---|fS + + ---@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, utils.set_hl(config.icon_hl or config.hl) }, + }, + line_hl_group = utils.set_hl(config.hl), + }); + + ---|fE +end + +---@param buffer integer +---@param item markview.parsed.asciidoc.tocs +asciidoc.toc = function (buffer, item) + ---|fS + + ---@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 "", utils.set_hl(main_config.icon_hl or main_config.hl) }, + { item.title or "Table of contents", utils.set_hl(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, utils.set_hl(config.hl) --[[@as string]]); + local shift_by = (main_config.shift_width or 1) * ( (entry.depth or 1) - 1 ); + + local line = { + { string.rep(config.shift_char or " ", shift_by), utils.set_hl(config.hl) }, + { config.icon or "", utils.set_hl(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 + + ---|fE +end + +---@param buffer integer +---@param content markview.parsed.asciidoc[] +asciidoc.render = function (buffer, content) + ---|fS + + 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 + + ---|fE +end + +---@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/renderers/asciidoc/tostring.lua b/lua/markview/renderers/asciidoc/tostring.lua new file mode 100644 index 00000000..9bcbf813 --- /dev/null +++ b/lua/markview/renderers/asciidoc/tostring.lua @@ -0,0 +1,246 @@ +--[[ +Basic renderer for `asciidoc`. Used to convert regular strings to preview strings. + +Used for width calculations. +]] +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/renderers/asciidoc_inline.lua b/lua/markview/renderers/asciidoc_inline.lua new file mode 100644 index 00000000..7164e507 --- /dev/null +++ b/lua/markview/renderers/asciidoc_inline.lua @@ -0,0 +1,354 @@ +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.bolds +asciidoc_inline.bold = function (buffer, item) + ---|fS + + ---@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 + #(item.delimiters[1] or ""), + conceal = "", + }); + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_end, range.col_end - #(item.delimiters[2] or ""), { + end_col = range.col_end, + conceal = "", + }); + + ---|fE +end + +---@param buffer integer +---@param item markview.parsed.asciidoc_inline.highlights +asciidoc_inline.highlight = function (buffer, item) + ---|fS + + ---@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) }, + { 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.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" + }); + + ---|fE +end + +---@param buffer integer +---@param item markview.parsed.asciidoc_inline.italics +asciidoc_inline.italic = function (buffer, item) + ---|fS + + ---@type markview.config.asciidoc_inline.italics? + 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 + #(item.delimiters[1] or ""), + conceal = "", + }); + + utils.set_extmark(buffer, asciidoc_inline.ns, range.row_end, range.col_end - #(item.delimiters[2] or ""), { + end_col = range.col_end, + conceal = "", + }); + + ---|fE +end + +---@param buffer integer +---@param item markview.parsed.asciidoc_inline.monospaces +asciidoc_inline.monospace = function (buffer, item) + ---|fS + + ---@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) }, + { 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.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" + }); + + ---|fE +end + +---@param buffer integer +---@param item markview.parsed.asciidoc_inline.labeled_uris +asciidoc_inline.labeled_uri = function (buffer, item) + ---|fS + + ---@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" + }); + + ---|fE +end + +---@param buffer integer +---@param item markview.parsed.asciidoc_inline.uris +asciidoc_inline.uri = function (buffer, item) + ---|fS + + ---@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" + }); + + ---|fE +end + +---@param buffer integer +---@param content markview.parsed.asciidoc_inline[] +asciidoc_inline.render = function (buffer, content) + ---|fS + + 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 + + ---|fE +end + +---@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 d35690f9..91ed55fd 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,8 @@ spec.default = { ---@type string[] Properties that should be sourced *externally*. spec.__external_config = { + "asciidoc", + "asciidoc_inline", "comment", "html", "markdown", diff --git a/lua/markview/types/parsers/asciidoc.lua b/lua/markview/types/parsers/asciidoc.lua new file mode 100644 index 00000000..92499f3c --- /dev/null +++ b/lua/markview/types/parsers/asciidoc.lua @@ -0,0 +1,221 @@ +---@meta + +------------------------------------------------------------------------------ + +---@class markview.parsed.asciidoc.admonitions +--- +---@field class "asciidoc_admonition" +---@field kind string Type of admonition(e.g. `NOTE`). +--- +---@field text string[] +---@field range markview.parsed.asciidoc.admonitions.range + + +---@class markview.parsed.asciidoc.admonitions.range +--- +---@field row_start integer +---@field col_start integer +--- +---@field row_end integer +---@field col_end integer +--- +---@field kind integer[] Range of the admonition kind(output of `{ TSNode:range() }`. + +------------------------------------------------------------------------------ + +---@class markview.parsed.asciidoc.document_attributes +--- +---@field class "asciidoc_document_attribute" +--- +---@field text string[] +---@field range markview.parsed.range + +------------------------------------------------------------------------------ + +---@class markview.parsed.asciidoc.document_titles +--- +---@field class "asciidoc_document_title" +--- +---@field text string[] +---@field range markview.parsed.range + +------------------------------------------------------------------------------ + +---@class markview.parsed.asciidoc.hrs +--- +---@field class "asciidoc_hr" +--- +---@field text string[] +---@field range markview.parsed.range + +------------------------------------------------------------------------------ + +---@class markview.parsed.asciidoc.images +--- +---@field class "asciidoc_image" +---@field destination string Source of the image. +--- +---@field text string[] +---@field range markview.parsed.asciidoc.images.range + + +---@class markview.parsed.asciidoc.images.range +--- +---@field row_start integer +---@field col_start integer +--- +---@field row_end integer +---@field col_end integer +--- +---@field destination integer[] Range of the image destination(output of `{ TSNode:range() }`. + +------------------------------------------------------------------------------ + +---@class markview.parsed.asciidoc.keycodes +--- +---@field class "asciidoc_keycode" +---@field content string Text inside the keycode. +--- +---@field text string[] +---@field range markview.parsed.asciidoc.keycodes.range + + +---@class markview.parsed.asciidoc.keycodes.range +--- +---@field row_start integer +---@field col_start integer +--- +---@field row_end integer +---@field col_end integer +--- +---@field content integer[] Range of the keycode destination(output of `{ TSNode:range() }`. + +------------------------------------------------------------------------------ + +---@class markview.parsed.asciidoc.list_items +--- +---@field class "asciidoc_list_item" +---@field checkbox? string +---@field marker string List item marker. +---@field n integer Item index. +--- +---@field text string[] +---@field range markview.parsed.asciidoc.list_items.range + + +---@class markview.parsed.asciidoc.list_items.range +--- +---@field row_start integer +---@field col_start integer +--- +---@field row_end integer +---@field col_end integer +--- +---@field marker_end integer End column for the list item marker(e.g. `*` or `.`). +--- +---@field checkbox_start? integer Start column for the checkbox(e.g. `[*]` or `[ ]`). +---@field checkbox_end? integer End column for the checkbox(e.g. `[*]` or `[ ]`). + +------------------------------------------------------------------------------ + +---@class markview.parsed.asciidoc.literal_blocks +--- +---@field class "asciidoc_literal_block" +---@field delimiters [ string, string ] Block delimiters. +---@field uses_tab boolean Are there tabs in the text? +--- +---@field text string[] +---@field range markview.parsed.asciidoc.literal_blocks.range + + +---@class markview.parsed.asciidoc.literal_blocks.range +--- +---@field row_start integer +---@field col_start integer +--- +---@field row_end integer +---@field col_end integer +--- +---@field start_delim integer[] Range of the block start delimiter(output of `{ TSNode:range() }`. +---@field end_delim integer[] Range of the block end delimiter(output of `{ TSNode:range() }`. + +------------------------------------------------------------------------------ + +---@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 + +------------------------------------------------------------------------------ + +---@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.admonitions +---| markview.parsed.asciidoc.document_attributes +---| markview.parsed.asciidoc.document_titles +---| markview.parsed.asciidoc.hrs +---| markview.parsed.asciidoc.images +---| markview.parsed.asciidoc.keycodes +---| markview.parsed.asciidoc.list_items +---| markview.parsed.asciidoc.literal_blocks +---| markview.parsed.asciidoc.section_titles +---| markview.parsed.asciidoc.tocs + +------------------------------------------------------------------------------ + +---@class markview.parsed.asciidoc_sorted +--- +---@field admonitions markview.parsed.asciidoc.admonitions[] +---@field document_attributes markview.parsed.asciidoc.document_attributes[] +---@field document_titles markview.parsed.asciidoc.document_titles[] +---@field hrs markview.parsed.asciidoc.hrs[] +---@field images markview.parsed.asciidoc.images[] +---@field keycodes markview.parsed.asciidoc.keycodes[] +---@field list_items markview.parsed.asciidoc.list_items[] +---@field literal_blocks markview.parsed.asciidoc.literal_blocks[] +---@field section_titles markview.parsed.asciidoc.section_titles[] +---@field tocs markview.parsed.asciidoc.tocs[] + +------------------------------------------------------------------------------ + +---@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/parsers/asciidoc_inline.lua b/lua/markview/types/parsers/asciidoc_inline.lua new file mode 100644 index 00000000..a9e7ed28 --- /dev/null +++ b/lua/markview/types/parsers/asciidoc_inline.lua @@ -0,0 +1,96 @@ +---@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.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" +---@field delimiters [ string?, string? ] Delimiters +--- +---@field text string[] +---@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 + +------------------------------------------------------------------------------ + +---@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[] +---@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]` or `https://example.com[foo,bar]`). +---@field label_col_end? integer End column of the **label** of an URI. + +------------------------------------------------------------------------------ + +---@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 +---| markview.parsed.asciidoc_inline.italics +---| markview.parsed.asciidoc_inline.labeled_uris +---| markview.parsed.asciidoc_inline.monospaces +---| markview.parsed.asciidoc_inline.uris + + +---@class markview.parsed.asciidoc_inline_sorted +--- +---@field bolds markview.parsed.asciidoc_inline.bolds +---@field highlights markview.parsed.asciidoc_inline.highlights +---@field italics markview.parsed.asciidoc_inline.italics +---@field labeled_uris markview.parsed.asciidoc_inline.labeled_uris +---@field monospaces markview.parsed.asciidoc_inline.monospaces +---@field uris markview.parsed.asciidoc_inline.uris + diff --git a/lua/markview/types/renderers/asciidoc.lua b/lua/markview/types/renderers/asciidoc.lua new file mode 100644 index 00000000..7e6cf3b9 --- /dev/null +++ b/lua/markview/types/renderers/asciidoc.lua @@ -0,0 +1,282 @@ +---@meta + +------------------------------------------------------------------------------ + +--- Configuration for admonitions. +---@class markview.config.asciidoc.admonitions +--- +---@field enable boolean Enable rendering of admonitions. +--- +---@field default markview.config.asciidoc.admonitions.opts Default configuration for admonitions. +---@field [string] markview.config.asciidoc.admonitions.opts Configuration for `[string]` admonitions. + + +---@class markview.config.asciidoc.admonitions.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. +--- +---@field hl? string Default highlight group(used by `*_hl` options when they are not set). +---@field desc_hl? string Highlight group for the `description`. +--- +---@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. + +------------------------------------------------------------------------------ + +--- Configuration for checkboxes. +---@class markview.config.asciidoc.checkboxes +--- +---@field enable boolean Enable rendering of checkboxes. +--- +---@field checked markview.config.asciidoc.checkboxes.opts Configuration for `[*]`. +---@field unchecked markview.config.asciidoc.checkboxes.opts Configuration for `[ ]`. +--- +---@field [string] markview.config.asciidoc.checkboxes.opts Configuration for `[string]` checkbox. + + +--[[ Options for a specific checkbox. ]] +---@class markview.config.asciidoc.checkboxes.opts +--- +---@field text string Text used to replace `[]` part. +---@field hl? string Highlight group for `text`. +---@field scope_hl? string Highlight group for the list item. + +------------------------------------------------------------------------------ + +--[[ Options for document attributes. ]] +---@class markview.config.asciidoc.document_attributes +--- +---@field enable boolean Enable concealing of document attributes? Requires `conceal_lines` support. + +------------------------------------------------------------------------------ + +---@class markview.config.asciidoc.document_titles +--- +---@field enable boolean Enable rendering of document titles. +--- +---@field sign? string Text to add to the sign column. +---@field sign_hl? string Highlight group for the sign. +--- +---@field icon? string Icon added before the title. +---@field icon_hl? string Highlight group for `icon`. +--- +---@field hl? string Fallback for all `*_hl` options. + +------------------------------------------------------------------------------ + +--- Configuration for horizontal rules. +---@class markview.config.asciidoc.hrs +--- +---@field enable boolean Enable preview of horizontal rules. +---@field parts markview.config.asciidoc.hrs.part[] Parts for the horizontal rules. + + +---@alias markview.config.asciidoc.hrs.part +---| markview.config.asciidoc.hrs.text +---| markview.config.asciidoc.hrs.repeating + + +---@class markview.config.asciidoc.hrs.text +--- +---@field type "text" Part name. +--- +---@field hl? string Highlight group for this part. +---@field text string Text to show. + + +---@class markview.config.asciidoc.hrs.repeating +--- +---@field type "repeating" Part name. +--- +---@field direction "left" | "right" Direction from which the highlight groups are applied from. +--- +---@field repeat_amount integer | fun(buffer: integer, item: markview.parsed.asciidoc.hrs): integer How many times to repeat the text. +---@field repeat_hl? boolean Whether to repeat the highlight groups. +---@field repeat_text? boolean Whether to repeat the text. +--- +---@field text string | string[] Text to repeat. +---@field hl? string | string[] Highlight group for the text. + +------------------------------------------------------------------------------ + +--- Configuration for image links. +---@class markview.config.asciidoc.images +--- +---@field enable boolean Enable rendering of image links +--- +---@field default markview.config.asciidoc.images.opts Default configuration for image links +---@field [string] markview.config.asciidoc.images.opts Configuration image links whose description matches `string`. + + +--[[ Options for a specific image link type. ]] +---@class markview.config.asciidoc.images.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. + +------------------------------------------------------------------------------ + +--- Configuration for image links. +---@class markview.config.asciidoc.literal_blocks +--- +---@field enable boolean Enable rendering of image links +---@field style? "simple" | "block" +--- +---@field pad_amount? integer +---@field pad_char? string +---@field min_width? integer +--- +---@field label? string +---@field label_hl? string +---@field label_direction? "left" | "right" +--- +---@field sign? string +---@field sign_hl? string +--- +---@field hl? string + +------------------------------------------------------------------------------ + +--- Configuration for keycodes. +---@class markview.config.asciidoc.keycodes +--- +---@field enable boolean Enable rendering of keycodes +--- +---@field default markview.config.asciidoc.keycodes.opts Default configuration for keycodes +---@field [string] markview.config.asciidoc.keycodes.opts Configuration of keycodes whose content matches `string`. **NOTE:** Case-insensitive + + +--[[ Options for a specific keycode type. ]] +---@alias markview.config.asciidoc.keycodes.opts markview.config.__inline + +------------------------------------------------------------------------------ + +--- Configuration for list items. +---@class markview.config.asciidoc.list_items +--- +---@field enable boolean +---@field shift_width integer | fun(buffer: integer, item: markview.parsed.markdown.list_items): integer Virtual indentation size for previewed list items. +--- +---@field marker_dot markview.config.asciidoc.list_items.opts Configuration for `.` list items. +---@field marker_minus markview.config.asciidoc.list_items.opts Configuration for `-` list items. +---@field marker_star markview.config.asciidoc.list_items.opts Configuration for `*` list items. +--- +---@field wrap? boolean Enables wrap support. + + +---@class markview.config.asciidoc.list_items.opts +--- +---@field enable? boolean Enable rendering of this list item type? +--- +---@field add_padding boolean When `true`, Add padding before the list item. +---@field conceal_on_checkboxes? boolean Should the list item marker be hidden if the item contains a `checkbox`? +--- +---[[ Text used to replace the list item marker. ]] +---@field text? +---| string +---| fun(buffer: integer, item: markview.parsed.asciidoc.list_items): string Dynamic marker. Used for stuff like adding list index to `ordered list items`. +--- +---@field hl? string Highlight group for the `text`. + +------------------------------------------------------------------------------ + +--- Configuration for section titles. +---@class markview.config.asciidoc.section_titles +--- +---@field enable boolean Enable rendering of section titles. +---@field shift_width integer Amount of spaces to add to the start for each title level. Useful to visualize nesting of sections. +--- +---@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 Icon added before the title. +---@field icon_hl? string Highlight group for `icon`. +--- +---@field sign? string Sign to show in the sign column. +---@field sign_hl? string Highlight group for the `sign`. +--- +---@field hl? string Fallback highlight group for the `*_hl` options. + +------------------------------------------------------------------------------ + +--- Configuration for generated Table of contents section.. +---@class markview.config.asciidoc.tocs +--- +---@field enable boolean Enable rendering of automated TOC. +---@field shift_width integer Amount if `shift_char` to add per item depth level. +--- +---@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 Highlight group for the TOC title. +--- +---@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 + + +--- Options for a specific item depth. +---@class markview.config.asciidoc.tocs.opts +--- +---@field shift_char? string The character used to shift the entry(helps visualize nesting, document structure). +---@field hl? string Highlight group for the text. +--- +---@field icon? string Icon for the TOC title. +---@field icon_hl? string Highlight group for `icon`. + +------------------------------------------------------------------------------ + +---@class markview.config.asciidoc +--- +---@field enable boolean Enable rendering of Asciidoc files? +--- +---@field admonitions markview.config.asciidoc.admonitions +---@field checkboxes markview.config.asciidoc.checkboxes +---@field document_attributes markview.config.asciidoc.document_attributes +---@field document_titles markview.config.asciidoc.document_titles +---@field horizontal_rules markview.config.asciidoc.hrs +---@field list_items markview.config.asciidoc.list_items +---@field section_titles markview.config.asciidoc.section_titles +---@field tocs markview.config.asciidoc.tocs + diff --git a/lua/markview/types/renderers/asciidoc_inline.lua b/lua/markview/types/renderers/asciidoc_inline.lua new file mode 100644 index 00000000..aae40c9a --- /dev/null +++ b/lua/markview/types/renderers/asciidoc_inline.lua @@ -0,0 +1,80 @@ +---@meta + + +---@class markview.config.asciidoc_inline.bolds +--- +---@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 highlight 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 enable boolean Enable rendering of inline asciidoc. +--- +---@field bolds markview.config.asciidoc_inline.bolds +---@field highlights markview.config.asciidoc_inline.highlights +---@field italics markview.config.asciidoc_inline.italics +---@field monospaces markview.config.asciidoc_inline.monospaces +---@field uris markview.config.asciidoc_inline.uris + diff --git a/markview.nvim.wiki b/markview.nvim.wiki index af5a5769..9c8e5467 160000 --- a/markview.nvim.wiki +++ b/markview.nvim.wiki @@ -1 +1 @@ -Subproject commit af5a576914fbf01fdb17d5e05f3c0083b65aa357 +Subproject commit 9c8e5467da865590bac6cea5a089a978202c7c8e diff --git a/test/asciidoc.adoc b/test/asciidoc.adoc new file mode 100644 index 00000000..2fb5358f --- /dev/null +++ b/test/asciidoc.adoc @@ -0,0 +1,92 @@ += Asciidoc examples +:toc: + +== Admonitions + +NOTE: An admonition draws the reader's attention to auxiliary information. + +Here are the other built-in admonition types: + +IMPORTANT: Don't forget the children! + +TIP: Look for the warp zone under the bridge. + +CAUTION: Slippery when wet. + +WARNING: The software you're about to use is untested. + +IMPORTANT: Sign off before stepping away from your computer. + +== Checkboxes + +=== Main + +- [*] Checked +- [ ] Unchecked + +=== Custom states + +- [/] Pending +- [>] Reschedule +- [-] Cancelled + + +== Horizontal rules + +''' + +== Literal blocks + +.... +error: 1954 Forbidden search +absolutely fatal: operation lost in the dodecahedron of doom + +Would you like to try again? y/n +.... + +== Document attributes + +:attribute: value + +== Images + +image::sunset.jpg[] + +image::sunset.jpg[Sunset] + +.A mountain sunset +[#img-sunset,caption="Figure 1: ",link=https://www.flickr.com/photos/javh/5448336655] +image::macros:sunset.jpg[Sunset,200,100] + +image::https://asciidoctor.org/images/octocat.jpg[GitHub mascot] + +== Keycodes + +kbd::space[] +kbd::tab[] + +== List items + +* List item ++ +Hello + +* Bye +** Nested list item +*** Deeper nested list item +* List item +*** Another nested list item +* List item +- Bye + +. Hi +.. Hello +.. Bye +. Bye +.. bye +.. hi + +== TOCs + +toc::[] + diff --git a/test/asciidoc_inline.adoc b/test/asciidoc_inline.adoc new file mode 100644 index 00000000..7c60af33 --- /dev/null +++ b/test/asciidoc_inline.adoc @@ -0,0 +1,30 @@ += Inline asciidoc syntax + +== Bold + +*Bold* + +== Highlight + +#Highlight# + +== Italic + +__Italic__ + +== Monospace + +`Monospace` + +== URI + +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!] +