Skip to content

Commit ff3e8e3

Browse files
Add support for custom checkbox states
# Details Proposed in: #42 Take a similar approach as callouts and use shortcut_links in list items as custom states of a checkbox. Configurable through checkbox -> custom. Each value must provide text, icon, and highlight.
1 parent 15ee2d9 commit ff3e8e3

File tree

13 files changed

+165
-20
lines changed

13 files changed

+165
-20
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Plugin to improve viewing Markdown files in Neovim
2727
- Block quotes: replace leading `>` with provided icon
2828
- Tables: replace border characters, does NOT automatically align
2929
- [Callouts](https://github.com/orgs/community/discussions/16925)
30+
- Custom checkbox states, function similar to `callouts`
3031
- `LaTeX` blocks: renders formulas if `latex` parser and `pylatexenc` are installed
3132
- Disable rendering when file is larger than provided value
3233
- Support custom handlers which are ran identically to builtin handlers
@@ -171,6 +172,10 @@ require('render-markdown').setup({
171172
unchecked = '󰄱 ',
172173
-- Character that will replace the [x] in checked checkboxes
173174
checked = '󰱒 ',
175+
-- Specify custom checkboxes, must be surrounded in square brackets
176+
custom = {
177+
todo = { text = '[-]', icon = '󰥔 ', highlight = '@markup.raw' },
178+
},
174179
},
175180
-- Character that will replace the > at the start of block quotes
176181
quote = '',
@@ -305,6 +310,17 @@ is used to hide the character.
305310

306311
The checked `[ ]` & unchecked `[x]` are directly replaced with these values.
307312

313+
Additionally `custom` states can be specified, an example of this is provided
314+
with: `todo = { text = '[-]', icon = '󰥔 ', highlight = '@markup.raw' }`.
315+
316+
This requires neovim >= `0.10.0` since it relies on `inline` extmarks.
317+
318+
The key in the `custom` table is unused. The parts of the value are:
319+
320+
- `text`: matched against the raw text of a `shortcut_link`, in the same way as `callouts`
321+
- `icon`: replaces the `text` value when rendering
322+
- `highlight`: color used for `icon`
323+
308324
### quote
309325

310326
Replaces the `|` character in front of `block_quotes`.

demo/box_dash_quote.gif

10.5 KB
Loading

demo/box_dash_quote.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
- [ ] Unchecked Checkbox
44
- [x] Checked Checkbox
5+
- [-] Todo Checkbox
56

67
---
78

doc/render-markdown.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Plugin to improve viewing Markdown files in Neovim
5050
- Block quotes: replace leading `>` with provided icon
5151
- Tables: replace border characters, does NOT automatically align
5252
- Callouts <https://github.com/orgs/community/discussions/16925>
53+
- Custom checkbox states, function similar to `callouts`
5354
- `LaTeX` blocks: renders formulas if `latex` parser and `pylatexenc` are installed
5455
- Disable rendering when file is larger than provided value
5556
- Support custom handlers which are ran identically to builtin handlers
@@ -203,6 +204,10 @@ Full Default Configuration ~
203204
unchecked = '󰄱 ',
204205
-- Character that will replace the [x] in checked checkboxes
205206
checked = '󰱒 ',
207+
-- Specify custom checkboxes, must be surrounded in square brackets
208+
custom = {
209+
todo = { text = '[-]', icon = '󰥔 ', highlight = '@markup.raw' },
210+
},
206211
},
207212
-- Character that will replace the > at the start of block quotes
208213
quote = '▋',
@@ -342,6 +347,17 @@ CHECKBOX ~
342347

343348
The checked `[ ]` & unchecked `[x]` are directly replaced with these values.
344349

350+
Additionally `custom` states can be specified, an example of this is provided
351+
with: `todo = { text = '[-]', icon = '󰥔 ', highlight = '@markup.raw' }`.
352+
353+
This requires neovim >= `0.10.0` since it relies on `inline` extmarks.
354+
355+
The key in the `custom` table is unused. The parts of the value are:
356+
357+
- `text`: matched against the raw text of a `shortcut_link`, in the same way as `callouts`
358+
- `icon`: replaces the `text` value when rendering
359+
- `highlight`: color used for `icon`
360+
345361

346362
QUOTE ~
347363

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
local state = require('render-markdown.state')
2+
3+
local M = {}
4+
5+
---@param value string
6+
---@return render.md.CustomCheckbox?
7+
M.get_exact = function(value)
8+
for _, checkbox in pairs(state.config.checkbox.custom) do
9+
---@diagnostic disable-next-line: undefined-field
10+
if checkbox.text == value then
11+
return checkbox
12+
end
13+
end
14+
return nil
15+
end
16+
17+
---@param value string
18+
---@return render.md.CustomCheckbox?
19+
M.get_starts = function(value)
20+
for _, checkbox in pairs(state.config.checkbox.custom) do
21+
---@diagnostic disable-next-line: undefined-field
22+
if vim.startswith(value, checkbox.text) then
23+
return checkbox
24+
end
25+
end
26+
return nil
27+
end
28+
29+
return M

lua/render-markdown/handler/markdown.lua

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
local callout = require('render-markdown.callout')
2+
local custom_checkbox = require('render-markdown.custom_checkbox')
23
local icons = require('render-markdown.icons')
34
local list = require('render-markdown.list')
45
local logger = require('render-markdown.logger')
56
local state = require('render-markdown.state')
7+
local str = require('render-markdown.str')
68
local ts = require('render-markdown.ts')
79
local util = require('render-markdown.util')
810

@@ -39,7 +41,7 @@ M.render_node = function(namespace, buf, capture, node)
3941
local background = list.clamp_last(highlights.heading.backgrounds, level)
4042
local foreground = list.clamp_last(highlights.heading.foregrounds, level)
4143

42-
local heading_text = { string.rep(' ', padding) .. heading, { foreground, background } }
44+
local heading_text = { str.pad(heading, padding), { foreground, background } }
4345
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, 0, {
4446
end_row = end_row + 1,
4547
end_col = 0,
@@ -71,7 +73,7 @@ M.render_node = function(namespace, buf, capture, node)
7173
return
7274
end
7375
-- Requires inline extmarks
74-
if vim.fn.has('nvim-0.10') == 0 then
76+
if not util.has_10 then
7577
return
7678
end
7779

@@ -86,7 +88,7 @@ M.render_node = function(namespace, buf, capture, node)
8688
virt_text_pos = 'inline',
8789
})
8890
elseif capture == 'list_marker' then
89-
if ts.sibling(node, { 'task_list_marker_unchecked', 'task_list_marker_checked' }) ~= nil then
91+
if M.sibling_checkbox(buf, node) then
9092
-- Hide the list marker for checkboxes rather than replacing with a bullet point
9193
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, start_col, {
9294
end_row = end_row,
@@ -101,7 +103,7 @@ M.render_node = function(namespace, buf, capture, node)
101103
local level = ts.level_in_section(node, 'list')
102104
local bullet = list.cycle(state.config.bullets, level)
103105

104-
local list_marker_text = { string.rep(' ', leading_spaces or 0) .. bullet, highlights.bullet }
106+
local list_marker_text = { str.pad(bullet, leading_spaces), highlights.bullet }
105107
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, start_col, {
106108
end_row = end_row,
107109
end_col = end_col,
@@ -118,8 +120,7 @@ M.render_node = function(namespace, buf, capture, node)
118120
local highlight = highlights.quote
119121
local quote = ts.parent_in_section(node, 'block_quote')
120122
if quote ~= nil then
121-
local quote_value = vim.treesitter.get_node_text(quote, buf)
122-
local key = callout.get_key_contains(quote_value)
123+
local key = callout.get_key_contains(vim.treesitter.get_node_text(quote, buf))
123124
if key ~= nil then
124125
highlight = highlights.callout[key]
125126
end
@@ -139,17 +140,14 @@ M.render_node = function(namespace, buf, capture, node)
139140
checkbox = state.config.checkbox.checked
140141
highlight = highlights.checkbox.checked
141142
end
142-
local padding = vim.fn.strdisplaywidth(value) - vim.fn.strdisplaywidth(checkbox)
143143

144-
if padding >= 0 then
145-
local checkbox_text = { string.rep(' ', padding) .. checkbox, highlight }
146-
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, start_col, {
147-
end_row = end_row,
148-
end_col = end_col,
149-
virt_text = { checkbox_text },
150-
virt_text_pos = 'overlay',
151-
})
152-
end
144+
local checkbox_text = { str.pad_to(value, checkbox), highlight }
145+
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, start_col, {
146+
end_row = end_row,
147+
end_col = end_col,
148+
virt_text = { checkbox_text },
149+
virt_text_pos = 'overlay',
150+
})
153151
elseif capture == 'table' then
154152
if state.config.table_style ~= 'full' then
155153
return
@@ -251,4 +249,21 @@ M.render_node = function(namespace, buf, capture, node)
251249
end
252250
end
253251

252+
---@param buf integer
253+
---@param node TSNode
254+
---@return boolean
255+
M.sibling_checkbox = function(buf, node)
256+
if ts.sibling(node, { 'task_list_marker_unchecked', 'task_list_marker_checked' }) ~= nil then
257+
return true
258+
end
259+
local paragraph = ts.sibling(node, { 'paragraph' })
260+
if paragraph == nil then
261+
return false
262+
end
263+
if custom_checkbox.get_starts(vim.treesitter.get_node_text(paragraph, buf)) ~= nil then
264+
return true
265+
end
266+
return false
267+
end
268+
254269
return M

lua/render-markdown/handler/markdown_inline.lua

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
local callout = require('render-markdown.callout')
2+
local custom_checkbox = require('render-markdown.custom_checkbox')
23
local logger = require('render-markdown.logger')
34
local state = require('render-markdown.state')
5+
local str = require('render-markdown.str')
6+
local util = require('render-markdown.util')
47

58
local M = {}
69

@@ -40,6 +43,25 @@ M.render_node = function(namespace, buf, capture, node)
4043
virt_text = { callout_text },
4144
virt_text_pos = 'overlay',
4245
})
46+
else
47+
-- Requires inline extmarks
48+
if not util.has_10 then
49+
return
50+
end
51+
52+
local checkbox = custom_checkbox.get_exact(value)
53+
if checkbox == nil then
54+
return
55+
end
56+
57+
local checkbox_text = { str.pad_to(value, checkbox.icon), checkbox.highlight }
58+
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, start_col, {
59+
end_row = end_row,
60+
end_col = end_col,
61+
virt_text = { checkbox_text },
62+
virt_text_pos = 'inline',
63+
conceal = '',
64+
})
4365
end
4466
else
4567
-- Should only get here if user provides custom capture, currently unhandled

lua/render-markdown/health.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ function M.check_keys(t1, t2, path)
8585
table.insert(errors, string.format('Invalid type: %s, expected %s, found %s', key, type(v1), type(v2)))
8686
elseif type(v1) == 'table' and type(v2) == 'table' then
8787
-- Some tables are meant to have unrestricted keys
88-
if not vim.tbl_contains({ 'win_options', 'custom_handlers' }, k) then
88+
if not vim.tbl_contains({ 'win_options', 'custom', 'custom_handlers' }, k) then
8989
vim.list_extend(errors, M.check_keys(v1, v2, key_path))
9090
end
9191
end

lua/render-markdown/init.lua

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,15 @@ local M = {}
4141
---@field public default any
4242
---@field public rendered any
4343

44+
---@class render.md.CustomCheckbox
45+
---@field public text string
46+
---@field public icon string
47+
---@field public highlight string
48+
4449
---@class render.md.UserCheckbox
4550
---@field public unchecked? string
4651
---@field public checked? string
52+
---@field public custom? table<string, render.md.CustomCheckbox[]>
4753

4854
---@class render.md.UserConfig
4955
---@field public start_enabled? boolean
@@ -144,6 +150,10 @@ M.default_config = {
144150
unchecked = '󰄱 ',
145151
-- Character that will replace the [x] in checked checkboxes
146152
checked = '󰱒 ',
153+
-- Specify custom checkboxes, must be surrounded in square brackets
154+
custom = {
155+
todo = { text = '[-]', icon = '󰥔 ', highlight = '@markup.raw' },
156+
},
147157
},
148158
-- Character that will replace the > at the start of block quotes
149159
quote = '',

lua/render-markdown/str.lua

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
local M = {}
2+
3+
---@param value string
4+
---@param s string
5+
---@return string
6+
M.pad_to = function(value, s)
7+
local padding = vim.fn.strdisplaywidth(value) - vim.fn.strdisplaywidth(s)
8+
return M.pad(s, padding)
9+
end
10+
11+
---@param s string
12+
---@param padding integer?
13+
---@return string
14+
M.pad = function(s, padding)
15+
return string.rep(' ', padding or 0) .. s
16+
end
17+
18+
return M

0 commit comments

Comments
 (0)