Skip to content

Commit b4016e8

Browse files
fix: ignore callouts outside of quotes and checkboxes outside of lists
## Details Previously `shortcut_links` would be rendered immediately if the contents matched any configured callouts or checkboxes. This isn't exactly harmful, but does mean, for instance, that a checkbox pattern at the start of a block quote will have its icon rendered and a list with a callout will have its icon rendered, which goes against the definition of these concepts. To fix this we now render both callouts and checkboxes through the main `markdown` handler rather than as part of the inline logic. Callouts while rendering block quotes, and checkboxes while rendering list items. This is possible now that we store the nodes along with their configurations inside the context. Handling callouts is straightforward, we copy paste the logic that was in the `shortcut` render for callouts into the `quote` render module and apply the logic to the values stored inside the context if they exist. Handling checkboxes is more complicated since we were already rendering checkboxes in the `markdown` handler and handling part of the rendering in the `bullet` render. Basically the logic ended up being split between `shortcut`, `bullet`, & `checkbox` with shared logic stored in `base` to produce consistent results, yuk. To improve this state and solve the problem a new `list` render implementation was added which gets used in place of where `bullet` is currently. The implementation of it runs the checkbox render, if that returns false (meaning no checkbox is present), then it runs the bullet render. This means the checkbox render no longer runs on checkbox nodes directly but instead on the parent list item, so the checkbox query is removed from the `markdown` handler. The `bullet` part of the rendering remains almost completely unchanged, the only difference is all the checkbox handling like hiding marks is removed and configuration can now happen more in the `setup` method. The `checkbox` render is updated to handle running on a list item, which means finding the node associated with the checkbox (if it exists). From there hide the mark of the list, render the marker (which allows this logic to be removed from base), and highlight the scope. In addition to fixing this problem the logic is now all located in a single place which should make adding features easier. The fallback pattern of list could end up being useful for rendering other things, but currently this is the only instance of diverging logic when rendering the same exact thing, so leaving it as a one off for now. Other changes: - use generic `config` name throughout when getting a specific config from `context`, rather than something specific like `latex` - remove `self.config` from `Base` render fields, instead config must be pulled via `self.context.config`, usually once in each implementation - since the `config` name is no longer being used rename all instances of `self.info` to `self.config`, which no stores the specific configuration of what is being rendered
1 parent 617f9c2 commit b4016e8

File tree

23 files changed

+462
-425
lines changed

23 files changed

+462
-425
lines changed

lua/render-markdown/handler/latex.lua

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ M.cache = {}
1616
---@return render.md.Mark[]
1717
function M.parse(ctx)
1818
local context = Context.get(ctx.buf)
19-
local latex = context.config.latex
20-
if context:skip(latex) then
19+
local config = context.config.latex
20+
if context:skip(config) then
2121
return {}
2222
end
23-
if vim.fn.executable(latex.converter) ~= 1 then
24-
log.add('debug', 'executable not found', latex.converter)
23+
if vim.fn.executable(config.converter) ~= 1 then
24+
log.add('debug', 'executable not found', config.converter)
2525
return {}
2626
end
2727

@@ -30,30 +30,30 @@ function M.parse(ctx)
3030

3131
local raw_expression = M.cache[node.text]
3232
if not raw_expression then
33-
raw_expression = vim.fn.system(latex.converter, node.text)
33+
raw_expression = vim.fn.system(config.converter, node.text)
3434
if vim.v.shell_error == 1 then
35-
log.add('error', latex.converter, raw_expression)
35+
log.add('error', config.converter, raw_expression)
3636
raw_expression = 'error'
3737
end
3838
M.cache[node.text] = raw_expression
3939
end
4040

4141
local expressions = {} ---@type string[]
42-
for _ = 1, latex.top_pad do
42+
for _ = 1, config.top_pad do
4343
expressions[#expressions + 1] = ''
4444
end
4545
for _, expression in ipairs(Str.split(raw_expression, '\n', true)) do
4646
expressions[#expressions + 1] = Str.pad(node.start_col) .. expression
4747
end
48-
for _ = 1, latex.bottom_pad do
48+
for _ = 1, config.bottom_pad do
4949
expressions[#expressions + 1] = ''
5050
end
5151

5252
local lines = Iter.list.map(expressions, function(expression)
53-
return { { expression, latex.highlight } }
53+
return { { expression, config.highlight } }
5454
end)
5555

56-
local above = latex.position == 'above'
56+
local above = config.position == 'above'
5757
local row = above and node.start_row or node.end_row
5858

5959
local marks = Marks.new(context, true)

lua/render-markdown/handler/markdown.lua

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@ function Handler.new(buf)
1616
self.query = ts.parse(
1717
'markdown',
1818
[[
19-
[
20-
(task_list_marker_unchecked)
21-
(task_list_marker_checked)
22-
] @checkbox
23-
2419
(fenced_code_block) @code
2520
2621
[
@@ -48,12 +43,11 @@ function Handler.new(buf)
4843
]]
4944
)
5045
self.renders = {
51-
checkbox = require('render-markdown.render.markdown.checkbox'),
5246
code = require('render-markdown.render.markdown.code'),
5347
dash = require('render-markdown.render.markdown.dash'),
5448
document = require('render-markdown.render.markdown.document'),
5549
heading = require('render-markdown.render.markdown.heading'),
56-
list = require('render-markdown.render.markdown.bullet'),
50+
list = require('render-markdown.render.markdown.list'),
5751
paragraph = require('render-markdown.render.markdown.paragraph'),
5852
quote = require('render-markdown.render.markdown.quote'),
5953
section = require('render-markdown.render.markdown.section'),

lua/render-markdown/health.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ local state = require('render-markdown.state')
55
local M = {}
66

77
---@private
8-
M.version = '8.3.27'
8+
M.version = '8.3.28'
99

1010
function M.check()
1111
M.start('version')

lua/render-markdown/lib/node.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,12 @@ function Node:child(target, row)
175175
return nil
176176
end
177177

178+
---@return render.md.Node?
179+
function Node:scope()
180+
local node = self:child('paragraph')
181+
return node and node:child('inline')
182+
end
183+
178184
---@param callback fun(node: render.md.Node)
179185
function Node:for_each_child(callback)
180186
for node in self.node:iter_children() do

lua/render-markdown/render/base.lua

Lines changed: 7 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
local Indent = require('render-markdown.lib.indent')
2-
local Str = require('render-markdown.lib.str')
32
local colors = require('render-markdown.core.colors')
43

54
---@class render.md.Render
65
---@field protected context render.md.request.Context
7-
---@field protected config render.md.main.Config
86
---@field protected marks render.md.Marks
97
---@field protected node render.md.Node
108
---@field protected setup fun(self: render.md.Render): boolean
@@ -19,7 +17,6 @@ Base.__index = Base
1917
function Base:execute(context, marks, node)
2018
local instance = setmetatable({}, self)
2119
instance.context = context
22-
instance.config = context.config
2320
instance.marks = marks
2421
instance.node = node
2522
if instance:setup() then
@@ -30,6 +27,12 @@ function Base:execute(context, marks, node)
3027
end
3128
end
3229

30+
---@protected
31+
---@return render.md.Line
32+
function Base:line()
33+
return self.context.config:line()
34+
end
35+
3336
---@protected
3437
---@return render.md.Indent
3538
function Base:indent()
@@ -41,7 +44,7 @@ end
4144
---@param text? string
4245
---@param highlight? string
4346
function Base:sign(enabled, text, highlight)
44-
local config = self.config.sign
47+
local config = self.context.config.sign
4548
if not config.enabled or not enabled or not text then
4649
return
4750
end
@@ -55,60 +58,4 @@ function Base:sign(enabled, text, highlight)
5558
})
5659
end
5760

58-
---@protected
59-
---@param icon string
60-
---@param highlight string
61-
---@return boolean
62-
function Base:check_icon(icon, highlight)
63-
local line = self.config:line():text(icon, highlight)
64-
local space = self.context:width(self.node) + 1 - Str.width(icon)
65-
local right_pad = self.config.checkbox.right_pad
66-
if space < 0 then
67-
-- not enough space to fit the icon in-place
68-
return self.marks:over('check_icon', self.node, {
69-
virt_text = line:pad(right_pad):get(),
70-
virt_text_pos = 'inline',
71-
conceal = '',
72-
}, { 0, 0, 0, 1 })
73-
else
74-
local fits = math.min(space, right_pad)
75-
space = space - fits
76-
right_pad = right_pad - fits
77-
local row = self.node.start_row
78-
local start_col = self.node.start_col
79-
local end_col = self.node.end_col + 1
80-
self.marks:add('check_icon', row, start_col, {
81-
end_col = end_col - space,
82-
virt_text = line:pad(fits):get(),
83-
virt_text_pos = 'overlay',
84-
})
85-
if space > 0 then
86-
-- hide extra space after the icon
87-
self.marks:add('check_icon', row, end_col - space, {
88-
end_col = end_col,
89-
conceal = '',
90-
})
91-
end
92-
if right_pad > 0 then
93-
-- add padding
94-
self.marks:add('check_icon', row, end_col, {
95-
virt_text = self.config:line():pad(right_pad):get(),
96-
virt_text_pos = 'inline',
97-
})
98-
end
99-
return true
100-
end
101-
end
102-
103-
---@protected
104-
---@param element render.md.mark.Element
105-
---@param node render.md.Node?
106-
---@param highlight? string
107-
function Base:scope(element, node, highlight)
108-
if not node or not highlight then
109-
return
110-
end
111-
self.marks:over(element, node:child('inline'), { hl_group = highlight })
112-
end
113-
11461
return Base

lua/render-markdown/render/html/comment.lua

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
local Base = require('render-markdown.render.base')
22

33
---@class render.md.render.html.Comment: render.md.Render
4-
---@field private info render.md.html.comment.Config
4+
---@field private config render.md.html.comment.Config
55
local Render = setmetatable({}, Base)
66
Render.__index = Render
77

88
---@protected
99
---@return boolean
1010
function Render:setup()
11-
self.info = self.config.html.comment
12-
if not self.info.conceal then
11+
self.config = self.context.config.html.comment
12+
if not self.config.conceal then
1313
return false
1414
end
1515
return true
@@ -18,9 +18,9 @@ end
1818
---@protected
1919
function Render:run()
2020
self.marks:over(true, self.node, { conceal = '' })
21-
if self.info.text then
21+
if self.config.text then
2222
self.marks:start(true, self.node, {
23-
virt_text = { { self.info.text, self.info.highlight } },
23+
virt_text = { { self.config.text, self.config.highlight } },
2424
virt_text_pos = 'inline',
2525
})
2626
end

lua/render-markdown/render/html/tag.lua

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
local Base = require('render-markdown.render.base')
22

33
---@class render.md.render.html.Tag: render.md.Render
4-
---@field private info render.md.html.Tag
4+
---@field private config render.md.html.Tag
55
local Render = setmetatable({}, Base)
66
Render.__index = Render
77

@@ -16,16 +16,16 @@ function Render:setup()
1616
if not name then
1717
return false
1818
end
19-
self.info = self.config.html.tag[name.text]
20-
return self.info ~= nil
19+
self.config = self.context.config.html.tag[name.text]
20+
return self.config ~= nil
2121
end
2222

2323
---@protected
2424
function Render:run()
2525
self.marks:over(true, self.node:child('start_tag'), { conceal = '' })
2626
self.marks:over(true, self.node:child('end_tag'), { conceal = '' })
2727
self.marks:start(false, self.node, {
28-
virt_text = { { self.info.icon, self.info.highlight } },
28+
virt_text = { { self.config.icon, self.config.highlight } },
2929
virt_text_pos = 'inline',
3030
})
3131
end

lua/render-markdown/render/inline/code.lua

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,26 @@ local Base = require('render-markdown.render.base')
22
local colors = require('render-markdown.core.colors')
33

44
---@class render.md.render.inline.Code: render.md.Render
5-
---@field private info render.md.code.Config
5+
---@field private config render.md.code.Config
66
local Render = setmetatable({}, Base)
77
Render.__index = Render
88

99
---@protected
1010
---@return boolean
1111
function Render:setup()
12-
self.info = self.config.code
13-
if self.context:skip(self.info) then
12+
self.config = self.context.config.code
13+
if self.context:skip(self.config) then
1414
return false
1515
end
16-
if not vim.tbl_contains({ 'normal', 'full' }, self.info.style) then
16+
if not vim.tbl_contains({ 'normal', 'full' }, self.config.style) then
1717
return false
1818
end
1919
return true
2020
end
2121

2222
---@protected
2323
function Render:run()
24-
local highlight = self.info.highlight_inline
24+
local highlight = self.config.highlight_inline
2525
self.marks:over('code_background', self.node, { hl_group = highlight })
2626
self:padding(highlight, true)
2727
self:padding(highlight, false)
@@ -31,14 +31,14 @@ end
3131
---@param highlight string
3232
---@param left boolean
3333
function Render:padding(highlight, left)
34-
local line = self.config:line()
34+
local line = self:line()
3535
local icon_highlight = colors.bg_as_fg(highlight)
3636
if left then
37-
line:text(self.info.inline_left, icon_highlight)
38-
line:pad(self.info.inline_pad, highlight)
37+
line:text(self.config.inline_left, icon_highlight)
38+
line:pad(self.config.inline_pad, highlight)
3939
else
40-
line:pad(self.info.inline_pad, highlight)
41-
line:text(self.info.inline_right, icon_highlight)
40+
line:pad(self.config.inline_pad, highlight)
41+
line:text(self.config.inline_right, icon_highlight)
4242
end
4343
if not line:empty() then
4444
local row = left and self.node.start_row or self.node.end_row

lua/render-markdown/render/inline/highlight.lua

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
local Base = require('render-markdown.render.base')
22

33
---@class render.md.render.inline.Highlight: render.md.Render
4-
---@field private info render.md.inline.highlight.Config
4+
---@field private config render.md.inline.highlight.Config
55
local Render = setmetatable({}, Base)
66
Render.__index = Render
77

88
---@protected
99
---@return boolean
1010
function Render:setup()
11-
self.info = self.config.inline_highlight
12-
if self.context:skip(self.info) then
11+
self.config = self.context.config.inline_highlight
12+
if self.context:skip(self.config) then
1313
return false
1414
end
1515
return true
@@ -18,15 +18,15 @@ end
1818
---@protected
1919
function Render:run()
2020
for _, range in ipairs(self.node:find('==[^=]+==')) do
21-
-- Hide first 2 equal signs
21+
-- hide first 2 equal signs
2222
self:hide_equals(range[1], range[2])
23-
-- Highlight contents
23+
-- highlight contents
2424
self.marks:add(false, range[1], range[2], {
2525
end_row = range[3],
2626
end_col = range[4],
27-
hl_group = self.info.highlight,
27+
hl_group = self.config.highlight,
2828
})
29-
-- Hide last 2 equal signs
29+
-- hide last 2 equal signs
3030
self:hide_equals(range[3], range[4] - 2)
3131
end
3232
end

lua/render-markdown/render/inline/link.lua

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,29 @@ Render.__index = Render
1212
---@protected
1313
---@return boolean
1414
function Render:setup()
15-
local link = self.config.link
16-
if self.context:skip(link) then
15+
local config = self.context.config.link
16+
if self.context:skip(config) then
1717
return false
1818
end
1919
if self.node:descendant('shortcut_link') then
2020
return false
2121
end
2222
---@type render.md.mark.Text
23-
local icon = { link.hyperlink, link.highlight }
23+
local icon = { config.hyperlink, config.highlight }
2424
local autolink = false
2525
if self.node.type == 'email_autolink' then
26-
icon[1] = link.email
26+
icon[1] = config.email
2727
autolink = true
2828
elseif self.node.type == 'image' then
29-
icon[1] = link.image
29+
icon[1] = config.image
3030
elseif self.node.type == 'inline_link' then
3131
local destination = self.node:child('link_destination')
3232
if destination then
33-
self.config:link_text(destination.text, icon)
33+
self.context.config:link_text(destination.text, icon)
3434
end
3535
elseif self.node.type == 'uri_autolink' then
3636
local destination = self.node.text:sub(2, -2)
37-
self.config:link_text(destination, icon)
37+
self.context.config:link_text(destination, icon)
3838
autolink = true
3939
end
4040
self.data = { icon = icon, autolink = autolink }

0 commit comments

Comments
 (0)