Full Code of Fildo7525/pretty_hover for AI

master a4212464431e cached
17 files
54.4 KB
15.0k tokens
1 requests
Download .txt
Repository: Fildo7525/pretty_hover
Branch: master
Commit: a4212464431e
Files: 17
Total size: 54.4 KB

Directory structure:
gitextract_zbixqniw/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── bug_report.md
│       └── feature_request.md
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── examples/
│   └── parsing.lua
└── lua/
    └── pretty_hover/
        ├── config.lua
        ├── core/
        │   ├── compatibility.lua
        │   └── util.lua
        ├── highlight.lua
        ├── init.lua
        ├── local_request.lua
        ├── number.lua
        └── parser/
            ├── init.lua
            ├── parser.lua
            └── references.lua

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: [e.g. iOS]
 - Browser [e.g. chrome, safari]
 - Version [e.g. 22]

**Smartphone (please complete the following information):**
 - Device: [e.g. iPhone6]
 - OS: [e.g. iOS8.1]
 - Browser [e.g. stock browser, safari]
 - Version [e.g. 22]

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .gitignore
================================================
lua/.luarc.json
test


================================================
FILE: CONTRIBUTING.md
================================================
Anybody is welcome to contribute to this project. If you have any idea, how to improve any
features or you have an idea for a new feature do not hesitate to fork this project
and create a PR. Any improvement is welocme.

In case you want to contribute to this project, here is the structure of the project:

**/core**
 - *compatibility.lua*
    - All the functions that change during the nvim versions.

 - *util.lua*
    - Utility project functions.

**/parser**
 - *init.lua*
    - Parser module public implementation. This should be used to parse the
      buffer. It is used internally by the plugin, too.

 - *parser.lua*
    - Internal implementation of the parser. The parser improvement or functionality has to be extended here.

 - *references.lua*
    - Parses the lines and detects the references in the buffer.

**/**
- *config.lua*
    - Default configuration of the plugin. This is used to set the default configuration of the plugin.

- *highlight.lua*
    - Module applying the highlighting detected by the parser.

- *init.lua*
    - The main module of the plugin. This supplys the public API of the plugin.

- *number.lua*
    - Module that creates a popup window with the number interpratation in multiple bases.


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 Filip Lobpreis

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<h1 align="center">
pretty_hover
</h1>

<p align="center">
<a href="https://github.com/Fildo7525/pretty_hover/stargazers">
	<img
		alt="Stargazers"
		src="https://img.shields.io/github/stars/Fildo7525/pretty_hover?style=for-the-badge&logo=starship&color=fae3b0&logoColor=d9e0ee&labelColor=282a36"
	/>
	</a>
	<a href="https://github.com/Fildo7525/pretty_hover/issues">
	<img
		alt="Issues"
		src="https://img.shields.io/github/issues/Fildo7525/pretty_hover?style=for-the-badge&logo=gitbook&color=ddb6f2&logoColor=d9e0ee&labelColor=282a36"
	/>
	</a>
	<a href="https://github.com/Fildo7525/pretty_hover/contributors">
	<img
		alt="Contributors"
		src="https://img.shields.io/github/contributors/Fildo7525/pretty_hover?style=for-the-badge&logo=opensourceinitiative&color=abe9b3&logoColor=d9e0ee&labelColor=282a36"
	/>
	</a>
</p>

## Table of contents

 - [How it looks](#how-it-looks)
 - [Installation and setup](#installation-and-setup)
 - [Configuration](#configuration)
 - [Integration](#integration)
	- [Blink.cmp](#blink.cmp)
 - [Default config](#default-configuration)
 - [Limitations](#limitations)
 - [Contributing](#contributing)
 - [Inspiration](#inspiration)

Pretty_hover is a lightweight plugin that parses the hover message before opening the popup window.
The output can be easily manipulated with. This will result in a more readable hover message.

An additional feature is `number conversion`. If you are tired of constantly converting some numbers to hex, octal
or binary you can use this plugin to do it for you.

### How it looks

> _**NOTE**_: The colors of the text depend on the color of your chosen colorscheme.
These pictures are taken with colorscheme `catppuccin-mocha`

Using native vim.lsp.buf.hover()
<img src="https://github.com/user-attachments/assets/5f4bd780-8a24-44c8-8a8e-4803ae9f7ace">

Using pretty_hover
<img src="https://github.com/user-attachments/assets/e547359e-0a82-4fac-ba75-388cdd291804">

## Installation and setup

### via Lazy
```lua
{
	"Fildo7525/pretty_hover",
	event = "LspAttach",
	opts = {}
},
```

### via Packer
```lua
use {
	"Fildo7525/pretty_hover",
	config = function()
		require("pretty_hover").setup({})
	end
}
```

### Using Pretty Hover
To open a hover window, run the following lua snippet (or bind it to a key)
```lua
require("pretty_hover").hover()
```

To close a hover window either move the cursor as with nvim's hover popup or
run the following lua snippet (e.g. from a keymap)
```lua
require("pretty_hover").close()
```
**NOTE: When focused on a hover window, you can also press `q` to close the hover window**

### Configuration

| Parameter		| Description	|
|----------------- | -------------- |
| line			 | If one of the supplied strings is located as the first word in the line the whole line is surrounded by `line.styler`. |
| listing		  | These words will be substituted with `listing.styler`. |
| group			 | 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. |
| header		   | List of strings. If this word is detected at the beginning of a line the word is substituted by `header.styler` |
| return statement | This words are substituted with **Return** (in bold) |
| references	   | 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)) |
| hl			   | 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. |
| border		   | Sets the border of the hover window. (none \| single \| double \| rounded \| solid \| shadow). |
| wrap			| Flag whether to wrap the text if the window is smaller. Otherwise the floating window is scrollable horizontally |
| max_width		| Sets the maximum width of the window. If you don't want any limitation set to nil. |
| max_height	   | Sets the maximum height of the window. If you don't want any limitation set to nil. |
| toggle	   | Flag detecting whether you want to have the hover just as a toggle window or make the popup focusable. |
| multi_server	   | Flag detecting whether you want to use the new multi lsp support or not. |

> _**NOTE**_: To really use this plugin you have to create a keymap that calls `require('pretty_hover').hover()` function.

The plugin supports code blocks. By specifying `@code{cpp}` the text in the popup window is highlighted with its filetype highlighter
until the `@endcode` is hit. When the filetype is not specified in the flag `@code` the filetype from the currently opened file is used.

#### Default configuration

```lua
{
	-- Tables grouping the detected strings and using the markdown highlighters.
	header = {
		detect = { "[\\@]class" },
		styler = '###',
	},
	line = {
		detect = { "[\\@]brief" },
		styler = '**',
	},
	listing = {
		detect = { "[\\@]li" },
		styler = " - ",
	},
	references = {
		detect = { "[\\@]ref", "[\\@]c", "[\\@]name" },
		styler = { "**", "`" },
	},
	group = {
		detect = {
			-- ["Group name"] = {"detectors"}
			["Parameters"] = { "[\\@]param", "[\\@]*param*" },
			["Types"] = { "[\\@]tparam" },
			["See"] = { "[\\@]see" },
			["Return Value"] = { "[\\@]retval" },
		},
		styler = "`",
	},

	-- Tables used for cleaner identification of hover segments.
	code = {
		start = { "[\\@]code" },
		ending = { "[\\@]endcode" },
	},
	return_statement = {
		"[\\@]return",
		"[\\@]*return*",
	},

	-- Highlight groups used in the hover method. Feel free to define your own highlight group.
	hl = {
		error = {
			color = "#DC2626",
			detect = { "[\\@]error", "[\\@]bug" },
			line = false, -- Flag detecting if the whole line should be highlighted
		},
		warning = {
			color = "#FBBF24",
			detect = { "[\\@]warning", "[\\@]thread_safety", "[\\@]throw" },
			line = false,
		},
		info = {
			color = "#2563EB",
			detect = { "[\\@]remark", "[\\@]note", "[\\@]notes" },
		},
		-- Here you can set up your highlight groups.
	},

	-- If you use nvim 0.11.0 or higher you can choose, whether you want to use the new
	-- multi lsp support or not. Otherwise this option is ignored.
	multi_server = true,
	border = "rounded",
	wrap = true,
	max_width = nil,
	max_height = nil,
	toggle = false,
}
```

### Integration

The plugin supports an easy integration:

```lua
local parsed = require("pretty_hover.parser").parse(text)
```

the parsed variable contains two fields `text` and `highlight`. The `text` field contains the converted text to markdown
and the `highlight` field contains the highlight groups for the text.

You can use the `parsed` variable to display the hover message in your own way.

```lua
vim.lsp.util.open_floating_preview(parsed.text, "markdown", {
	focus = true,
	focusable = true,
	wrap = true,
	wrap_at = 100,
	max_width = 100,
	border = "rounded",
	focus_id = "pretty-hover-example",
})
```

To see an example of the implementation see the `pretty_hover/examples/parsing.lua` file.

#### Blink.cmp

This functionality is supported for blink.cmp from version v0.13.0 and higher.
To use this plugin with `blink.cmp` documentation you can add the following code snippet to you configuration:

```lua
{
	completion = {
		documentation = {
			draw = function(opts)
				if opts.item and opts.item.documentation and opts.item.documentation.value then
					local out = require("pretty_hover.parser").parse(opts.item.documentation.value)
					opts.item.documentation.value = out:string()
				end

				opts.default_implementation(opts)
			end,
		}
	},
}
```

### Limitations

Currently, Neovim supports these markdown stylers: \`, \*, \`\`\`[language]. Unfortunately, you cannot do any
of their combination. If the support is extended there will be more options to style the pop-up window.
Newly this plugin started supporting highlighting see the [Configuration](#configuration) for more information.

### Contributing

If you have any idea how to improve this plugin do not hesitate to create a PR. Otherwise, if you know how
to improve the plugin mention it in a new issue. Enjoy the plugin.

### Inspiration

https://github.com/lewis6991/hover.nvim


================================================
FILE: examples/parsing.lua
================================================
local text = [[### function `main`
---
	→ `int`
Parameters:
	- `int argc`
	- `char ** argv`

@brief Neque porro quisquam est qui dolorem @c ipsum quia dolor sit amet, consectetur, adipisci velit..."

Lorem Ipsum is simply dummy text of the printing and typesetting industry.
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.
It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
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.

@note This is a note.

@param argc Number of arguments from the command line.
@param argv The arguments in format of the strings.
@return int The return of the program. Usually 0 for successful run.
---
```cpp
int main(int argc, char *argv[])
```
]]

local text_table = {
"	### function `main`",
"---",
"	→ `int`",
"Parameters:",
"	- `int argc`",
"	- `char ** argv`",
"",
"@brief Neque porro quisquam est qui dolorem @c ipsum quia dolor sit amet, consectetur, adipisci velit...",
"",
"Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
"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.",
"It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.",
"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.",
"",
"@note This is a note.",
"",
"@param argc Number of arguments from the command line.",
"@param argv The arguments in format of the strings.",
"@return int The return of the program. Usually 0 for successful run.",
"---",
"```cpp",
"int main(int argc, char *argv[])",
"```",
}

local parser = require("pretty_hover.parser")

local out = parser.parse(text)
--[[ local out = parser.parse(text_table) ]]

local bufnr, winnr = vim.lsp.util.open_floating_preview(out.text, "markdown", {
	focus = true,
	focusable = true,
	wrap = true,
	wrap_at = 100,
	max_width = 100,
	border = "rounded",
	focus_id = "pretty-hover-example",
})

--[[ require('pretty_hover.highlight').apply_highlight(out.highlighting, bufnr, require("pretty_hover").get_config()) ]]
require('pretty_hover.highlight').apply_highlight(out.highlighting, bufnr)


================================================
FILE: lua/pretty_hover/config.lua
================================================
---@class PrettyHoverConfig
local M = {
	_config = {},

	header = {
		detect = {"[\\@]class"},
		styler = '###',
	},
	line = {
		detect = { "[\\@]brief" },
		styler = '**',
	},
	listing = {
		detect = { "[\\@]li" },
		styler = " - ",
	},
	references = {
		detect = {
			"[\\@]ref",
			"[\\@]c",
			"[\\@]name",
			"[\\@]a",
		},
		styler = { "**", "`" },
	},
	group = {
		detect = {
			["Parameters"] = { "[\\@]param", "[\\@]*param*" },
			["Types"] = { "[\\@]tparam" },
			["See"] = { "[\\@]see" },
			["Return Value"] = { "[\\@]retval" },
		},
		styler = "`",
	},

	code = {
		start = {"[\\@]code"},
		ending = {"[\\@]endcode"},
	},
	return_statement = {
		"[\\@]return",
		"[\\@]*return*",
	},

	hl = {
		error = {
			color = "#DC2626",
			detect = {"[\\@]error", "[\\@]bug"},
			line = false,
		},
		warning = {
			color = "#FBBF24",
			detect = {"[\\@]warning", "[\\@]thread_safety", "[\\@]throw"},
			line = false,
		},
		info = {
			color = "#4FC1FF",
			detect = {"[\\@]remark", "[\\@]note", "[\\@]notes"},
		}
	},

	multi_server = true,
	border = "rounded",
	wrap = true,
	max_width = nil,
	max_height = nil,
	toggle = false,
}

---@class PrettyHoverConfig
---@brief This class is used to configure the pretty_hover plugin.
---@param config table Table of options to be used for the pretty_hover configuration. If none or empty is provided, the
---previous configuration will be used. If the previous configuration is also empty, the default configuration will be used.
---@return table config The configuration table that will be used for the pretty_hover plugin.
function M:instance(config)
	config = config or {}

	if vim.tbl_isempty(config) and not vim.tbl_isempty(self._config) then
		return self._config
	end

	self._config = vim.tbl_deep_extend('force', {}, self, config)
	require("pretty_hover.highlight").setup_colors(self._config)
	return self._config
end

-- return M
return M


================================================
FILE: lua/pretty_hover/core/compatibility.lua
================================================
local M = {}

--- Function that encapsulates the changes in nvim api for getting the active clients.
---
--- @return table List of active clients.
function M.get_clients()
	if vim.version().minor >= 11 then
		return vim.lsp.get_clients()
	else
		return vim.lsp.get_active_clients()
	end
end

function M.nvim_hl(name, fg)
	if vim.version().minor >= 11 then
		local ns = vim.api.nvim_get_namespaces()["pretty_hover_ns"]
		return vim.api.nvim_get_hl(ns, {name = name})
	end

	return vim.api.nvim_get_hl_by_name(name, fg)
end

return M


================================================
FILE: lua/pretty_hover/core/util.lua
================================================
local api = vim.api
local hl = require("pretty_hover.highlight")
local hover_ns = api.nvim_create_namespace('pretty_hover_range')
local compatibility = require("pretty_hover.core.compatibility")

local M = {}

local winnr = 0
local bufnr = 0

function string:split(delimiter)
	local result = { }
	local from = 1
	local delim_from, delim_to = string.find( self, delimiter, from )
	while delim_from do
		table.insert( result, string.sub( self, from , delim_from-1 ) )
		from = delim_to + 1
		delim_from, delim_to = string.find( self, delimiter, from )
	end
	table.insert( result, string.sub( self, from ) )
	return result
end

--- Check if a table contains desired element. vim.tbl_contains does not work for all cases.
---@param tbl table Table to be checked.
---@param el string Element to be checked.
---@return boolean True if the table contains the element, false otherwise.
function M.tbl_contains(tbl, el)
	if not el then
		return false
	end
	if not tbl then
		return false
	end

	for _, v in pairs(tbl) do
		if el:find(v) then
			return true
		end
	end
	return false
end

--- Checks the table for the desired element. If the element is found, it is returned, otherwise nil is returned.
---@param tbl table Table to be checked.
---@param el string Element to be checked for.
---@return string The element if it is found, nil otherwise.
function M.find(tbl, el)
	if not el or not tbl then
		return ""
	end

	for _, v in pairs(tbl) do
		if el:find(v) then
			return el
		end
	end
	return ""
end

--- Count the printable strings in the table.
---@param tbl table Table of string from hover.
---@return number Number of printable lines.
function M.printable_table_size(tbl)
	local count = 0

	for _, el in pairs(tbl) do
		if el then
			count = count + 1
		end
	end

	return count
end

--- Splits a string into a table of strings.
---@param toSplit string String to be split.
---@param separator string|nil The separator. If not defined, the separator is set to "%S+".
---@return table Table of strings split by the separator.
function M.split(toSplit, separator)
	local indentation = nil

	if separator == nil then
		indentation = string.match(toSplit, "^%s+")
		separator = "%S+"
	end

	if toSplit == nil then
		return {}
	end

	local chunks = {}
	if indentation ~= nil and indentation:len() > 0 then
		table.insert(chunks, indentation)
	end

	for substring in toSplit:gmatch(separator) do
		-- These both cases are here because of python server. Some servers have '.... ' in front of every line and some
		-- servers surround the whole message with '```text' and '```'. This is a workaround for that.
		if substring:sub(1, 2) == ". " then
			substring = substring:sub(5)
		end

		table.insert(chunks, substring)
	end

	return chunks
end

--- Join the elements of a table into a string with a delimiter.
---@param tbl table Table to be joined.
---@param delim string Delimiter to be used.
---@return string Joined string.
function M.join_table(tbl, delim)
	local result = ""
	for idx, chunk in pairs(tbl) do
		result = result .. chunk
		if idx ~= #tbl then
			result = result .. delim
		end
	end
	return result
end

--- This function checks all the active clients for current buffer and returns the active client that supports the current file type.
---@return table|nil Active client for the current buffer or nil if there is no active client.
function M.get_current_active_client()
	for _, client in ipairs(compatibility.get_clients()) do
		if M.tbl_contains(client.config.filetypes, vim.bo.filetype) then
			return client
		end
	end
	return nil
end

--- Close the opened floating window.
function M.close_float()
	-- Safeguard around accidentally calling close when there is no pretty_hover window open
	if winnr == 0 and bufnr == 0 then
		return
	end

	api.nvim_buf_clear_namespace(vim.fn.bufnr(), hover_ns, 0, -1)

	-- Before closing the window, check if it is still valid.
	if not api.nvim_win_is_valid(winnr) then
		winnr = 0
		bufnr = 0
		return
	end

	api.nvim_win_close(winnr, true)
	winnr = 0
	bufnr = 0
end

--- The file is a link in markdown style and is represented as [\w+](<uri>#L<row>,<col>).
--- This function opens the file in a new buffer and jumps to the given line and column.
function M.open_file_under_cursor()
	local line = api.nvim_get_current_line()
	local target = line:match("%[(.-)%]%((.-)#L(%d+),?(%d*)%)")

	if not target then
		vim.notify("1. No valid file link under cursor", vim.log.levels.WARN)
		return
	end

	local _, uri, row, col = line:match("%[(.-)%]%((.-)#L(%d+),?(%d*)%)")
	if not uri or not row then
		vim.notify("2. No valid file link under cursor", vim.log.levels.WARN)
		return
	end

	row = tonumber(row)
	col = tonumber(col) or 0

	M.close_float()

	-- Open the file in a new buffer
	vim.cmd("edit " .. uri)

	-- Jump to the specified line and column
	api.nvim_win_set_cursor(0, {row, col})
end

--- Opens a floating window with the documentation transformed from doxygen to markdown.
---@param hover_text string[] Text to be converted.
---@param format string Filetype to be used for the conversion.
---@param config table Table of options to be used for the conversion to the markdown language.
function M.open_float(hover_text, format, config)
	if not hover_text or #hover_text == 0 then
		-- There is nothing to display, quit out early
		local tabled_numbers = require("pretty_hover.number").get_number_representations()
		if not tabled_numbers then
			vim.notify("No information available", vim.log.levels.INFO)
			return
		end

		M.open_float(tabled_numbers:split("\n"), format, config)
		return
	end

	-- Convert Doxygen comments to Markdown format
	local out = require("pretty_hover.parser").parse(hover_text)
	if #out.text == 0 then
		vim.notify("No information available", vim.log.levels.INFO)
		return
	end

	if config.toggle and winnr ~= 0 then
		M.close_float()
		return
	end

	local language = format
	if config.one_liner then
		language = vim.bo.filetype
	end

	bufnr, winnr = vim.lsp.util.open_floating_preview(out.text, language, {
		border = config.border,
		focusable = true,
		focus = true,
		focus_id = "pretty-hover",
		wrap = config.wrap,
		wrap_at = config.max_width and config.max_width - 2 or nil,
		max_width = config.max_width,
		max_height = config.max_height,
	})

	vim.wo[winnr].foldenable = false
	vim.bo[bufnr].modifiable = false
	vim.bo[bufnr].bufhidden = 'wipe'

	hl.apply_highlight(out.highlighting, bufnr, config)

	vim.keymap.set('n', 'gf', M.open_file_under_cursor, {
		buffer = bufnr,
		silent = true,
		nowait = true,
	})

	vim.keymap.set('n', 'q', M.close_float, {
		buffer = bufnr,
		silent = true,
		nowait = true,
	})

    return bufnr, winnr
end

return M



================================================
FILE: lua/pretty_hover/highlight.lua
================================================
local M = {}

local compatibility = require "pretty_hover.core.compatibility"

local api = vim.api

--- Convert HEX color representation to RGB
---@param hex string HEX color representation
---@return number|nil, number|nil, number|nil # RGB color representation
function M.hex2rgb(hex)
	hex = hex:gsub("#", "")
	return tonumber("0x" .. hex:sub(1, 2)), tonumber("0x" .. hex:sub(3, 4)), tonumber("0x" .. hex:sub(5, 6))
end

--- Check if HEX color is dark
---@param hex string HEX color representation
---@return boolean True if color is dark, false otherwise
function M.is_dark(hex)
	local r, g, b = M.hex2rgb(hex)
	local lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255
	return lum <= 0.5
end

--- Get the highlight group
---@param name string Highlight group name
---@return table|nil Highlight group
function M.get_hl(name)
	local hl = compatibility.nvim_hl(name, true)

	for _, key in pairs({ "foreground", "background", "special" }) do
		if hl[key] then
			hl[key] = string.format("#%06x", hl[key])
		end
	end

	return hl
end

--- Setup color groups for pretty_hover plugin.
---@param config table Options from the config.
function M.setup_colors(config)
	M.hl_ns = api.nvim_create_namespace("pretty_hover_ns")
	local normal = M.get_hl("Normal")

	if not normal then
		vim.notify("No normal highlight group found", vim.log.levels.WARN)
		return
	end

	for kw, hl_groups in pairs(config.hl) do
		local kw_color = hl_groups.color or "default"
		local hex

		if kw_color:sub(1, 1) == "#" then
			hex = kw_color
		else
			local colors = M.options.colors[kw_color]
			colors = type(colors) == "string" and { colors } or colors

			for _, color in pairs(colors) do
				if color:sub(1, 1) == "#" then
					hex = color
					break
				end
				local c = M.get_hl(color)
				if c and c.foreground then
					hex = c.foreground
					break
				end
			end
		end
		if not hex then
			error("Todo: no color for " .. kw)
		end

		vim.cmd("hi def PH" .. kw .. " guibg=NONE  guifg=" .. hex .. " gui=NONE")
	end
end

--- Applies the highlight to the lines of the opened floating window.
--- The used groups are ErrorMsg and WarningMsg. For the proper highlighting, the
--- highlight groups must be defined.
---@param hl_data table Table of control variables that were set during the conversion to markdown.
---@param bufnr number Buffer number of the pop-up window.
---@param config table|nil Table of configurations.
---@overload fun(hl_data: table, bufnr: number)
---@overload fun(hl_data: table, bufnr: number, config: table)
function M.apply_highlight(hl_data, bufnr, config)
	if not config then
		config = require("pretty_hover").get_config()
	end

	if M.hl_ns then
		api.nvim_buf_clear_namespace(bufnr, M.hl_ns, 0, -1)
	end

	M.hl_ns = api.nvim_create_namespace("pretty_hover_ns")

	for name, _ in pairs(config.hl) do
		if hl_data.lines[tostring(name)] then
			for _, line in pairs(hl_data.lines[tostring(name)]) do
				if type(line) == "table" then
					api.nvim_buf_add_highlight(bufnr, M.hl_ns, "PH"..tostring(name), line.line_nr, 0, line.to);
				end
			end
		end
	end
end


return M


================================================
FILE: lua/pretty_hover/init.lua
================================================
local api = vim.api
local cfg = require("pretty_hover.config")
local h_util = require("pretty_hover.core.util")
local local_hover_request = require("pretty_hover.local_request").local_hover_request

local M = {}

--- Parses the response from the server and displays the hover information converted to markdown.
function M.hover(config)
	local params = vim.lsp.util.make_position_params(0, 'utf-16')

	-- Check if the server for this file type exists and supports hover.
	local client = h_util.get_current_active_client()
	local hover_support_present = client and client.capabilities.textDocument.hover

	if not client or not hover_support_present then
		vim.notify("There is no client for this filetype or the client does not support the hover capability.", vim.log.levels.WARN)
		return
	end

	config = config or {}
	cfg:instance().hover_cnf = config

	vim.lsp.buf_request_all(0, "textDocument/hover", params, local_hover_request)
end

--- Setup the plugin to use the given options.
---@param config table Options to be set for the plugin.
function M.setup(config)
	config = cfg:instance(config)

	if config.toggle then
		local id = api.nvim_create_augroup("pretty_hover_augroup", {
			clear = true,
		})
		api.nvim_create_autocmd({ "CursorMoved" }, {
			callback = function()
				require("pretty_hover.core.util").close_float()
			end,
			group = id,
		})
	end
end

--- Close the opened floating window.
function M.close()
	h_util.close_float()
end

function M.get_config()
	return cfg:instance()
end

return M


================================================
FILE: lua/pretty_hover/local_request.lua
================================================
local api = vim.api
local lsp = vim.lsp
local util = vim.lsp.util
local hover_ns = api.nvim_create_namespace('pretty_hover_range')
local cfg = require("pretty_hover.config")

local h_util = require("pretty_hover.core.util")
local number = require("pretty_hover.number")

local M = {}

local function parse_response_contents(contents)
	local hover_text = contents.value;
	-- vtsls workaround, this lsp does not contain value in the contents. It's just pure text.
	if type(contents) == "string" then
		hover_text = contents
	end

	if hover_text ~= nil then
		return hover_text
	end

	-- typescript-tools.nvim workaround
	-- Add a test in case there are no contents.
	if not pcall(function() hover_text = contents[1].value end) then
		return
	end
	hover_text = hover_text or ""
	for i = 2, #contents do
		if type(contents[i]) ~= "string" then
			vim.notify("Unexpected item type found in hover request's response.\n" ..
				"Please report an issue on github: https://github.com/Fildo7525/pretty_hover",
				vim.log.levels.ERROR)
			break
		end
		hover_text = hover_text .. contents[i]
	end
	return hover_text
end

local function request_below11(results)
	local called = false

	for _, response in pairs(results) do
		if response.result and response.result.contents and called == false then
			called = true
			local contents = response.result.contents

			-- We have to do this because of java. Sometimes is the value parameter split
			-- into two chunks. Leaving the rest of the hover message as the second argument
			-- in the received table.
			if contents.language == "java" then
				for _, content in pairs(contents) do
					local hover_text = content.value or content
					if not hover_text then
						vim.notify("There is no text to be displayed", vim.log.levels.INFO)
						return
					end

					h_util.open_float(hover_text, "markdown", cfg:instance())
				end
			else
				local hover_text = parse_response_contents(response.result.contents)
				if not hover_text then
					vim.notify("There is no text to be displayed", vim.log.levels.INFO)
					return
				end

				h_util.open_float(hover_text, "markdown", cfg:instance())
			end
		end
	end

	if not called then
		local hover_text = number.get_number_representations()
		if not hover_text then
			return
		end

		h_util.open_float(hover_text, "markdown", cfg:instance())
		return
	end
end

local function request_above11(results, ctx)
	local bufnr = assert(ctx.bufnr)
	if api.nvim_get_current_buf() ~= bufnr then
		-- Ignore result since buffer changed. This happens for slow language servers.
		return
	end

	-- Filter errors from results
	local results1 = {} --- @type table<integer,lsp.Hover>

	for client_id, resp in pairs(results) do
		local err, result = resp.err, resp.result
		if err then
			lsp.log.error(err.code, err.message)
		elseif result then
			results1[client_id] = result
		end
	end

	if vim.tbl_isempty(results1) then
		if cfg:instance().hover_cnf.silent ~= true then
			local hover_text = number.get_number_representations()
			if not hover_text then
				vim.notify('No information available')
				return
			end

			h_util.open_float(hover_text, "markdown", cfg:instance())
			return
		end
		return
	end

	local contents = {} --- @type string[]

	local nresults = #vim.tbl_keys(results1)

	local format = 'markdown'

	for client_id, result in pairs(results1) do
		local client = assert(lsp.get_client_by_id(client_id))
		if nresults > 1 then
			-- Show client name if there are multiple clients
			contents[#contents + 1] = string.format('# %s', client.name)
		end
		if type(result.contents) == 'table' and result.contents.kind == 'plaintext' then
			if #results1 == 1 then
				format = 'plaintext'
				contents = vim.split(result.contents.value or '', '\n', { trimempty = true })
			else
				-- Surround plaintext with ``` to get correct formatting
				contents[#contents + 1] = '```'
				vim.list_extend(
					contents,
					vim.split(result.contents.value or '', '\n', { trimempty = true })
				)
				contents[#contents + 1] = '```'
			end
		else
			vim.list_extend(contents, util.convert_input_to_markdown_lines(result.contents))
		end
		local range = result.range
		if range then
			local start = range.start
			local end_ = range['end']
			local start_idx = util._get_line_byte_from_position(bufnr, start, client.offset_encoding)
			local end_idx = util._get_line_byte_from_position(bufnr, end_, client.offset_encoding)

			vim.hl.range(
				bufnr,
				hover_ns,
				'LspReferenceTarget',
				{ start.line, start_idx },
				{ end_.line, end_idx },
				{ priority = vim.hl.priorities.user }
			)
		end
		contents[#contents + 1] = '---'
	end

	-- Remove last linebreak ('---')
	contents[#contents] = nil

	if vim.tbl_isempty(contents) then
		if cfg:instance().hover_cnf.silent ~= true then
			vim.notify('No information available')
		end
		return
	end

	local _, winnr = h_util.open_float(contents, format, cfg:instance())

	-- Remove selection highlighting after window is closed
	api.nvim_create_autocmd('WinClosed', {
		pattern = tostring(winnr),
		once = true,
		callback = function()
			api.nvim_buf_clear_namespace(bufnr, hover_ns, 0, -1)
			return true
		end,
	})
end

--- Function that will be used in hover request invoked by lsp.
---@param results table Table of responses from the server.
---@param ctx table Context of the request.
function M.local_hover_request(results, ctx)
	-- Multi-server support is only available in nvim-0.11 and above.
	-- The user can still decide to use the multi-server or not.
	if vim.fn.has('nvim-0.11') == 1 and cfg:instance().multi_server then
		request_above11(results, ctx)
		return
	end

	request_below11(results)
end

return M


================================================
FILE: lua/pretty_hover/number.lua
================================================
local M = {}

--- Convert the input number to the specified base.
--- @param num number Number to be converted.
--- @param base number Base to convert the number to.
--- @return string The number converted into specified base in a string format.
function M.toBase(num, base)
	local baseChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	local baseStr = ""
	local idx = 0
	while num > 0 do
		local rem = num % base
		baseStr = baseChars:sub(rem + 1, rem + 1) .. baseStr
		num = math.floor(num / base)

		idx = idx + 1
		if idx % 4 == 0 and num > 0 then
			baseStr = " " .. baseStr
		end
	end
	return baseStr == "" and "0" or baseStr
end

--- Function to convert a number to its binary representation
--- @see toBase
--- @param num number Number to be converted.
--- @return string The number converted into binary in a string format.
function M.toBinary(num)
	return M.toBase(num, 2)
end

--- Function to convert a number to its octal representation
--- @see toBase
--- @param num number Number to be converted.
--- @return string The number converted into octal in a string format.
function M.toOctal(num)
	return M.toBase(num, 8)
end

--- Function to convert a number to its hexadecimal representation
--- @see toBase
--- @param num number Number to be converted.
--- @return string The number converted into hexadecimal in a string format.
function M.toHex(num)
	return M.toBase(num, 16)
end

--- Pretty prints the decimal number by adding spaces after every 3 digits.
--- @param num string Number to be pretty printed.
--- @return string The pretty printed number.
function M.prettyDecimal(num)
	local len = #num
	local pretty = ""
	local idx = 0

	for i = len, 1, -1 do
		pretty = num:sub(i, i) .. pretty
		idx = idx + 1
		if idx % 3 == 0 and i ~= 1 then
			pretty = " " .. pretty
		end
	end

	return pretty
end

--- Get the type of the number.
--- @param num string Number to get the type of.
--- @return number|nil The type of the number.
function M.get_number_type(num)
	local original
	if type(num) == 'string' and tonumber(num) then
		original = num
	else
		return nil
	end

	if original[1] ~= 0 or #original < 2 then
		return 10
	elseif original[2] == 'x' then
		return 16
	elseif original[2] == 'b' then
		return 2
	elseif original[2] == 'o' then
		return 8
	else
		return 10
	end
end

--- Function to get all representations of a number
--- @param num string Number to get the representations of.
--- @param type number Type of the number.
--- @param return_type string The type of the return value, either "string" or "table".
--- @return string[]|nil A table containing the representations of the number in different bases.
function M.get_numerical_representations(num, type, return_type)
	local tmp = tonumber(num, type)
	if not tmp then
		return nil
	end

	local decimal = M.prettyDecimal(tostring(tmp))
	local binary = M.toBinary(tmp)
	local octal = M.toOctal(tmp)
	local hexadecimal = M.toHex(tmp)

	local s =  string.format("### Number types:\n---\nBinary: 0b%s\nOctal: 0o%s\nDecimal: %s\nHexadecimal: 0x%s\n", binary, octal, decimal, hexadecimal)
	if return_type == "string" then
		return s
	end
	return s:split("\n")
end

--- Function to get the number representations of the current word under the cursor.
--- @see get_number_type
--- @param return_type? string The number to get the representations of.
--- @return string|string[]|nil The number representations of the current word under the cursor.
function M.get_number_representations(return_type)
	local num = vim.fn.expand("<cword>");
	if return_type == nil then
		return_type = "table"
	end

	if num:sub(1,1) == '-' then
		num = num:sub(2)
	end

	local number_type = M.get_number_type(num)
	if not number_type then
		return
	end

	if number_type ~= 10 then
		num = num:sub(2)
	end
	return M.get_numerical_representations(num, number_type, return_type)
end

return M


================================================
FILE: lua/pretty_hover/parser/init.lua
================================================
local M = {}

local parser = require("pretty_hover.parser.parser")

---@class ParserOutput
---@field public text table
---@field public highlighting table
---@field public new fun(self, text: table, highlighting: table): ParserOutput
---@field public empty fun(self): ParserOutput
---@field public string fun(self): string
local ParserOutput = {
	text = {},
	highlighting = {},
	string = function(self)
		return table.concat(self.text, "\n")
	end,
}

--- Comparator function to check if two ParserOutput structs are equal.
---@param lhs ParserOutput
---@param rhs ParserOutput
---@return boolean Result whether the lhs and rhs sides of the comparison are the same
local function ParserOutputEQ(lhs, rhs)
	local str_lhs = vim.inspect(lhs)
	local str_rhs = vim.inspect(rhs)

	return vim.fn.sha256(str_lhs) == vim.fn.sha256(str_rhs)
end

--- Creates new ParserOutput object.
---
---@param text table of parsed input. This goes directly to functions like vim.lsp.util.open_floating_preview
---@param highlighting table of highlighting data
---
---The highlighting data can be applied fx. like this
---
---```lua
--- if M.hl_ns then
--- 	api.nvim_buf_clear_namespace(bufnr, M.hl_ns, 0, -1)
--- end
---
--- M.hl_ns = api.nvim_create_namespace("pretty_hover_ns")
---
--- for name, _ in pairs(config.hl) do
--- 	if hl_data.lines[tostring(name)] then
--- 		for _, line in pairs(hl_data.lines[tostring(name)]) do
--- 			if type(line) == "table" then
--- 				api.nvim_buf_add_highlight(bufnr, M.hl_ns, "PH"..tostring(name), line.line_nr, 0, line.to);
--- 			end
--- 		end
--- 	end
--- end
---```
---
---@return ParserOutput out New object of the referenced type
function ParserOutput:new(text, highlighting)
	local out = {}
	setmetatable(out, { __index = self, __eq = ParserOutputEQ })
	out.text = text
	out.highlighting = highlighting
	return out
end

function ParserOutput:empty()
	return ParserOutput:new({}, {})
end

--- @brief This method parses the input string or table and converts the contents from doxygen into markdown format.
---
--- NOTE: The string must have new lines inside. If the string is not separated by them the parsing will not be done.
--- Additionally, if nil, empty string or empty table are passed in the returned object will have empty text and highlighting
--- fields.
---
--- The output highlighting data can be applied fx. this
---
--- ```lua
--- function M.apply_highlight(config, hl_data, bufnr)
---ll 	if M.hl_ns then
--- 		api.nvim_buf_clear_namespace(bufnr, M.hl_ns, 0, -1)
--- 	end
---
--- 	M.hl_ns = api.nvim_create_namespace("pretty_hover_ns")
---
--- 	for name, _ in pairs(config.hl) do
--- 		if hl_data.lines[tostring(name)] then
--- 			for _, line in pairs(hl_data.lines[tostring(name)]) do
--- 				if type(line) == "table" then
--- 					api.nvim_buf_add_highlight(bufnr, M.hl_ns, "PH"..tostring(name), line.line_nr, 0, line.to);
--- 				end
--- 			end
--- 		end
--- 	end
--- end
--- ```
--- @param text string|table Text as a string
--- @return ParserOutput Converted doxygen text into markdown.
---
--- @see pretty_hover.core.util.open_float function for the implementation in this plugin
---
--- @overload fun(text: string): ParserOutput
--- @overload fun(text: table): ParserOutput
function M.parse(text)
	if not text
	   or (type(text) == "string" and text == "")
	   or (type(text) == "table" and vim.tbl_isempty(text))
	then
		return ParserOutput:empty()
	end

	local config = require("pretty_hover.config"):instance()

	local hl_data = {
		replacement = "",
		lines = {},
	}

	local tbl = parser.convert_to_markdown(text, config, hl_data)

	return ParserOutput:new(tbl, hl_data)
end

return M



================================================
FILE: lua/pretty_hover/parser/parser.lua
================================================
local util = require("pretty_hover.core.util")

local M = {
	brief = {
		detected = false,
		option = "",
	},
	text_start_detected = false,
}

--- Transforms the line from doxygen type into markdown
---@param line string Line to be transformed.
---@param config table Table of options to be used for the conversion to the markdown language.
---@param hl_data table Table of control variables to be used for the pop-up window highlighting.
---@param control table Table of control variables to be used for the conversion to the markdown language.
---@return table Table of strings from doxygen to markdown.
function M.transform_line(line, config, control, hl_data)
	local result = {}

	-- Some servers add whitespaces infornt of some rows.
	if line:find("^%s+[\\@]") then
		line = line:gsub("^%s+", "")
	end

	if line:find("&nbsp;") then
		line = line:gsub("&nbsp;", " ")
	end

	if line:find("^(%s*)\\%-%s*") then
		line = line:gsub("^(%s*)\\%-%s*", "%1- ")
		-- vim.print(line)
	end

	if line:find("^```text$") then
		M.text_start_detected = true
		line = ""
	end

	if line:find("^```$") and M.text_start_detected then
		M.text_start_detected = false
		line = ""
	end


	local tbl = util.split(line)
	local el = tbl[1]
	local insertEmptyLine = false

	for name, group in pairs(config.hl) do
		if util.tbl_contains(group.detect, el) then
			tbl[1] = string.upper(util.find(group.detect, el))
			if tbl[1]:sub(1, 1) == '@' then
				tbl[1] = tbl[1]:sub(2)
			else
				tbl[1] = tbl[1]:sub(3)
			end
			hl_data.lines[tostring(name)].detected = true
			hl_data.replacement = tbl[1]
		end
	end

	-- Either end the brief line or extend it to the next line.
	if M.brief.detected and el and not el:sub(1,2):gmatch("[\\@]")() then
		if M.brief.option == "continue" then
			table.insert(result, "")
			M.brief.detected = false
			M.brief.option = ""

		elseif M.brief.option == "start" then
			tbl[1] = config.line.styler .. tbl[1]
			tbl[#tbl] = tbl[#tbl] .. config.line.styler
			M.brief.detected = false
			M.brief.option = ""
		end
	end

	if util.tbl_contains(config.header.detect, el) then
		tbl[1] = config.header.styler
		insertEmptyLine = true;

	elseif util.tbl_contains(config.line.detect, el) then
		table.remove(tbl, 1)
		M.brief.detected = true

		if #tbl == 0 then
			M.brief.option = "start"

		else
			tbl[1] = config.line.styler .. (tbl[1] or "")
			tbl[#tbl] = tbl[#tbl] .. config.line.styler
			M.brief.option = "continue"
		end

	elseif util.tbl_contains(config.listing.detect, el) then
		tbl[1] = config.listing.styler

	elseif util.tbl_contains(config.return_statement, el) then
		table.insert(result, "")
		tbl[1] = "**Return**"
		line = util.join_table(tbl, " ")

	elseif util.tbl_contains(config.code.start, el) then
		local language = el:gmatch("{(%w+)}")() or vim.o.filetype
		table.insert(result, "```" .. language)
		table.remove(tbl, 1)

	elseif util.tbl_contains(config.code.ending, el) then
		table.insert(result, "```")
		table.remove(tbl, 1)
	end

	for name, group in pairs(config.group.detect) do
		if group and util.tbl_contains(group, el) and string.match(tbl[1], el) and tbl[2] ~= nil then
			tbl[2] = config.group.styler .. tbl[2] .. config.group.styler
			if el == tbl[1] then
				table.remove(tbl, 1)
			end

			if control[name] then
				control[tostring(name)] = false
				table.insert(result, "---")
				table.insert(result, "**" .. name .. "**")
			end
		end
	end

	local ref = require("pretty_hover.parser.references")
	tbl = ref.check_line_for_references(tbl, config)
	line = util.join_table(tbl, " ")
	table.insert(result, line)
	if insertEmptyLine then
		table.insert(result, "")
	end
	return result
end

--- Converts a string returned by response.result.contents.value from vim.lsp[textDocument/hover] to markdown.
---@param toConvert string|table Documentation of the string to be converted.
---@param config table Table of options to be used for the conversion to the markdown language.
---@param hl_data table Table of control variables to be used for the pop-up window highlighting.
---@return table Converted table of strings from doxygen to markdown.
---@overload fun(toConvert: string, config: table, hl_data: table): table
---@overload fun(toConvert: table, config: table, hl_data: table): table
function M.convert_to_markdown(toConvert, config, hl_data)

	config.one_liner = false
	local result = {}

	local control = {}
	for name, group in pairs(config.group.detect) do
		control[tostring(name)] = true
	end

	local lines = toConvert
	if type(toConvert) == "string" then
		lines = util.split(toConvert, "([^\n]*)\n?")
	end

	if #lines == 0 then
		return result
	end

	-- Remove footer padding. The last line is always empty.
	if lines[#lines] == "" then
		table.remove(lines, #lines)
	end

	for name, _ in pairs(config.hl) do
		hl_data.lines[tostring(name)] = {}
	end

	for _, line in pairs(lines) do
		local toAdd = M.transform_line(line, config, control, hl_data)
		vim.list_extend(result, toAdd)

		for name, group in pairs(hl_data.lines) do
			if group.detected then
				group.detected = false
				table.insert(hl_data.lines[tostring(name)], {
					line_nr = util.printable_table_size(result) - 2,
					to = (config.hl[tostring(name)].line and -1 or string.len(hl_data.replacement))
				})
			end
		end
	end

	-- If the message is only one-liner, remove the code block.
	-- See issue #24
	if #result == 3 and result[#result] == "```" then
		result = { result[2] }
		config.one_liner = true
	end

	return result
end

return M



================================================
FILE: lua/pretty_hover/parser/references.lua
================================================
local M = {}

local util = require("pretty_hover.core.util")

--- Detect if the check line is already in bold.
---@param table_line table Table of words to be checked.
---@return boolean True if the line style is bold, false otherwise.
function M.is_bold(table_line)
	local last_word = table_line[#table_line]
	return table_line[1]:find("*") == 1 and last_word:find("*") == #last_word-1
end

--- Based on the tabled_line markdown representation, this function returns the surrounding string.
---@param tabled_line table Table of words to be checked.
---@param config table Table of options to be used for the conversion to the markdown language.
---@return table The first element of the table is boolean which indicates if the string is already converted. Second element is the surrounding string.
function M.get_surround_string(tabled_line, config)
	if tabled_line and #tabled_line > 0 and M.is_bold(tabled_line) then
		return { is_brief = true, marker = config.references.styler[2]}
	else
		return { is_brief = false, marker = config.references.styler[1]}
	end
end

--- Checks the current line on the index if it is an opening reference.
---@param tabled_line table Table of strings representing current line.
---@param index integer Index of the line to be checked.
---@return boolean True if the reference is opening, false otherwise.
function M.is_opening_reference(tabled_line, index)
	if not tabled_line or not tabled_line[index + 1] or not tabled_line[index] then
		return false;
	end

	return (tabled_line[index]:find("[(]") or tabled_line[index+1]:find("[(]")) and not tabled_line[index+1]:find("[)]")
end

--- Surrounds the reference from the front. If the reference is opened, it is not closed.
---@param tabled_line table Table of strings representing current line.
---@param index integer Index of the word to be checked.
---@param config table Table of options to be used for the conversion to the markdown language.
---@param surround table Table of the surrounding strings.
function M.surround_references(tabled_line, index, config, surround)
	-- Surround the word in brief line.
	if surround.is_brief then
		-- End the brief line formatting if possible.
		if tabled_line[index-1] then
			tabled_line[index-1] = tabled_line[index-1] .. config.line.styler
		end
		-- Start the reference formatting.
		tabled_line[index] = surround.marker .. tabled_line[index+1]

		-- End the reference formatting and start the brief line formatting if possible.
		if tabled_line[index+2] and not surround.openedReference then
			tabled_line[index] = tabled_line[index] .. surround.marker
			tabled_line[index+2] = config.line.styler .. tabled_line[index+2]

		elseif tabled_line[index+2] then
			-- The reference is opened so we don't add ending reference.

		else
			tabled_line[index] = string.sub(tabled_line[index], 1, #tabled_line[index]-2) .. surround.marker
			if tabled_line[index]:find('[)]') then
				surround.openedReference = false
			end
		end

	-- Surround the word in non-brief line.
	else
		if not tabled_line or not tabled_line[index + 1] or not tabled_line[index] then
			return;
		end

		tabled_line[index] = surround.marker .. tabled_line[index+1]

		-- End the reference formatting and start the brief line formatting if possible.
		if not surround.openedReference then
			tabled_line[index] = tabled_line[index] .. surround.marker
		end
	end
end

--- Close the opened reference if it is opened.
---@param tabled_line table Table of strings representing current line.
---@param index integer Index of the word to be checked.
---@param config table Table of options to be used for the conversion to the markdown language.
---@param surround table Table of the surrounding strings.
function M.close_opened_references(tabled_line, index, config, surround)
	if surround.openedReference and tabled_line[index]:find("[)]") then
		if surround.is_brief then
			if tabled_line[index+1] then
				tabled_line[index] = tabled_line[index] .. surround.marker
				tabled_line[index+1] = config.line.styler .. tabled_line[index+1]
			else
				tabled_line[index] = string.sub(tabled_line[index], 1, #tabled_line[index]-2) .. surround.marker
			end

		else
			tabled_line[index] = tabled_line[index] .. surround.marker

		end
		surround.openedReference = false
	end
end

--- Detects the HTML style hyperlinks in the line and converts them to markdown.
---@param tabled_line table Line from the hover message split into words.
---@param word string Word to be checked.
---@param index integer Index of the word from the @c tabled_line to be checked.
function M.detect_hyper_links(tabled_line, word, index)
	if word ~= "\\<a" then
		return
	end

	table.remove(tabled_line, index)

	word = tabled_line[index]
	local whole_link = vim.split(word, "\"", {trimempty = true})
	local link = whole_link[2]
	local styler = require("pretty_hover").get_config().line.styler

	-- The link is not closed in the same part of the line separated by the space.
	if word:sub(1,4) == "href" and word:match("\\</a>") then
		-- Handle the case of the link being the last word in the line.
		styler = word:match("\\</a>" .. styler) ~= nil and styler or ""
		local link_text = whole_link[3]:match("([%w_:.]+)\\</a>") or link
		tabled_line[index] = "[" .. link_text  .. "](" .. link .. ")" .. styler

	-- The link is closed in the next part of the line.
	elseif word:sub(1,4) == "href" then
		local link_text = whole_link[3]:sub(2)
		table.remove(tabled_line, index)

		-- Accumulate all the words until the closing tag.
		while not tabled_line[index]:match("\\</a>") do
			link_text = link_text .. " " .. tabled_line[index]
			table.remove(tabled_line, index)
		end

		-- The last word may be a space or just the closing tag.
		local final_word = tabled_line[index]:match("(%w+)\\</a>") or ""
		link_text = link_text .. " " .. final_word

		-- Handle the case of the link being the last word in the line.
		styler = tabled_line[index]:match("\\</a>" .. styler) ~= nil and styler or ""

		tabled_line[index] = "[" .. link_text  .. "](" .. link .. ")" ..styler
	end
end

--- Remove escaping sequence before every character in the escapees string.
--- The used escapees are '*' by default. Be sure that adding character to the string will not break
--- the highlighting. e.g. adding _ will not do harm in words like __foo but in workds like __foo__.
--- The second one will be highlighted as bold. The first one will be shortened to _foo.
---
--- @param tabled_line table Line from the hover message split into words.
--- @param escapees table? Characters to be removed from the escape sequence.
local function remove_excape_characters(tabled_line, escapees)
	escapees = vim.tbl_deep_extend("force", { "*" }, escapees or {})
	local to_escape = table.concat(escapees)

	for index, word in ipairs(tabled_line) do
		tabled_line[index] = word:gsub("\\([" .. to_escape .. "])", "%1")
	end
end

--- Converts all the references to markdown text.
---@param tabled_line table Words to be checked.
---@param config table Table of options to be used for the conversion to the markdown language.
---@return table Converted line to markdown.
function M.check_line_for_references(tabled_line, config)
	local surround = M.get_surround_string(tabled_line, config)
	surround.openedReference = false

	remove_excape_characters(tabled_line)

	for index, word in ipairs(tabled_line) do
		if util.tbl_contains(config.references.detect, word) then
			-- Handle the parenthesis surrounding the reference.
			if tabled_line[index]:sub(1,1) == "(" then
				tabled_line[index+1] = "(" .. tabled_line[index+1]
			end

			if M.is_opening_reference(tabled_line, index) then
				surround.openedReference = true
			end

			M.surround_references(tabled_line, index, config, surround)

			table.remove(tabled_line, index + 1)
		end

		M.close_opened_references(tabled_line, index, config, surround)
		M.detect_hyper_links(tabled_line, word, index)

		-- We cannot use `word` because it will change also the hyperlinks.
		tabled_line[index] = tabled_line[index]:gsub("\\(<%w+)", "%1")
	end

	return tabled_line
end

return M
Download .txt
gitextract_zbixqniw/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── bug_report.md
│       └── feature_request.md
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── examples/
│   └── parsing.lua
└── lua/
    └── pretty_hover/
        ├── config.lua
        ├── core/
        │   ├── compatibility.lua
        │   └── util.lua
        ├── highlight.lua
        ├── init.lua
        ├── local_request.lua
        ├── number.lua
        └── parser/
            ├── init.lua
            ├── parser.lua
            └── references.lua
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (61K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 834,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 595,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
  },
  {
    "path": ".gitignore",
    "chars": 21,
    "preview": "lua/.luarc.json\ntest\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1232,
    "preview": "Anybody is welcome to contribute to this project. If you have any idea, how to improve any\nfeatures or you have an idea "
  },
  {
    "path": "LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2023 Filip Lobpreis\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "README.md",
    "chars": 8507,
    "preview": "<h1 align=\"center\">\npretty_hover\n</h1>\n\n<p align=\"center\">\n<a href=\"https://github.com/Fildo7525/pretty_hover/stargazers"
  },
  {
    "path": "examples/parsing.lua",
    "chars": 2745,
    "preview": "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 q"
  },
  {
    "path": "lua/pretty_hover/config.lua",
    "chars": 1897,
    "preview": "---@class PrettyHoverConfig\nlocal M = {\n\t_config = {},\n\n\theader = {\n\t\tdetect = {\"[\\\\@]class\"},\n\t\tstyler = '###',\n\t},\n\tli"
  },
  {
    "path": "lua/pretty_hover/core/compatibility.lua",
    "chars": 532,
    "preview": "local M = {}\n\n--- Function that encapsulates the changes in nvim api for getting the active clients.\n---\n--- @return tab"
  },
  {
    "path": "lua/pretty_hover/core/util.lua",
    "chars": 6654,
    "preview": "local api = vim.api\nlocal hl = require(\"pretty_hover.highlight\")\nlocal hover_ns = api.nvim_create_namespace('pretty_hove"
  },
  {
    "path": "lua/pretty_hover/highlight.lua",
    "chars": 3076,
    "preview": "local M = {}\n\nlocal compatibility = require \"pretty_hover.core.compatibility\"\n\nlocal api = vim.api\n\n--- Convert HEX colo"
  },
  {
    "path": "lua/pretty_hover/init.lua",
    "chars": 1513,
    "preview": "local api = vim.api\nlocal cfg = require(\"pretty_hover.config\")\nlocal h_util = require(\"pretty_hover.core.util\")\nlocal lo"
  },
  {
    "path": "lua/pretty_hover/local_request.lua",
    "chars": 5659,
    "preview": "local api = vim.api\nlocal lsp = vim.lsp\nlocal util = vim.lsp.util\nlocal hover_ns = api.nvim_create_namespace('pretty_hov"
  },
  {
    "path": "lua/pretty_hover/number.lua",
    "chars": 3842,
    "preview": "local M = {}\n\n--- Convert the input number to the specified base.\n--- @param num number Number to be converted.\n--- @par"
  },
  {
    "path": "lua/pretty_hover/parser/init.lua",
    "chars": 3756,
    "preview": "local M = {}\r\n\r\nlocal parser = require(\"pretty_hover.parser.parser\")\r\n\r\n---@class ParserOutput\r\n---@field public text ta"
  },
  {
    "path": "lua/pretty_hover/parser/parser.lua",
    "chars": 5675,
    "preview": "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\tte"
  },
  {
    "path": "lua/pretty_hover/parser/references.lua",
    "chars": 8067,
    "preview": "local M = {}\n\nlocal util = require(\"pretty_hover.core.util\")\n\n--- Detect if the check line is already in bold.\n---@param"
  }
]

About this extraction

This page contains the full source code of the Fildo7525/pretty_hover GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (54.4 KB), approximately 15.0k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!