[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS: [e.g. iOS]\n - Browser [e.g. chrome, safari]\n - Version [e.g. 22]\n\n**Smartphone (please complete the following information):**\n - Device: [e.g. iPhone6]\n - OS: [e.g. iOS8.1]\n - Browser [e.g. stock browser, safari]\n - Version [e.g. 22]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".gitignore",
    "content": "lua/.luarc.json\ntest\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Anybody is welcome to contribute to this project. If you have any idea, how to improve any\nfeatures or you have an idea for a new feature do not hesitate to fork this project\nand create a PR. Any improvement is welocme.\n\nIn case you want to contribute to this project, here is the structure of the project:\n\n**/core**\n - *compatibility.lua*\n    - All the functions that change during the nvim versions.\n\n - *util.lua*\n    - Utility project functions.\n\n**/parser**\n - *init.lua*\n    - Parser module public implementation. This should be used to parse the\n      buffer. It is used internally by the plugin, too.\n\n - *parser.lua*\n    - Internal implementation of the parser. The parser improvement or functionality has to be extended here.\n\n - *references.lua*\n    - Parses the lines and detects the references in the buffer.\n\n**/**\n- *config.lua*\n    - Default configuration of the plugin. This is used to set the default configuration of the plugin.\n\n- *highlight.lua*\n    - Module applying the highlighting detected by the parser.\n\n- *init.lua*\n    - The main module of the plugin. This supplys the public API of the plugin.\n\n- *number.lua*\n    - Module that creates a popup window with the number interpratation in multiple bases.\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Filip Lobpreis\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">\npretty_hover\n</h1>\n\n<p align=\"center\">\n<a href=\"https://github.com/Fildo7525/pretty_hover/stargazers\">\n\t<img\n\t\talt=\"Stargazers\"\n\t\tsrc=\"https://img.shields.io/github/stars/Fildo7525/pretty_hover?style=for-the-badge&logo=starship&color=fae3b0&logoColor=d9e0ee&labelColor=282a36\"\n\t/>\n\t</a>\n\t<a href=\"https://github.com/Fildo7525/pretty_hover/issues\">\n\t<img\n\t\talt=\"Issues\"\n\t\tsrc=\"https://img.shields.io/github/issues/Fildo7525/pretty_hover?style=for-the-badge&logo=gitbook&color=ddb6f2&logoColor=d9e0ee&labelColor=282a36\"\n\t/>\n\t</a>\n\t<a href=\"https://github.com/Fildo7525/pretty_hover/contributors\">\n\t<img\n\t\talt=\"Contributors\"\n\t\tsrc=\"https://img.shields.io/github/contributors/Fildo7525/pretty_hover?style=for-the-badge&logo=opensourceinitiative&color=abe9b3&logoColor=d9e0ee&labelColor=282a36\"\n\t/>\n\t</a>\n</p>\n\n## Table of contents\n\n - [How it looks](#how-it-looks)\n - [Installation and setup](#installation-and-setup)\n - [Configuration](#configuration)\n - [Integration](#integration)\n\t- [Blink.cmp](#blink.cmp)\n - [Default config](#default-configuration)\n - [Limitations](#limitations)\n - [Contributing](#contributing)\n - [Inspiration](#inspiration)\n\nPretty_hover is a lightweight plugin that parses the hover message before opening the popup window.\nThe output can be easily manipulated with. This will result in a more readable hover message.\n\nAn additional feature is `number conversion`. If you are tired of constantly converting some numbers to hex, octal\nor binary you can use this plugin to do it for you.\n\n### How it looks\n\n> _**NOTE**_: The colors of the text depend on the color of your chosen colorscheme.\nThese pictures are taken with colorscheme `catppuccin-mocha`\n\nUsing native vim.lsp.buf.hover()\n<img src=\"https://github.com/user-attachments/assets/5f4bd780-8a24-44c8-8a8e-4803ae9f7ace\">\n\nUsing pretty_hover\n<img src=\"https://github.com/user-attachments/assets/e547359e-0a82-4fac-ba75-388cdd291804\">\n\n## Installation and setup\n\n### via Lazy\n```lua\n{\n\t\"Fildo7525/pretty_hover\",\n\tevent = \"LspAttach\",\n\topts = {}\n},\n```\n\n### via Packer\n```lua\nuse {\n\t\"Fildo7525/pretty_hover\",\n\tconfig = function()\n\t\trequire(\"pretty_hover\").setup({})\n\tend\n}\n```\n\n### Using Pretty Hover\nTo open a hover window, run the following lua snippet (or bind it to a key)\n```lua\nrequire(\"pretty_hover\").hover()\n```\n\nTo close a hover window either move the cursor as with nvim's hover popup or\nrun the following lua snippet (e.g. from a keymap)\n```lua\nrequire(\"pretty_hover\").close()\n```\n**NOTE: When focused on a hover window, you can also press `q` to close the hover window**\n\n### Configuration\n\n| Parameter\t\t| Description\t|\n|----------------- | -------------- |\n| line\t\t\t | If one of the supplied strings is located as the first word in the line the whole line is surrounded by `line.styler`. |\n| listing\t\t  | These words will be substituted with `listing.styler`. |\n| group\t\t\t | Table containing group name and its detectors. If this word is detected at the beginning of a line the next word is surrounded by `group.styler`. The whole group is separated by an line and the first line containing es the group name. |\n| header\t\t   | List of strings. If this word is detected at the beginning of a line the word is substituted by `header.styler` |\n| return statement | This words are substituted with **Return** (in bold) |\n| references\t   | If any word from this list is detected, the next word is surrounded by `references.styler[1]`. If this word is located in `line` section the next word is surrounded by `references.styler[2]` (see [Limitations](#limitations)) |\n| hl\t\t\t   | This is a table of highlighting groups. You can define new groups by specifying at least two parameters. `color` and `detect`. Flag `line` is not mandatory, however by setting this flag you can ensure that the whole line is highlighted. When a detector from the table `detect` is found the detector is made uppercase, omits the beginning tag and gets highlighted. |\n| border\t\t   | Sets the border of the hover window. (none \\| single \\| double \\| rounded \\| solid \\| shadow). |\n| wrap\t\t\t| Flag whether to wrap the text if the window is smaller. Otherwise the floating window is scrollable horizontally |\n| max_width\t\t| Sets the maximum width of the window. If you don't want any limitation set to nil. |\n| max_height\t   | Sets the maximum height of the window. If you don't want any limitation set to nil. |\n| toggle\t   | Flag detecting whether you want to have the hover just as a toggle window or make the popup focusable. |\n| multi_server\t   | Flag detecting whether you want to use the new multi lsp support or not. |\n\n> _**NOTE**_: To really use this plugin you have to create a keymap that calls `require('pretty_hover').hover()` function.\n\nThe plugin supports code blocks. By specifying `@code{cpp}` the text in the popup window is highlighted with its filetype highlighter\nuntil the `@endcode` is hit. When the filetype is not specified in the flag `@code` the filetype from the currently opened file is used.\n\n#### Default configuration\n\n```lua\n{\n\t-- Tables grouping the detected strings and using the markdown highlighters.\n\theader = {\n\t\tdetect = { \"[\\\\@]class\" },\n\t\tstyler = '###',\n\t},\n\tline = {\n\t\tdetect = { \"[\\\\@]brief\" },\n\t\tstyler = '**',\n\t},\n\tlisting = {\n\t\tdetect = { \"[\\\\@]li\" },\n\t\tstyler = \" - \",\n\t},\n\treferences = {\n\t\tdetect = { \"[\\\\@]ref\", \"[\\\\@]c\", \"[\\\\@]name\" },\n\t\tstyler = { \"**\", \"`\" },\n\t},\n\tgroup = {\n\t\tdetect = {\n\t\t\t-- [\"Group name\"] = {\"detectors\"}\n\t\t\t[\"Parameters\"] = { \"[\\\\@]param\", \"[\\\\@]*param*\" },\n\t\t\t[\"Types\"] = { \"[\\\\@]tparam\" },\n\t\t\t[\"See\"] = { \"[\\\\@]see\" },\n\t\t\t[\"Return Value\"] = { \"[\\\\@]retval\" },\n\t\t},\n\t\tstyler = \"`\",\n\t},\n\n\t-- Tables used for cleaner identification of hover segments.\n\tcode = {\n\t\tstart = { \"[\\\\@]code\" },\n\t\tending = { \"[\\\\@]endcode\" },\n\t},\n\treturn_statement = {\n\t\t\"[\\\\@]return\",\n\t\t\"[\\\\@]*return*\",\n\t},\n\n\t-- Highlight groups used in the hover method. Feel free to define your own highlight group.\n\thl = {\n\t\terror = {\n\t\t\tcolor = \"#DC2626\",\n\t\t\tdetect = { \"[\\\\@]error\", \"[\\\\@]bug\" },\n\t\t\tline = false, -- Flag detecting if the whole line should be highlighted\n\t\t},\n\t\twarning = {\n\t\t\tcolor = \"#FBBF24\",\n\t\t\tdetect = { \"[\\\\@]warning\", \"[\\\\@]thread_safety\", \"[\\\\@]throw\" },\n\t\t\tline = false,\n\t\t},\n\t\tinfo = {\n\t\t\tcolor = \"#2563EB\",\n\t\t\tdetect = { \"[\\\\@]remark\", \"[\\\\@]note\", \"[\\\\@]notes\" },\n\t\t},\n\t\t-- Here you can set up your highlight groups.\n\t},\n\n\t-- If you use nvim 0.11.0 or higher you can choose, whether you want to use the new\n\t-- multi lsp support or not. Otherwise this option is ignored.\n\tmulti_server = true,\n\tborder = \"rounded\",\n\twrap = true,\n\tmax_width = nil,\n\tmax_height = nil,\n\ttoggle = false,\n}\n```\n\n### Integration\n\nThe plugin supports an easy integration:\n\n```lua\nlocal parsed = require(\"pretty_hover.parser\").parse(text)\n```\n\nthe parsed variable contains two fields `text` and `highlight`. The `text` field contains the converted text to markdown\nand the `highlight` field contains the highlight groups for the text.\n\nYou can use the `parsed` variable to display the hover message in your own way.\n\n```lua\nvim.lsp.util.open_floating_preview(parsed.text, \"markdown\", {\n\tfocus = true,\n\tfocusable = true,\n\twrap = true,\n\twrap_at = 100,\n\tmax_width = 100,\n\tborder = \"rounded\",\n\tfocus_id = \"pretty-hover-example\",\n})\n```\n\nTo see an example of the implementation see the `pretty_hover/examples/parsing.lua` file.\n\n#### Blink.cmp\n\nThis functionality is supported for blink.cmp from version v0.13.0 and higher.\nTo use this plugin with `blink.cmp` documentation you can add the following code snippet to you configuration:\n\n```lua\n{\n\tcompletion = {\n\t\tdocumentation = {\n\t\t\tdraw = function(opts)\n\t\t\t\tif opts.item and opts.item.documentation and opts.item.documentation.value then\n\t\t\t\t\tlocal out = require(\"pretty_hover.parser\").parse(opts.item.documentation.value)\n\t\t\t\t\topts.item.documentation.value = out:string()\n\t\t\t\tend\n\n\t\t\t\topts.default_implementation(opts)\n\t\t\tend,\n\t\t}\n\t},\n}\n```\n\n### Limitations\n\nCurrently, Neovim supports these markdown stylers: \\`, \\*, \\`\\`\\`[language]. Unfortunately, you cannot do any\nof their combination. If the support is extended there will be more options to style the pop-up window.\nNewly this plugin started supporting highlighting see the [Configuration](#configuration) for more information.\n\n### Contributing\n\nIf you have any idea how to improve this plugin do not hesitate to create a PR. Otherwise, if you know how\nto improve the plugin mention it in a new issue. Enjoy the plugin.\n\n### Inspiration\n\nhttps://github.com/lewis6991/hover.nvim\n"
  },
  {
    "path": "examples/parsing.lua",
    "content": "local text = [[### function `main`\r\n---\r\n\t→ `int`\r\nParameters:\r\n\t- `int argc`\r\n\t- `char ** argv`\r\n\r\n@brief Neque porro quisquam est qui dolorem @c ipsum quia dolor sit amet, consectetur, adipisci velit...\"\r\n\r\nLorem Ipsum is simply dummy text of the printing and typesetting industry.\r\nLorem Ipsum has been the @c industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.\r\nIt has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.\r\nIt was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.\r\n\r\n@note This is a note.\r\n\r\n@param argc Number of arguments from the command line.\r\n@param argv The arguments in format of the strings.\r\n@return int The return of the program. Usually 0 for successful run.\r\n---\r\n```cpp\r\nint main(int argc, char *argv[])\r\n```\r\n]]\r\n\r\nlocal text_table = {\r\n\"\t### function `main`\",\r\n\"---\",\r\n\"\t→ `int`\",\r\n\"Parameters:\",\r\n\"\t- `int argc`\",\r\n\"\t- `char ** argv`\",\r\n\"\",\r\n\"@brief Neque porro quisquam est qui dolorem @c ipsum quia dolor sit amet, consectetur, adipisci velit...\",\r\n\"\",\r\n\"Lorem Ipsum is simply dummy text of the printing and typesetting industry.\",\r\n\"Lorem Ipsum has been the @c industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.\",\r\n\"It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.\",\r\n\"It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.\",\r\n\"\",\r\n\"@note This is a note.\",\r\n\"\",\r\n\"@param argc Number of arguments from the command line.\",\r\n\"@param argv The arguments in format of the strings.\",\r\n\"@return int The return of the program. Usually 0 for successful run.\",\r\n\"---\",\r\n\"```cpp\",\r\n\"int main(int argc, char *argv[])\",\r\n\"```\",\r\n}\r\n\r\nlocal parser = require(\"pretty_hover.parser\")\r\n\r\nlocal out = parser.parse(text)\r\n--[[ local out = parser.parse(text_table) ]]\r\n\r\nlocal bufnr, winnr = vim.lsp.util.open_floating_preview(out.text, \"markdown\", {\r\n\tfocus = true,\r\n\tfocusable = true,\r\n\twrap = true,\r\n\twrap_at = 100,\r\n\tmax_width = 100,\r\n\tborder = \"rounded\",\r\n\tfocus_id = \"pretty-hover-example\",\r\n})\r\n\r\n--[[ require('pretty_hover.highlight').apply_highlight(out.highlighting, bufnr, require(\"pretty_hover\").get_config()) ]]\r\nrequire('pretty_hover.highlight').apply_highlight(out.highlighting, bufnr)\r\n"
  },
  {
    "path": "lua/pretty_hover/config.lua",
    "content": "---@class PrettyHoverConfig\nlocal M = {\n\t_config = {},\n\n\theader = {\n\t\tdetect = {\"[\\\\@]class\"},\n\t\tstyler = '###',\n\t},\n\tline = {\n\t\tdetect = { \"[\\\\@]brief\" },\n\t\tstyler = '**',\n\t},\n\tlisting = {\n\t\tdetect = { \"[\\\\@]li\" },\n\t\tstyler = \" - \",\n\t},\n\treferences = {\n\t\tdetect = {\n\t\t\t\"[\\\\@]ref\",\n\t\t\t\"[\\\\@]c\",\n\t\t\t\"[\\\\@]name\",\n\t\t\t\"[\\\\@]a\",\n\t\t},\n\t\tstyler = { \"**\", \"`\" },\n\t},\n\tgroup = {\n\t\tdetect = {\n\t\t\t[\"Parameters\"] = { \"[\\\\@]param\", \"[\\\\@]*param*\" },\n\t\t\t[\"Types\"] = { \"[\\\\@]tparam\" },\n\t\t\t[\"See\"] = { \"[\\\\@]see\" },\n\t\t\t[\"Return Value\"] = { \"[\\\\@]retval\" },\n\t\t},\n\t\tstyler = \"`\",\n\t},\n\n\tcode = {\n\t\tstart = {\"[\\\\@]code\"},\n\t\tending = {\"[\\\\@]endcode\"},\n\t},\n\treturn_statement = {\n\t\t\"[\\\\@]return\",\n\t\t\"[\\\\@]*return*\",\n\t},\n\n\thl = {\n\t\terror = {\n\t\t\tcolor = \"#DC2626\",\n\t\t\tdetect = {\"[\\\\@]error\", \"[\\\\@]bug\"},\n\t\t\tline = false,\n\t\t},\n\t\twarning = {\n\t\t\tcolor = \"#FBBF24\",\n\t\t\tdetect = {\"[\\\\@]warning\", \"[\\\\@]thread_safety\", \"[\\\\@]throw\"},\n\t\t\tline = false,\n\t\t},\n\t\tinfo = {\n\t\t\tcolor = \"#4FC1FF\",\n\t\t\tdetect = {\"[\\\\@]remark\", \"[\\\\@]note\", \"[\\\\@]notes\"},\n\t\t}\n\t},\n\n\tmulti_server = true,\n\tborder = \"rounded\",\n\twrap = true,\n\tmax_width = nil,\n\tmax_height = nil,\n\ttoggle = false,\n}\n\n---@class PrettyHoverConfig\n---@brief This class is used to configure the pretty_hover plugin.\n---@param config table Table of options to be used for the pretty_hover configuration. If none or empty is provided, the\n---previous configuration will be used. If the previous configuration is also empty, the default configuration will be used.\n---@return table config The configuration table that will be used for the pretty_hover plugin.\nfunction M:instance(config)\n\tconfig = config or {}\n\n\tif vim.tbl_isempty(config) and not vim.tbl_isempty(self._config) then\n\t\treturn self._config\n\tend\n\n\tself._config = vim.tbl_deep_extend('force', {}, self, config)\n\trequire(\"pretty_hover.highlight\").setup_colors(self._config)\n\treturn self._config\nend\n\n-- return M\nreturn M\n"
  },
  {
    "path": "lua/pretty_hover/core/compatibility.lua",
    "content": "local M = {}\n\n--- Function that encapsulates the changes in nvim api for getting the active clients.\n---\n--- @return table List of active clients.\nfunction M.get_clients()\n\tif vim.version().minor >= 11 then\n\t\treturn vim.lsp.get_clients()\n\telse\n\t\treturn vim.lsp.get_active_clients()\n\tend\nend\n\nfunction M.nvim_hl(name, fg)\n\tif vim.version().minor >= 11 then\n\t\tlocal ns = vim.api.nvim_get_namespaces()[\"pretty_hover_ns\"]\n\t\treturn vim.api.nvim_get_hl(ns, {name = name})\n\tend\n\n\treturn vim.api.nvim_get_hl_by_name(name, fg)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/pretty_hover/core/util.lua",
    "content": "local api = vim.api\nlocal hl = require(\"pretty_hover.highlight\")\nlocal hover_ns = api.nvim_create_namespace('pretty_hover_range')\nlocal compatibility = require(\"pretty_hover.core.compatibility\")\n\nlocal M = {}\n\nlocal winnr = 0\nlocal bufnr = 0\n\nfunction string:split(delimiter)\n\tlocal result = { }\n\tlocal from = 1\n\tlocal delim_from, delim_to = string.find( self, delimiter, from )\n\twhile delim_from do\n\t\ttable.insert( result, string.sub( self, from , delim_from-1 ) )\n\t\tfrom = delim_to + 1\n\t\tdelim_from, delim_to = string.find( self, delimiter, from )\n\tend\n\ttable.insert( result, string.sub( self, from ) )\n\treturn result\nend\n\n--- Check if a table contains desired element. vim.tbl_contains does not work for all cases.\n---@param tbl table Table to be checked.\n---@param el string Element to be checked.\n---@return boolean True if the table contains the element, false otherwise.\nfunction M.tbl_contains(tbl, el)\n\tif not el then\n\t\treturn false\n\tend\n\tif not tbl then\n\t\treturn false\n\tend\n\n\tfor _, v in pairs(tbl) do\n\t\tif el:find(v) then\n\t\t\treturn true\n\t\tend\n\tend\n\treturn false\nend\n\n--- Checks the table for the desired element. If the element is found, it is returned, otherwise nil is returned.\n---@param tbl table Table to be checked.\n---@param el string Element to be checked for.\n---@return string The element if it is found, nil otherwise.\nfunction M.find(tbl, el)\n\tif not el or not tbl then\n\t\treturn \"\"\n\tend\n\n\tfor _, v in pairs(tbl) do\n\t\tif el:find(v) then\n\t\t\treturn el\n\t\tend\n\tend\n\treturn \"\"\nend\n\n--- Count the printable strings in the table.\n---@param tbl table Table of string from hover.\n---@return number Number of printable lines.\nfunction M.printable_table_size(tbl)\n\tlocal count = 0\n\n\tfor _, el in pairs(tbl) do\n\t\tif el then\n\t\t\tcount = count + 1\n\t\tend\n\tend\n\n\treturn count\nend\n\n--- Splits a string into a table of strings.\n---@param toSplit string String to be split.\n---@param separator string|nil The separator. If not defined, the separator is set to \"%S+\".\n---@return table Table of strings split by the separator.\nfunction M.split(toSplit, separator)\n\tlocal indentation = nil\n\n\tif separator == nil then\n\t\tindentation = string.match(toSplit, \"^%s+\")\n\t\tseparator = \"%S+\"\n\tend\n\n\tif toSplit == nil then\n\t\treturn {}\n\tend\n\n\tlocal chunks = {}\n\tif indentation ~= nil and indentation:len() > 0 then\n\t\ttable.insert(chunks, indentation)\n\tend\n\n\tfor substring in toSplit:gmatch(separator) do\n\t\t-- These both cases are here because of python server. Some servers have '.... ' in front of every line and some\n\t\t-- servers surround the whole message with '```text' and '```'. This is a workaround for that.\n\t\tif substring:sub(1, 2) == \". \" then\n\t\t\tsubstring = substring:sub(5)\n\t\tend\n\n\t\ttable.insert(chunks, substring)\n\tend\n\n\treturn chunks\nend\n\n--- Join the elements of a table into a string with a delimiter.\n---@param tbl table Table to be joined.\n---@param delim string Delimiter to be used.\n---@return string Joined string.\nfunction M.join_table(tbl, delim)\n\tlocal result = \"\"\n\tfor idx, chunk in pairs(tbl) do\n\t\tresult = result .. chunk\n\t\tif idx ~= #tbl then\n\t\t\tresult = result .. delim\n\t\tend\n\tend\n\treturn result\nend\n\n--- This function checks all the active clients for current buffer and returns the active client that supports the current file type.\n---@return table|nil Active client for the current buffer or nil if there is no active client.\nfunction M.get_current_active_client()\n\tfor _, client in ipairs(compatibility.get_clients()) do\n\t\tif M.tbl_contains(client.config.filetypes, vim.bo.filetype) then\n\t\t\treturn client\n\t\tend\n\tend\n\treturn nil\nend\n\n--- Close the opened floating window.\nfunction M.close_float()\n\t-- Safeguard around accidentally calling close when there is no pretty_hover window open\n\tif winnr == 0 and bufnr == 0 then\n\t\treturn\n\tend\n\n\tapi.nvim_buf_clear_namespace(vim.fn.bufnr(), hover_ns, 0, -1)\n\n\t-- Before closing the window, check if it is still valid.\n\tif not api.nvim_win_is_valid(winnr) then\n\t\twinnr = 0\n\t\tbufnr = 0\n\t\treturn\n\tend\n\n\tapi.nvim_win_close(winnr, true)\n\twinnr = 0\n\tbufnr = 0\nend\n\n--- The file is a link in markdown style and is represented as [\\w+](<uri>#L<row>,<col>).\n--- This function opens the file in a new buffer and jumps to the given line and column.\nfunction M.open_file_under_cursor()\n\tlocal line = api.nvim_get_current_line()\n\tlocal target = line:match(\"%[(.-)%]%((.-)#L(%d+),?(%d*)%)\")\n\n\tif not target then\n\t\tvim.notify(\"1. No valid file link under cursor\", vim.log.levels.WARN)\n\t\treturn\n\tend\n\n\tlocal _, uri, row, col = line:match(\"%[(.-)%]%((.-)#L(%d+),?(%d*)%)\")\n\tif not uri or not row then\n\t\tvim.notify(\"2. No valid file link under cursor\", vim.log.levels.WARN)\n\t\treturn\n\tend\n\n\trow = tonumber(row)\n\tcol = tonumber(col) or 0\n\n\tM.close_float()\n\n\t-- Open the file in a new buffer\n\tvim.cmd(\"edit \" .. uri)\n\n\t-- Jump to the specified line and column\n\tapi.nvim_win_set_cursor(0, {row, col})\nend\n\n--- Opens a floating window with the documentation transformed from doxygen to markdown.\n---@param hover_text string[] Text to be converted.\n---@param format string Filetype to be used for the conversion.\n---@param config table Table of options to be used for the conversion to the markdown language.\nfunction M.open_float(hover_text, format, config)\n\tif not hover_text or #hover_text == 0 then\n\t\t-- There is nothing to display, quit out early\n\t\tlocal tabled_numbers = require(\"pretty_hover.number\").get_number_representations()\n\t\tif not tabled_numbers then\n\t\t\tvim.notify(\"No information available\", vim.log.levels.INFO)\n\t\t\treturn\n\t\tend\n\n\t\tM.open_float(tabled_numbers:split(\"\\n\"), format, config)\n\t\treturn\n\tend\n\n\t-- Convert Doxygen comments to Markdown format\n\tlocal out = require(\"pretty_hover.parser\").parse(hover_text)\n\tif #out.text == 0 then\n\t\tvim.notify(\"No information available\", vim.log.levels.INFO)\n\t\treturn\n\tend\n\n\tif config.toggle and winnr ~= 0 then\n\t\tM.close_float()\n\t\treturn\n\tend\n\n\tlocal language = format\n\tif config.one_liner then\n\t\tlanguage = vim.bo.filetype\n\tend\n\n\tbufnr, winnr = vim.lsp.util.open_floating_preview(out.text, language, {\n\t\tborder = config.border,\n\t\tfocusable = true,\n\t\tfocus = true,\n\t\tfocus_id = \"pretty-hover\",\n\t\twrap = config.wrap,\n\t\twrap_at = config.max_width and config.max_width - 2 or nil,\n\t\tmax_width = config.max_width,\n\t\tmax_height = config.max_height,\n\t})\n\n\tvim.wo[winnr].foldenable = false\n\tvim.bo[bufnr].modifiable = false\n\tvim.bo[bufnr].bufhidden = 'wipe'\n\n\thl.apply_highlight(out.highlighting, bufnr, config)\n\n\tvim.keymap.set('n', 'gf', M.open_file_under_cursor, {\n\t\tbuffer = bufnr,\n\t\tsilent = true,\n\t\tnowait = true,\n\t})\n\n\tvim.keymap.set('n', 'q', M.close_float, {\n\t\tbuffer = bufnr,\n\t\tsilent = true,\n\t\tnowait = true,\n\t})\n\n    return bufnr, winnr\nend\n\nreturn M\n\n"
  },
  {
    "path": "lua/pretty_hover/highlight.lua",
    "content": "local M = {}\n\nlocal compatibility = require \"pretty_hover.core.compatibility\"\n\nlocal api = vim.api\n\n--- Convert HEX color representation to RGB\n---@param hex string HEX color representation\n---@return number|nil, number|nil, number|nil # RGB color representation\nfunction M.hex2rgb(hex)\n\thex = hex:gsub(\"#\", \"\")\n\treturn tonumber(\"0x\" .. hex:sub(1, 2)), tonumber(\"0x\" .. hex:sub(3, 4)), tonumber(\"0x\" .. hex:sub(5, 6))\nend\n\n--- Check if HEX color is dark\n---@param hex string HEX color representation\n---@return boolean True if color is dark, false otherwise\nfunction M.is_dark(hex)\n\tlocal r, g, b = M.hex2rgb(hex)\n\tlocal lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255\n\treturn lum <= 0.5\nend\n\n--- Get the highlight group\n---@param name string Highlight group name\n---@return table|nil Highlight group\nfunction M.get_hl(name)\n\tlocal hl = compatibility.nvim_hl(name, true)\n\n\tfor _, key in pairs({ \"foreground\", \"background\", \"special\" }) do\n\t\tif hl[key] then\n\t\t\thl[key] = string.format(\"#%06x\", hl[key])\n\t\tend\n\tend\n\n\treturn hl\nend\n\n--- Setup color groups for pretty_hover plugin.\n---@param config table Options from the config.\nfunction M.setup_colors(config)\n\tM.hl_ns = api.nvim_create_namespace(\"pretty_hover_ns\")\n\tlocal normal = M.get_hl(\"Normal\")\n\n\tif not normal then\n\t\tvim.notify(\"No normal highlight group found\", vim.log.levels.WARN)\n\t\treturn\n\tend\n\n\tfor kw, hl_groups in pairs(config.hl) do\n\t\tlocal kw_color = hl_groups.color or \"default\"\n\t\tlocal hex\n\n\t\tif kw_color:sub(1, 1) == \"#\" then\n\t\t\thex = kw_color\n\t\telse\n\t\t\tlocal colors = M.options.colors[kw_color]\n\t\t\tcolors = type(colors) == \"string\" and { colors } or colors\n\n\t\t\tfor _, color in pairs(colors) do\n\t\t\t\tif color:sub(1, 1) == \"#\" then\n\t\t\t\t\thex = color\n\t\t\t\t\tbreak\n\t\t\t\tend\n\t\t\t\tlocal c = M.get_hl(color)\n\t\t\t\tif c and c.foreground then\n\t\t\t\t\thex = c.foreground\n\t\t\t\t\tbreak\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\t\tif not hex then\n\t\t\terror(\"Todo: no color for \" .. kw)\n\t\tend\n\n\t\tvim.cmd(\"hi def PH\" .. kw .. \" guibg=NONE  guifg=\" .. hex .. \" gui=NONE\")\n\tend\nend\n\n--- Applies the highlight to the lines of the opened floating window.\n--- The used groups are ErrorMsg and WarningMsg. For the proper highlighting, the\n--- highlight groups must be defined.\n---@param hl_data table Table of control variables that were set during the conversion to markdown.\n---@param bufnr number Buffer number of the pop-up window.\n---@param config table|nil Table of configurations.\n---@overload fun(hl_data: table, bufnr: number)\n---@overload fun(hl_data: table, bufnr: number, config: table)\nfunction M.apply_highlight(hl_data, bufnr, config)\n\tif not config then\n\t\tconfig = require(\"pretty_hover\").get_config()\n\tend\n\n\tif M.hl_ns then\n\t\tapi.nvim_buf_clear_namespace(bufnr, M.hl_ns, 0, -1)\n\tend\n\n\tM.hl_ns = api.nvim_create_namespace(\"pretty_hover_ns\")\n\n\tfor name, _ in pairs(config.hl) do\n\t\tif hl_data.lines[tostring(name)] then\n\t\t\tfor _, line in pairs(hl_data.lines[tostring(name)]) do\n\t\t\t\tif type(line) == \"table\" then\n\t\t\t\t\tapi.nvim_buf_add_highlight(bufnr, M.hl_ns, \"PH\"..tostring(name), line.line_nr, 0, line.to);\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\tend\nend\n\n\nreturn M\n"
  },
  {
    "path": "lua/pretty_hover/init.lua",
    "content": "local api = vim.api\nlocal cfg = require(\"pretty_hover.config\")\nlocal h_util = require(\"pretty_hover.core.util\")\nlocal local_hover_request = require(\"pretty_hover.local_request\").local_hover_request\n\nlocal M = {}\n\n--- Parses the response from the server and displays the hover information converted to markdown.\nfunction M.hover(config)\n\tlocal params = vim.lsp.util.make_position_params(0, 'utf-16')\n\n\t-- Check if the server for this file type exists and supports hover.\n\tlocal client = h_util.get_current_active_client()\n\tlocal hover_support_present = client and client.capabilities.textDocument.hover\n\n\tif not client or not hover_support_present then\n\t\tvim.notify(\"There is no client for this filetype or the client does not support the hover capability.\", vim.log.levels.WARN)\n\t\treturn\n\tend\n\n\tconfig = config or {}\n\tcfg:instance().hover_cnf = config\n\n\tvim.lsp.buf_request_all(0, \"textDocument/hover\", params, local_hover_request)\nend\n\n--- Setup the plugin to use the given options.\n---@param config table Options to be set for the plugin.\nfunction M.setup(config)\n\tconfig = cfg:instance(config)\n\n\tif config.toggle then\n\t\tlocal id = api.nvim_create_augroup(\"pretty_hover_augroup\", {\n\t\t\tclear = true,\n\t\t})\n\t\tapi.nvim_create_autocmd({ \"CursorMoved\" }, {\n\t\t\tcallback = function()\n\t\t\t\trequire(\"pretty_hover.core.util\").close_float()\n\t\t\tend,\n\t\t\tgroup = id,\n\t\t})\n\tend\nend\n\n--- Close the opened floating window.\nfunction M.close()\n\th_util.close_float()\nend\n\nfunction M.get_config()\n\treturn cfg:instance()\nend\n\nreturn M\n"
  },
  {
    "path": "lua/pretty_hover/local_request.lua",
    "content": "local api = vim.api\nlocal lsp = vim.lsp\nlocal util = vim.lsp.util\nlocal hover_ns = api.nvim_create_namespace('pretty_hover_range')\nlocal cfg = require(\"pretty_hover.config\")\n\nlocal h_util = require(\"pretty_hover.core.util\")\nlocal number = require(\"pretty_hover.number\")\n\nlocal M = {}\n\nlocal function parse_response_contents(contents)\n\tlocal hover_text = contents.value;\n\t-- vtsls workaround, this lsp does not contain value in the contents. It's just pure text.\n\tif type(contents) == \"string\" then\n\t\thover_text = contents\n\tend\n\n\tif hover_text ~= nil then\n\t\treturn hover_text\n\tend\n\n\t-- typescript-tools.nvim workaround\n\t-- Add a test in case there are no contents.\n\tif not pcall(function() hover_text = contents[1].value end) then\n\t\treturn\n\tend\n\thover_text = hover_text or \"\"\n\tfor i = 2, #contents do\n\t\tif type(contents[i]) ~= \"string\" then\n\t\t\tvim.notify(\"Unexpected item type found in hover request's response.\\n\" ..\n\t\t\t\t\"Please report an issue on github: https://github.com/Fildo7525/pretty_hover\",\n\t\t\t\tvim.log.levels.ERROR)\n\t\t\tbreak\n\t\tend\n\t\thover_text = hover_text .. contents[i]\n\tend\n\treturn hover_text\nend\n\nlocal function request_below11(results)\n\tlocal called = false\n\n\tfor _, response in pairs(results) do\n\t\tif response.result and response.result.contents and called == false then\n\t\t\tcalled = true\n\t\t\tlocal contents = response.result.contents\n\n\t\t\t-- We have to do this because of java. Sometimes is the value parameter split\n\t\t\t-- into two chunks. Leaving the rest of the hover message as the second argument\n\t\t\t-- in the received table.\n\t\t\tif contents.language == \"java\" then\n\t\t\t\tfor _, content in pairs(contents) do\n\t\t\t\t\tlocal hover_text = content.value or content\n\t\t\t\t\tif not hover_text then\n\t\t\t\t\t\tvim.notify(\"There is no text to be displayed\", vim.log.levels.INFO)\n\t\t\t\t\t\treturn\n\t\t\t\t\tend\n\n\t\t\t\t\th_util.open_float(hover_text, \"markdown\", cfg:instance())\n\t\t\t\tend\n\t\t\telse\n\t\t\t\tlocal hover_text = parse_response_contents(response.result.contents)\n\t\t\t\tif not hover_text then\n\t\t\t\t\tvim.notify(\"There is no text to be displayed\", vim.log.levels.INFO)\n\t\t\t\t\treturn\n\t\t\t\tend\n\n\t\t\t\th_util.open_float(hover_text, \"markdown\", cfg:instance())\n\t\t\tend\n\t\tend\n\tend\n\n\tif not called then\n\t\tlocal hover_text = number.get_number_representations()\n\t\tif not hover_text then\n\t\t\treturn\n\t\tend\n\n\t\th_util.open_float(hover_text, \"markdown\", cfg:instance())\n\t\treturn\n\tend\nend\n\nlocal function request_above11(results, ctx)\n\tlocal bufnr = assert(ctx.bufnr)\n\tif api.nvim_get_current_buf() ~= bufnr then\n\t\t-- Ignore result since buffer changed. This happens for slow language servers.\n\t\treturn\n\tend\n\n\t-- Filter errors from results\n\tlocal results1 = {} --- @type table<integer,lsp.Hover>\n\n\tfor client_id, resp in pairs(results) do\n\t\tlocal err, result = resp.err, resp.result\n\t\tif err then\n\t\t\tlsp.log.error(err.code, err.message)\n\t\telseif result then\n\t\t\tresults1[client_id] = result\n\t\tend\n\tend\n\n\tif vim.tbl_isempty(results1) then\n\t\tif cfg:instance().hover_cnf.silent ~= true then\n\t\t\tlocal hover_text = number.get_number_representations()\n\t\t\tif not hover_text then\n\t\t\t\tvim.notify('No information available')\n\t\t\t\treturn\n\t\t\tend\n\n\t\t\th_util.open_float(hover_text, \"markdown\", cfg:instance())\n\t\t\treturn\n\t\tend\n\t\treturn\n\tend\n\n\tlocal contents = {} --- @type string[]\n\n\tlocal nresults = #vim.tbl_keys(results1)\n\n\tlocal format = 'markdown'\n\n\tfor client_id, result in pairs(results1) do\n\t\tlocal client = assert(lsp.get_client_by_id(client_id))\n\t\tif nresults > 1 then\n\t\t\t-- Show client name if there are multiple clients\n\t\t\tcontents[#contents + 1] = string.format('# %s', client.name)\n\t\tend\n\t\tif type(result.contents) == 'table' and result.contents.kind == 'plaintext' then\n\t\t\tif #results1 == 1 then\n\t\t\t\tformat = 'plaintext'\n\t\t\t\tcontents = vim.split(result.contents.value or '', '\\n', { trimempty = true })\n\t\t\telse\n\t\t\t\t-- Surround plaintext with ``` to get correct formatting\n\t\t\t\tcontents[#contents + 1] = '```'\n\t\t\t\tvim.list_extend(\n\t\t\t\t\tcontents,\n\t\t\t\t\tvim.split(result.contents.value or '', '\\n', { trimempty = true })\n\t\t\t\t)\n\t\t\t\tcontents[#contents + 1] = '```'\n\t\t\tend\n\t\telse\n\t\t\tvim.list_extend(contents, util.convert_input_to_markdown_lines(result.contents))\n\t\tend\n\t\tlocal range = result.range\n\t\tif range then\n\t\t\tlocal start = range.start\n\t\t\tlocal end_ = range['end']\n\t\t\tlocal start_idx = util._get_line_byte_from_position(bufnr, start, client.offset_encoding)\n\t\t\tlocal end_idx = util._get_line_byte_from_position(bufnr, end_, client.offset_encoding)\n\n\t\t\tvim.hl.range(\n\t\t\t\tbufnr,\n\t\t\t\thover_ns,\n\t\t\t\t'LspReferenceTarget',\n\t\t\t\t{ start.line, start_idx },\n\t\t\t\t{ end_.line, end_idx },\n\t\t\t\t{ priority = vim.hl.priorities.user }\n\t\t\t)\n\t\tend\n\t\tcontents[#contents + 1] = '---'\n\tend\n\n\t-- Remove last linebreak ('---')\n\tcontents[#contents] = nil\n\n\tif vim.tbl_isempty(contents) then\n\t\tif cfg:instance().hover_cnf.silent ~= true then\n\t\t\tvim.notify('No information available')\n\t\tend\n\t\treturn\n\tend\n\n\tlocal _, winnr = h_util.open_float(contents, format, cfg:instance())\n\n\t-- Remove selection highlighting after window is closed\n\tapi.nvim_create_autocmd('WinClosed', {\n\t\tpattern = tostring(winnr),\n\t\tonce = true,\n\t\tcallback = function()\n\t\t\tapi.nvim_buf_clear_namespace(bufnr, hover_ns, 0, -1)\n\t\t\treturn true\n\t\tend,\n\t})\nend\n\n--- Function that will be used in hover request invoked by lsp.\n---@param results table Table of responses from the server.\n---@param ctx table Context of the request.\nfunction M.local_hover_request(results, ctx)\n\t-- Multi-server support is only available in nvim-0.11 and above.\n\t-- The user can still decide to use the multi-server or not.\n\tif vim.fn.has('nvim-0.11') == 1 and cfg:instance().multi_server then\n\t\trequest_above11(results, ctx)\n\t\treturn\n\tend\n\n\trequest_below11(results)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/pretty_hover/number.lua",
    "content": "local M = {}\n\n--- Convert the input number to the specified base.\n--- @param num number Number to be converted.\n--- @param base number Base to convert the number to.\n--- @return string The number converted into specified base in a string format.\nfunction M.toBase(num, base)\n\tlocal baseChars = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n\tlocal baseStr = \"\"\n\tlocal idx = 0\n\twhile num > 0 do\n\t\tlocal rem = num % base\n\t\tbaseStr = baseChars:sub(rem + 1, rem + 1) .. baseStr\n\t\tnum = math.floor(num / base)\n\n\t\tidx = idx + 1\n\t\tif idx % 4 == 0 and num > 0 then\n\t\t\tbaseStr = \" \" .. baseStr\n\t\tend\n\tend\n\treturn baseStr == \"\" and \"0\" or baseStr\nend\n\n--- Function to convert a number to its binary representation\n--- @see toBase\n--- @param num number Number to be converted.\n--- @return string The number converted into binary in a string format.\nfunction M.toBinary(num)\n\treturn M.toBase(num, 2)\nend\n\n--- Function to convert a number to its octal representation\n--- @see toBase\n--- @param num number Number to be converted.\n--- @return string The number converted into octal in a string format.\nfunction M.toOctal(num)\n\treturn M.toBase(num, 8)\nend\n\n--- Function to convert a number to its hexadecimal representation\n--- @see toBase\n--- @param num number Number to be converted.\n--- @return string The number converted into hexadecimal in a string format.\nfunction M.toHex(num)\n\treturn M.toBase(num, 16)\nend\n\n--- Pretty prints the decimal number by adding spaces after every 3 digits.\n--- @param num string Number to be pretty printed.\n--- @return string The pretty printed number.\nfunction M.prettyDecimal(num)\n\tlocal len = #num\n\tlocal pretty = \"\"\n\tlocal idx = 0\n\n\tfor i = len, 1, -1 do\n\t\tpretty = num:sub(i, i) .. pretty\n\t\tidx = idx + 1\n\t\tif idx % 3 == 0 and i ~= 1 then\n\t\t\tpretty = \" \" .. pretty\n\t\tend\n\tend\n\n\treturn pretty\nend\n\n--- Get the type of the number.\n--- @param num string Number to get the type of.\n--- @return number|nil The type of the number.\nfunction M.get_number_type(num)\n\tlocal original\n\tif type(num) == 'string' and tonumber(num) then\n\t\toriginal = num\n\telse\n\t\treturn nil\n\tend\n\n\tif original[1] ~= 0 or #original < 2 then\n\t\treturn 10\n\telseif original[2] == 'x' then\n\t\treturn 16\n\telseif original[2] == 'b' then\n\t\treturn 2\n\telseif original[2] == 'o' then\n\t\treturn 8\n\telse\n\t\treturn 10\n\tend\nend\n\n--- Function to get all representations of a number\n--- @param num string Number to get the representations of.\n--- @param type number Type of the number.\n--- @param return_type string The type of the return value, either \"string\" or \"table\".\n--- @return string[]|nil A table containing the representations of the number in different bases.\nfunction M.get_numerical_representations(num, type, return_type)\n\tlocal tmp = tonumber(num, type)\n\tif not tmp then\n\t\treturn nil\n\tend\n\n\tlocal decimal = M.prettyDecimal(tostring(tmp))\n\tlocal binary = M.toBinary(tmp)\n\tlocal octal = M.toOctal(tmp)\n\tlocal hexadecimal = M.toHex(tmp)\n\n\tlocal s =  string.format(\"### Number types:\\n---\\nBinary: 0b%s\\nOctal: 0o%s\\nDecimal: %s\\nHexadecimal: 0x%s\\n\", binary, octal, decimal, hexadecimal)\n\tif return_type == \"string\" then\n\t\treturn s\n\tend\n\treturn s:split(\"\\n\")\nend\n\n--- Function to get the number representations of the current word under the cursor.\n--- @see get_number_type\n--- @param return_type? string The number to get the representations of.\n--- @return string|string[]|nil The number representations of the current word under the cursor.\nfunction M.get_number_representations(return_type)\n\tlocal num = vim.fn.expand(\"<cword>\");\n\tif return_type == nil then\n\t\treturn_type = \"table\"\n\tend\n\n\tif num:sub(1,1) == '-' then\n\t\tnum = num:sub(2)\n\tend\n\n\tlocal number_type = M.get_number_type(num)\n\tif not number_type then\n\t\treturn\n\tend\n\n\tif number_type ~= 10 then\n\t\tnum = num:sub(2)\n\tend\n\treturn M.get_numerical_representations(num, number_type, return_type)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/pretty_hover/parser/init.lua",
    "content": "local M = {}\r\n\r\nlocal parser = require(\"pretty_hover.parser.parser\")\r\n\r\n---@class ParserOutput\r\n---@field public text table\r\n---@field public highlighting table\r\n---@field public new fun(self, text: table, highlighting: table): ParserOutput\r\n---@field public empty fun(self): ParserOutput\r\n---@field public string fun(self): string\r\nlocal ParserOutput = {\r\n\ttext = {},\r\n\thighlighting = {},\r\n\tstring = function(self)\r\n\t\treturn table.concat(self.text, \"\\n\")\r\n\tend,\r\n}\r\n\r\n--- Comparator function to check if two ParserOutput structs are equal.\r\n---@param lhs ParserOutput\r\n---@param rhs ParserOutput\r\n---@return boolean Result whether the lhs and rhs sides of the comparison are the same\r\nlocal function ParserOutputEQ(lhs, rhs)\r\n\tlocal str_lhs = vim.inspect(lhs)\r\n\tlocal str_rhs = vim.inspect(rhs)\r\n\r\n\treturn vim.fn.sha256(str_lhs) == vim.fn.sha256(str_rhs)\r\nend\r\n\r\n--- Creates new ParserOutput object.\r\n---\r\n---@param text table of parsed input. This goes directly to functions like vim.lsp.util.open_floating_preview\r\n---@param highlighting table of highlighting data\r\n---\r\n---The highlighting data can be applied fx. like this\r\n---\r\n---```lua\r\n--- if M.hl_ns then\r\n--- \tapi.nvim_buf_clear_namespace(bufnr, M.hl_ns, 0, -1)\r\n--- end\r\n---\r\n--- M.hl_ns = api.nvim_create_namespace(\"pretty_hover_ns\")\r\n---\r\n--- for name, _ in pairs(config.hl) do\r\n--- \tif hl_data.lines[tostring(name)] then\r\n--- \t\tfor _, line in pairs(hl_data.lines[tostring(name)]) do\r\n--- \t\t\tif type(line) == \"table\" then\r\n--- \t\t\t\tapi.nvim_buf_add_highlight(bufnr, M.hl_ns, \"PH\"..tostring(name), line.line_nr, 0, line.to);\r\n--- \t\t\tend\r\n--- \t\tend\r\n--- \tend\r\n--- end\r\n---```\r\n---\r\n---@return ParserOutput out New object of the referenced type\r\nfunction ParserOutput:new(text, highlighting)\r\n\tlocal out = {}\r\n\tsetmetatable(out, { __index = self, __eq = ParserOutputEQ })\r\n\tout.text = text\r\n\tout.highlighting = highlighting\r\n\treturn out\r\nend\r\n\r\nfunction ParserOutput:empty()\r\n\treturn ParserOutput:new({}, {})\r\nend\r\n\r\n--- @brief This method parses the input string or table and converts the contents from doxygen into markdown format.\r\n---\r\n--- NOTE: The string must have new lines inside. If the string is not separated by them the parsing will not be done.\r\n--- Additionally, if nil, empty string or empty table are passed in the returned object will have empty text and highlighting\r\n--- fields.\r\n---\r\n--- The output highlighting data can be applied fx. this\r\n---\r\n--- ```lua\r\n--- function M.apply_highlight(config, hl_data, bufnr)\r\n---ll \tif M.hl_ns then\r\n--- \t\tapi.nvim_buf_clear_namespace(bufnr, M.hl_ns, 0, -1)\r\n--- \tend\r\n---\r\n--- \tM.hl_ns = api.nvim_create_namespace(\"pretty_hover_ns\")\r\n---\r\n--- \tfor name, _ in pairs(config.hl) do\r\n--- \t\tif hl_data.lines[tostring(name)] then\r\n--- \t\t\tfor _, line in pairs(hl_data.lines[tostring(name)]) do\r\n--- \t\t\t\tif type(line) == \"table\" then\r\n--- \t\t\t\t\tapi.nvim_buf_add_highlight(bufnr, M.hl_ns, \"PH\"..tostring(name), line.line_nr, 0, line.to);\r\n--- \t\t\t\tend\r\n--- \t\t\tend\r\n--- \t\tend\r\n--- \tend\r\n--- end\r\n--- ```\r\n--- @param text string|table Text as a string\r\n--- @return ParserOutput Converted doxygen text into markdown.\r\n---\r\n--- @see pretty_hover.core.util.open_float function for the implementation in this plugin\r\n---\r\n--- @overload fun(text: string): ParserOutput\r\n--- @overload fun(text: table): ParserOutput\r\nfunction M.parse(text)\r\n\tif not text\r\n\t   or (type(text) == \"string\" and text == \"\")\r\n\t   or (type(text) == \"table\" and vim.tbl_isempty(text))\r\n\tthen\r\n\t\treturn ParserOutput:empty()\r\n\tend\r\n\r\n\tlocal config = require(\"pretty_hover.config\"):instance()\r\n\r\n\tlocal hl_data = {\r\n\t\treplacement = \"\",\r\n\t\tlines = {},\r\n\t}\r\n\r\n\tlocal tbl = parser.convert_to_markdown(text, config, hl_data)\r\n\r\n\treturn ParserOutput:new(tbl, hl_data)\r\nend\r\n\r\nreturn M\r\n\r\n"
  },
  {
    "path": "lua/pretty_hover/parser/parser.lua",
    "content": "local util = require(\"pretty_hover.core.util\")\r\n\r\nlocal M = {\r\n\tbrief = {\r\n\t\tdetected = false,\r\n\t\toption = \"\",\r\n\t},\r\n\ttext_start_detected = false,\r\n}\r\n\r\n--- Transforms the line from doxygen type into markdown\r\n---@param line string Line to be transformed.\r\n---@param config table Table of options to be used for the conversion to the markdown language.\r\n---@param hl_data table Table of control variables to be used for the pop-up window highlighting.\r\n---@param control table Table of control variables to be used for the conversion to the markdown language.\r\n---@return table Table of strings from doxygen to markdown.\r\nfunction M.transform_line(line, config, control, hl_data)\r\n\tlocal result = {}\r\n\r\n\t-- Some servers add whitespaces infornt of some rows.\r\n\tif line:find(\"^%s+[\\\\@]\") then\r\n\t\tline = line:gsub(\"^%s+\", \"\")\r\n\tend\r\n\r\n\tif line:find(\"&nbsp;\") then\r\n\t\tline = line:gsub(\"&nbsp;\", \" \")\r\n\tend\r\n\r\n\tif line:find(\"^(%s*)\\\\%-%s*\") then\r\n\t\tline = line:gsub(\"^(%s*)\\\\%-%s*\", \"%1- \")\r\n\t\t-- vim.print(line)\r\n\tend\r\n\r\n\tif line:find(\"^```text$\") then\r\n\t\tM.text_start_detected = true\r\n\t\tline = \"\"\r\n\tend\r\n\r\n\tif line:find(\"^```$\") and M.text_start_detected then\r\n\t\tM.text_start_detected = false\r\n\t\tline = \"\"\r\n\tend\r\n\r\n\r\n\tlocal tbl = util.split(line)\r\n\tlocal el = tbl[1]\r\n\tlocal insertEmptyLine = false\r\n\r\n\tfor name, group in pairs(config.hl) do\r\n\t\tif util.tbl_contains(group.detect, el) then\r\n\t\t\ttbl[1] = string.upper(util.find(group.detect, el))\r\n\t\t\tif tbl[1]:sub(1, 1) == '@' then\r\n\t\t\t\ttbl[1] = tbl[1]:sub(2)\r\n\t\t\telse\r\n\t\t\t\ttbl[1] = tbl[1]:sub(3)\r\n\t\t\tend\r\n\t\t\thl_data.lines[tostring(name)].detected = true\r\n\t\t\thl_data.replacement = tbl[1]\r\n\t\tend\r\n\tend\r\n\r\n\t-- Either end the brief line or extend it to the next line.\r\n\tif M.brief.detected and el and not el:sub(1,2):gmatch(\"[\\\\@]\")() then\r\n\t\tif M.brief.option == \"continue\" then\r\n\t\t\ttable.insert(result, \"\")\r\n\t\t\tM.brief.detected = false\r\n\t\t\tM.brief.option = \"\"\r\n\r\n\t\telseif M.brief.option == \"start\" then\r\n\t\t\ttbl[1] = config.line.styler .. tbl[1]\r\n\t\t\ttbl[#tbl] = tbl[#tbl] .. config.line.styler\r\n\t\t\tM.brief.detected = false\r\n\t\t\tM.brief.option = \"\"\r\n\t\tend\r\n\tend\r\n\r\n\tif util.tbl_contains(config.header.detect, el) then\r\n\t\ttbl[1] = config.header.styler\r\n\t\tinsertEmptyLine = true;\r\n\r\n\telseif util.tbl_contains(config.line.detect, el) then\r\n\t\ttable.remove(tbl, 1)\r\n\t\tM.brief.detected = true\r\n\r\n\t\tif #tbl == 0 then\r\n\t\t\tM.brief.option = \"start\"\r\n\r\n\t\telse\r\n\t\t\ttbl[1] = config.line.styler .. (tbl[1] or \"\")\r\n\t\t\ttbl[#tbl] = tbl[#tbl] .. config.line.styler\r\n\t\t\tM.brief.option = \"continue\"\r\n\t\tend\r\n\r\n\telseif util.tbl_contains(config.listing.detect, el) then\r\n\t\ttbl[1] = config.listing.styler\r\n\r\n\telseif util.tbl_contains(config.return_statement, el) then\r\n\t\ttable.insert(result, \"\")\r\n\t\ttbl[1] = \"**Return**\"\r\n\t\tline = util.join_table(tbl, \" \")\r\n\r\n\telseif util.tbl_contains(config.code.start, el) then\r\n\t\tlocal language = el:gmatch(\"{(%w+)}\")() or vim.o.filetype\r\n\t\ttable.insert(result, \"```\" .. language)\r\n\t\ttable.remove(tbl, 1)\r\n\r\n\telseif util.tbl_contains(config.code.ending, el) then\r\n\t\ttable.insert(result, \"```\")\r\n\t\ttable.remove(tbl, 1)\r\n\tend\r\n\r\n\tfor name, group in pairs(config.group.detect) do\r\n\t\tif group and util.tbl_contains(group, el) and string.match(tbl[1], el) and tbl[2] ~= nil then\r\n\t\t\ttbl[2] = config.group.styler .. tbl[2] .. config.group.styler\r\n\t\t\tif el == tbl[1] then\r\n\t\t\t\ttable.remove(tbl, 1)\r\n\t\t\tend\r\n\r\n\t\t\tif control[name] then\r\n\t\t\t\tcontrol[tostring(name)] = false\r\n\t\t\t\ttable.insert(result, \"---\")\r\n\t\t\t\ttable.insert(result, \"**\" .. name .. \"**\")\r\n\t\t\tend\r\n\t\tend\r\n\tend\r\n\r\n\tlocal ref = require(\"pretty_hover.parser.references\")\r\n\ttbl = ref.check_line_for_references(tbl, config)\r\n\tline = util.join_table(tbl, \" \")\r\n\ttable.insert(result, line)\r\n\tif insertEmptyLine then\r\n\t\ttable.insert(result, \"\")\r\n\tend\r\n\treturn result\r\nend\r\n\r\n--- Converts a string returned by response.result.contents.value from vim.lsp[textDocument/hover] to markdown.\r\n---@param toConvert string|table Documentation of the string to be converted.\r\n---@param config table Table of options to be used for the conversion to the markdown language.\r\n---@param hl_data table Table of control variables to be used for the pop-up window highlighting.\r\n---@return table Converted table of strings from doxygen to markdown.\r\n---@overload fun(toConvert: string, config: table, hl_data: table): table\r\n---@overload fun(toConvert: table, config: table, hl_data: table): table\r\nfunction M.convert_to_markdown(toConvert, config, hl_data)\r\n\r\n\tconfig.one_liner = false\r\n\tlocal result = {}\r\n\r\n\tlocal control = {}\r\n\tfor name, group in pairs(config.group.detect) do\r\n\t\tcontrol[tostring(name)] = true\r\n\tend\r\n\r\n\tlocal lines = toConvert\r\n\tif type(toConvert) == \"string\" then\r\n\t\tlines = util.split(toConvert, \"([^\\n]*)\\n?\")\r\n\tend\r\n\r\n\tif #lines == 0 then\r\n\t\treturn result\r\n\tend\r\n\r\n\t-- Remove footer padding. The last line is always empty.\r\n\tif lines[#lines] == \"\" then\r\n\t\ttable.remove(lines, #lines)\r\n\tend\r\n\r\n\tfor name, _ in pairs(config.hl) do\r\n\t\thl_data.lines[tostring(name)] = {}\r\n\tend\r\n\r\n\tfor _, line in pairs(lines) do\r\n\t\tlocal toAdd = M.transform_line(line, config, control, hl_data)\r\n\t\tvim.list_extend(result, toAdd)\r\n\r\n\t\tfor name, group in pairs(hl_data.lines) do\r\n\t\t\tif group.detected then\r\n\t\t\t\tgroup.detected = false\r\n\t\t\t\ttable.insert(hl_data.lines[tostring(name)], {\r\n\t\t\t\t\tline_nr = util.printable_table_size(result) - 2,\r\n\t\t\t\t\tto = (config.hl[tostring(name)].line and -1 or string.len(hl_data.replacement))\r\n\t\t\t\t})\r\n\t\t\tend\r\n\t\tend\r\n\tend\r\n\r\n\t-- If the message is only one-liner, remove the code block.\r\n\t-- See issue #24\r\n\tif #result == 3 and result[#result] == \"```\" then\r\n\t\tresult = { result[2] }\r\n\t\tconfig.one_liner = true\r\n\tend\r\n\r\n\treturn result\r\nend\r\n\r\nreturn M\r\n\r\n"
  },
  {
    "path": "lua/pretty_hover/parser/references.lua",
    "content": "local M = {}\n\nlocal util = require(\"pretty_hover.core.util\")\n\n--- Detect if the check line is already in bold.\n---@param table_line table Table of words to be checked.\n---@return boolean True if the line style is bold, false otherwise.\nfunction M.is_bold(table_line)\n\tlocal last_word = table_line[#table_line]\n\treturn table_line[1]:find(\"*\") == 1 and last_word:find(\"*\") == #last_word-1\nend\n\n--- Based on the tabled_line markdown representation, this function returns the surrounding string.\n---@param tabled_line table Table of words to be checked.\n---@param config table Table of options to be used for the conversion to the markdown language.\n---@return table The first element of the table is boolean which indicates if the string is already converted. Second element is the surrounding string.\nfunction M.get_surround_string(tabled_line, config)\n\tif tabled_line and #tabled_line > 0 and M.is_bold(tabled_line) then\n\t\treturn { is_brief = true, marker = config.references.styler[2]}\n\telse\n\t\treturn { is_brief = false, marker = config.references.styler[1]}\n\tend\nend\n\n--- Checks the current line on the index if it is an opening reference.\n---@param tabled_line table Table of strings representing current line.\n---@param index integer Index of the line to be checked.\n---@return boolean True if the reference is opening, false otherwise.\nfunction M.is_opening_reference(tabled_line, index)\n\tif not tabled_line or not tabled_line[index + 1] or not tabled_line[index] then\n\t\treturn false;\n\tend\n\n\treturn (tabled_line[index]:find(\"[(]\") or tabled_line[index+1]:find(\"[(]\")) and not tabled_line[index+1]:find(\"[)]\")\nend\n\n--- Surrounds the reference from the front. If the reference is opened, it is not closed.\n---@param tabled_line table Table of strings representing current line.\n---@param index integer Index of the word to be checked.\n---@param config table Table of options to be used for the conversion to the markdown language.\n---@param surround table Table of the surrounding strings.\nfunction M.surround_references(tabled_line, index, config, surround)\n\t-- Surround the word in brief line.\n\tif surround.is_brief then\n\t\t-- End the brief line formatting if possible.\n\t\tif tabled_line[index-1] then\n\t\t\ttabled_line[index-1] = tabled_line[index-1] .. config.line.styler\n\t\tend\n\t\t-- Start the reference formatting.\n\t\ttabled_line[index] = surround.marker .. tabled_line[index+1]\n\n\t\t-- End the reference formatting and start the brief line formatting if possible.\n\t\tif tabled_line[index+2] and not surround.openedReference then\n\t\t\ttabled_line[index] = tabled_line[index] .. surround.marker\n\t\t\ttabled_line[index+2] = config.line.styler .. tabled_line[index+2]\n\n\t\telseif tabled_line[index+2] then\n\t\t\t-- The reference is opened so we don't add ending reference.\n\n\t\telse\n\t\t\ttabled_line[index] = string.sub(tabled_line[index], 1, #tabled_line[index]-2) .. surround.marker\n\t\t\tif tabled_line[index]:find('[)]') then\n\t\t\t\tsurround.openedReference = false\n\t\t\tend\n\t\tend\n\n\t-- Surround the word in non-brief line.\n\telse\n\t\tif not tabled_line or not tabled_line[index + 1] or not tabled_line[index] then\n\t\t\treturn;\n\t\tend\n\n\t\ttabled_line[index] = surround.marker .. tabled_line[index+1]\n\n\t\t-- End the reference formatting and start the brief line formatting if possible.\n\t\tif not surround.openedReference then\n\t\t\ttabled_line[index] = tabled_line[index] .. surround.marker\n\t\tend\n\tend\nend\n\n--- Close the opened reference if it is opened.\n---@param tabled_line table Table of strings representing current line.\n---@param index integer Index of the word to be checked.\n---@param config table Table of options to be used for the conversion to the markdown language.\n---@param surround table Table of the surrounding strings.\nfunction M.close_opened_references(tabled_line, index, config, surround)\n\tif surround.openedReference and tabled_line[index]:find(\"[)]\") then\n\t\tif surround.is_brief then\n\t\t\tif tabled_line[index+1] then\n\t\t\t\ttabled_line[index] = tabled_line[index] .. surround.marker\n\t\t\t\ttabled_line[index+1] = config.line.styler .. tabled_line[index+1]\n\t\t\telse\n\t\t\t\ttabled_line[index] = string.sub(tabled_line[index], 1, #tabled_line[index]-2) .. surround.marker\n\t\t\tend\n\n\t\telse\n\t\t\ttabled_line[index] = tabled_line[index] .. surround.marker\n\n\t\tend\n\t\tsurround.openedReference = false\n\tend\nend\n\n--- Detects the HTML style hyperlinks in the line and converts them to markdown.\n---@param tabled_line table Line from the hover message split into words.\n---@param word string Word to be checked.\n---@param index integer Index of the word from the @c tabled_line to be checked.\nfunction M.detect_hyper_links(tabled_line, word, index)\n\tif word ~= \"\\\\<a\" then\n\t\treturn\n\tend\n\n\ttable.remove(tabled_line, index)\n\n\tword = tabled_line[index]\n\tlocal whole_link = vim.split(word, \"\\\"\", {trimempty = true})\n\tlocal link = whole_link[2]\n\tlocal styler = require(\"pretty_hover\").get_config().line.styler\n\n\t-- The link is not closed in the same part of the line separated by the space.\n\tif word:sub(1,4) == \"href\" and word:match(\"\\\\</a>\") then\n\t\t-- Handle the case of the link being the last word in the line.\n\t\tstyler = word:match(\"\\\\</a>\" .. styler) ~= nil and styler or \"\"\n\t\tlocal link_text = whole_link[3]:match(\"([%w_:.]+)\\\\</a>\") or link\n\t\ttabled_line[index] = \"[\" .. link_text  .. \"](\" .. link .. \")\" .. styler\n\n\t-- The link is closed in the next part of the line.\n\telseif word:sub(1,4) == \"href\" then\n\t\tlocal link_text = whole_link[3]:sub(2)\n\t\ttable.remove(tabled_line, index)\n\n\t\t-- Accumulate all the words until the closing tag.\n\t\twhile not tabled_line[index]:match(\"\\\\</a>\") do\n\t\t\tlink_text = link_text .. \" \" .. tabled_line[index]\n\t\t\ttable.remove(tabled_line, index)\n\t\tend\n\n\t\t-- The last word may be a space or just the closing tag.\n\t\tlocal final_word = tabled_line[index]:match(\"(%w+)\\\\</a>\") or \"\"\n\t\tlink_text = link_text .. \" \" .. final_word\n\n\t\t-- Handle the case of the link being the last word in the line.\n\t\tstyler = tabled_line[index]:match(\"\\\\</a>\" .. styler) ~= nil and styler or \"\"\n\n\t\ttabled_line[index] = \"[\" .. link_text  .. \"](\" .. link .. \")\" ..styler\n\tend\nend\n\n--- Remove escaping sequence before every character in the escapees string.\n--- The used escapees are '*' by default. Be sure that adding character to the string will not break\n--- the highlighting. e.g. adding _ will not do harm in words like __foo but in workds like __foo__.\n--- The second one will be highlighted as bold. The first one will be shortened to _foo.\n---\n--- @param tabled_line table Line from the hover message split into words.\n--- @param escapees table? Characters to be removed from the escape sequence.\nlocal function remove_excape_characters(tabled_line, escapees)\n\tescapees = vim.tbl_deep_extend(\"force\", { \"*\" }, escapees or {})\n\tlocal to_escape = table.concat(escapees)\n\n\tfor index, word in ipairs(tabled_line) do\n\t\ttabled_line[index] = word:gsub(\"\\\\([\" .. to_escape .. \"])\", \"%1\")\n\tend\nend\n\n--- Converts all the references to markdown text.\n---@param tabled_line table Words to be checked.\n---@param config table Table of options to be used for the conversion to the markdown language.\n---@return table Converted line to markdown.\nfunction M.check_line_for_references(tabled_line, config)\n\tlocal surround = M.get_surround_string(tabled_line, config)\n\tsurround.openedReference = false\n\n\tremove_excape_characters(tabled_line)\n\n\tfor index, word in ipairs(tabled_line) do\n\t\tif util.tbl_contains(config.references.detect, word) then\n\t\t\t-- Handle the parenthesis surrounding the reference.\n\t\t\tif tabled_line[index]:sub(1,1) == \"(\" then\n\t\t\t\ttabled_line[index+1] = \"(\" .. tabled_line[index+1]\n\t\t\tend\n\n\t\t\tif M.is_opening_reference(tabled_line, index) then\n\t\t\t\tsurround.openedReference = true\n\t\t\tend\n\n\t\t\tM.surround_references(tabled_line, index, config, surround)\n\n\t\t\ttable.remove(tabled_line, index + 1)\n\t\tend\n\n\t\tM.close_opened_references(tabled_line, index, config, surround)\n\t\tM.detect_hyper_links(tabled_line, word, index)\n\n\t\t-- We cannot use `word` because it will change also the hyperlinks.\n\t\ttabled_line[index] = tabled_line[index]:gsub(\"\\\\(<%w+)\", \"%1\")\n\tend\n\n\treturn tabled_line\nend\n\nreturn M\n"
  }
]