Skip to content

Commit

Permalink
Add support for user defined handlers
Browse files Browse the repository at this point in the history
# Details

Allow users to specify handlers for whatever language they want
using the `custom_handlers` configuration parameter.

As an implementation it is meant to give users the same ability
as the native renderers for `markdown`, `markdown_inline` and
`latex`. Convenience methods are not provided and users are
expected to use the vim api directly.

Provide an example as well as some details in the README.

By integrating with this plugin the `extmarks` set by the user
will behave identically to the ones created by the plugin. All
validation on buffers, windows and state also works as expected.
  • Loading branch information
MeanderingProgrammer committed May 31, 2024
1 parent d4b63e2 commit 473e48d
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 31 deletions.
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Plugin to improve viewing Markdown files in Neovim
- Basic support for `LaTeX` if `pylatexenc` is installed on system
- Disable rendering when file is larger than provided value
- Support for [callouts](https://github.com/orgs/community/discussions/16925)
- Support custom handlers which are ran identically to native handlers

# Dependencies

Expand Down Expand Up @@ -149,6 +150,9 @@ require('render-markdown').setup({
-- normal: renders the rows of tables
-- none: disables rendering, use this if you prefer having cell highlights
table_style = 'full',
-- Mapping from treesitter language to user defined handlers
-- See 'Custom Handlers' section for more info
custom_handlers = {},
-- Define the highlight groups to use when rendering various components
highlights = {
heading = {
Expand Down Expand Up @@ -204,6 +208,73 @@ require('render-markdown').setup({

- Function can also be accessed directly through `require('render-markdown').toggle()`

# Custom Handlers

Custom handlers allow users to integrate custom rendering for either unsupported
languages or to override the native implementations. This can also be used to
disable a native language, as custom handlers have priority.

For example disabling the `LaTeX` handler can be done with:

```lua
require('render-markdown').setup({
custom_handlers = {
latex = { render = function() end },
},
}
```

Each handler must conform to the following interface:

```lua
---@class render.md.Handler
---@field public render fun(namespace: integer, root: TSNode, buf: integer)
```

The `render` function parameters are:

- `namespace`: The id that this plugin interacts with when setting and clearing `extmark`s
- `root`: The root treesitter node for the specified language
- `buf`: The buffer containing the root node

Custom handlers are ran identically to native ones, so by writing custom `extmark`s
(see :h nvim_buf_set_extmark()) to the provided `namespace` this plugin will handle
clearing the `extmark`s on mode changes as well as re-calling the `render` function
when needed.

This is a high level interface, as such creating, parsing, and iterating through
a treesitter query is entirely up to the user if the functionality they want needs
this. We do not provide any convenience functions, but you are more than welcome
to use patterns from the native handlers.

## More Complex Example

Lets say for `python` we want to highlight lines with function definitions.

```lua
-- Parse query outside of the render function to avoid doing it for each call
local query = vim.treesitter.query.parse('python', '(function_definition) @def')
local function render_python(namespace, root, buf)
for id, node in query:iter_captures(root, buf) do
local capture = query.captures[id]
local start_row, _, _, _ = node:range()
if capture == 'def' then
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, 0, {
end_row = start_row + 1,
end_col = 0,
hl_group = 'DiffDelete',
hl_eol = true,
})
end
end
end
require('render-markdown').setup({
custom_handlers = {
python = { render = render_python },
},
}
```

# Purpose

There are many existing markdown rendering plugins in the Neovim ecosystem. However,
Expand Down
90 changes: 83 additions & 7 deletions doc/render-markdown.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
*render-markdown.txt* For 0.10.0 Last change: 2024 May 22
*render-markdown.txt* For 0.10.0 Last change: 2024 May 31

==============================================================================
Table of Contents *render-markdown-table-of-contents*
Expand All @@ -11,12 +11,14 @@ Table of Contents *render-markdown-table-of-contents*
- packer.nvim |render-markdown-install-packer.nvim|
5. Setup |render-markdown-setup|
6. Commands |render-markdown-commands|
7. Purpose |render-markdown-purpose|
8. Markdown Ecosystem |render-markdown-markdown-ecosystem|
7. Custom Handlers |render-markdown-custom-handlers|
- More Complex Example|render-markdown-custom-handlers-more-complex-example|
8. Purpose |render-markdown-purpose|
9. Markdown Ecosystem |render-markdown-markdown-ecosystem|
- Render in Neovim |render-markdown-markdown-ecosystem-render-in-neovim|
- Render in Browser |render-markdown-markdown-ecosystem-render-in-browser|
- Orthogonal |render-markdown-markdown-ecosystem-orthogonal|
9. Links |render-markdown-links|
10. Links |render-markdown-links|

==============================================================================
1. markdown.nvim *render-markdown-markdown.nvim*
Expand All @@ -41,6 +43,7 @@ Plugin to improve viewing Markdown files in Neovim
- Basic support for `LaTeX` if `pylatexenc` is installed on system
- Disable rendering when file is larger than provided value
- Support for callouts <https://github.com/orgs/community/discussions/16925>
- Support custom handlers which are ran identically to native handlers


==============================================================================
Expand Down Expand Up @@ -178,6 +181,9 @@ modified by the user.
-- normal: renders the rows of tables
-- none: disables rendering, use this if you prefer having cell highlights
table_style = 'full',
-- Mapping from treesitter language to user defined handlers
-- See 'Custom Handlers' section for more info
custom_handlers = {},
-- Define the highlight groups to use when rendering various components
highlights = {
heading = {
Expand Down Expand Up @@ -237,7 +243,77 @@ modified by the user.


==============================================================================
7. Purpose *render-markdown-purpose*
7. Custom Handlers *render-markdown-custom-handlers*

Custom handlers allow users to integrate custom rendering for either
unsupported languages or to override the native implementations. This can also
be used to disable a native language, as custom handlers have priority.

For example disabling the `LaTeX` handler can be done with:

>lua
require('render-markdown').setup({
custom_handlers = {
latex = { render = function() end },
},
}
<

Each handler must conform to the following interface:

>lua
---@class render.md.Handler
---@field public render fun(namespace: integer, root: TSNode, buf: integer)
<

The `render` function parameters are:

- `namespace`: The id that this plugin interacts with when setting and clearing `extmark`s
- `root`: The root treesitter node for the specified language
- `buf`: The buffer containing the root node

Custom handlers are ran identically to native ones, so by writing custom
`extmark`s (see :h nvim_buf_set_extmark()) to the provided `namespace` this
plugin will handle clearing the `extmark`s on mode changes as well as
re-calling the `render` function when needed.

This is a high level interface, as such creating, parsing, and iterating
through a treesitter query is entirely up to the user if the functionality they
want needs this. We do not provide any convenience functions, but you are more
than welcome to use patterns from the native handlers.


MORE COMPLEX EXAMPLE *render-markdown-custom-handlers-more-complex-example*

Lets say for `python` we want to highlight lines with function definitions.

>lua
-- Parse query outside of the render function to avoid doing it for each call
local query = vim.treesitter.query.parse('python', '(function_definition) @def')
local function render_python(namespace, root, buf)
for id, node in query:iter_captures(root, buf) do
local capture = query.captures[id]
local start_row, _, _, _ = node:range()
if capture == 'def' then
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, 0, {
end_row = start_row + 1,
end_col = 0,
hl_group = 'DiffDelete',
hl_eol = true,
})
end
end
end
require('render-markdown').setup({
custom_handlers = {
python = { render = render_python },
},
}
<


==============================================================================
8. Purpose *render-markdown-purpose*

There are many existing markdown rendering plugins in the Neovim ecosystem.
However, most of these rely on syncing a separate browser window with the
Expand All @@ -254,7 +330,7 @@ this plugin.


==============================================================================
8. Markdown Ecosystem *render-markdown-markdown-ecosystem*
9. Markdown Ecosystem *render-markdown-markdown-ecosystem*

There are many `markdown` plugins that specialize in different aspects of
interacting with `markdown` files. This plugin specializes in rendering the
Expand Down Expand Up @@ -297,7 +373,7 @@ also have no issues running alongside this plugin.
specific keybindings for interacting with `markdown` files

==============================================================================
9. Links *render-markdown-links*
10. Links *render-markdown-links*

1. *Demo*: demo/demo.gif

Expand Down
2 changes: 1 addition & 1 deletion lua/render-markdown/handler/latex.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ local cache = {

local M = {}

---@param namespace number
---@param namespace integer
---@param root TSNode
---@param buf integer
M.render = function(namespace, root, buf)
Expand Down
2 changes: 1 addition & 1 deletion lua/render-markdown/handler/markdown.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ local util = require('render-markdown.util')

local M = {}

---@param namespace number
---@param namespace integer
---@param root TSNode
---@param buf integer
M.render = function(namespace, root, buf)
Expand Down
2 changes: 1 addition & 1 deletion lua/render-markdown/handler/markdown_inline.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ local state = require('render-markdown.state')

local M = {}

---@param namespace number
---@param namespace integer
---@param root TSNode
---@param buf integer
M.render = function(namespace, root, buf)
Expand Down
5 changes: 4 additions & 1 deletion lua/render-markdown/health.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ local function check_keys(t1, t2, path)
elseif type(v1) ~= type(v2) then
table.insert(errors, string.format('Invalid type: %s, expected %s but found %s', key, type(v1), type(v2)))
elseif type(v1) == 'table' and type(v2) == 'table' then
vim.list_extend(errors, check_keys(v1, v2, key_path))
-- Some tables are meant to have unrestricted keys
if k ~= 'custom_handlers' then
vim.list_extend(errors, check_keys(v1, v2, key_path))
end
end
end
return errors
Expand Down
7 changes: 7 additions & 0 deletions lua/render-markdown/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ local M = {}
---@field public quote? string
---@field public callout? render.md.UserCallout

---@class render.md.Handler
---@field public render fun(namespace: integer, root: TSNode, buf: integer)

---@class render.md.UserConceal
---@field public default? integer
---@field public rendered? integer
Expand All @@ -58,6 +61,7 @@ local M = {}
---@field public callout? render.md.UserCallout
---@field public conceal? render.md.UserConceal
---@field public table_style? 'full'|'normal'|'none'
---@field public custom_handlers? table<string, render.md.Handler>
---@field public highlights? render.md.UserHighlights

---@type render.md.Config
Expand Down Expand Up @@ -147,6 +151,9 @@ M.default_config = {
-- normal: renders the rows of tables
-- none: disables rendering, use this if you prefer having cell highlights
table_style = 'full',
-- Mapping from treesitter language to user defined handlers
-- See 'Custom Handlers' section for more info
custom_handlers = {},
-- Define the highlight groups to use when rendering various components
highlights = {
heading = {
Expand Down
1 change: 1 addition & 0 deletions lua/render-markdown/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,5 @@
---@field public callout render.md.Callout
---@field public conceal render.md.Conceal
---@field public table_style 'full'|'normal'|'none'
---@field public custom_handlers table<string, render.md.Handler>
---@field public highlights render.md.Highlights
20 changes: 13 additions & 7 deletions lua/render-markdown/ui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,20 @@ M.refresh = function(buf)
parser:for_each_tree(function(tree, language_tree)
local language = language_tree:lang()
logger.debug({ language = language })
if language == 'markdown' then
markdown.render(M.namespace, tree:root(), buf)
elseif language == 'markdown_inline' then
markdown_inline.render(M.namespace, tree:root(), buf)
elseif language == 'latex' then
latex.render(M.namespace, tree:root(), buf)
local user_handler = state.config.custom_handlers[language]
if user_handler == nil then
if language == 'markdown' then
markdown.render(M.namespace, tree:root(), buf)
elseif language == 'markdown_inline' then
markdown_inline.render(M.namespace, tree:root(), buf)
elseif language == 'latex' then
latex.render(M.namespace, tree:root(), buf)
else
logger.debug('No handler found')
end
else
logger.debug('No handler found')
logger.debug('Using user defined handler')
user_handler.render(M.namespace, tree:root(), buf)
end
end)
logger.flush()
Expand Down
42 changes: 29 additions & 13 deletions scripts/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,34 @@ def main() -> None:


def update_types(init_file: Path) -> None:
lines: list[str] = []
# Group comments into class + fields
lua_classes: list[list[str]] = []
current_class: list[str] = []
for comment in get_comments(init_file):
comment_type: str = comment.split()[0].split("@")[-1]
if comment_type in ["class", "field"]:
comment = comment.replace("User", "")
if comment_type == "field":
assert "?" in comment, f"All fields must be optional: {comment}"
comment = comment.replace("?", "")
if comment_type == "class" and len(lines) > 0:
lines.append("")
lines.append(comment)
lines.append("")
if comment_type == "class":
if len(current_class) > 0:
lua_classes.append(current_class)
current_class = [comment]
elif comment_type == "field":
current_class.append(comment)
lua_classes.append(current_class)

# Generate lines that get written to types.lua
lines: list[str] = []
for lua_class in lua_classes:
name, fields = lua_class[0], lua_class[1:]
if "User" in name:
# User classes are expected to have optional fields
lines.append(name.replace("User", ""))
for field in fields:
assert "?" in field, f"User fields must be optional: {field}"
lines.append(field.replace("User", "").replace("?", ""))
lines.append("")
else:
# Non user classes are expected to have mandatory fields
for field in fields:
assert "?" not in field, f"Non user fields must be mandatory: {field}"

types_file: Path = Path("lua/render-markdown/types.lua")
types_file.write_text("\n".join(lines))
Expand Down Expand Up @@ -62,9 +78,9 @@ def get_default_config(file: Path) -> str:
def get_readme_config(file: Path) -> str:
query = "(code_fence_content) @content"
code_blocks = ts_query(file, query, "content")
query_code_blocks = [code for code in code_blocks if "query" in code]
assert len(query_code_blocks) == 1
return query_code_blocks[0]
code_blocks = [code for code in code_blocks if "enabled" in code]
assert len(code_blocks) == 1
return code_blocks[0]


def ts_query(file: Path, query_string: str, target: str) -> list[str]:
Expand Down

0 comments on commit 473e48d

Please sign in to comment.