Skip to content

Commit 146d4ff

Browse files
feat: render wiki style links in yaml
## Details Request: #496 Adds ability to render wiki style links in yaml, primarily for people that use `obsidian` style metadata via `frontmatter`. Still requires user to install `yaml` parser so we can query nodes. Currently only supports the style that is enclosed in double quotes and double square brackets. Values used for rendering are currently pulled from the same configuration as `markdown` wiki link rendering, i.e. `link.wiki`. The logic to do the rendering is pulled directly from the `markdown` implementation, by refactoring a little both code points call the same core logic to perform rendering. The inputs store slightly different information so a bit more parsing is done on the text to compute various offsets and text bounds, but seems to all work well!
1 parent 06ed008 commit 146d4ff

File tree

6 files changed

+106
-71
lines changed

6 files changed

+106
-71
lines changed

doc/render-markdown.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*render-markdown.txt* For NVIM v0.11.3 Last change: 2025 August 15
1+
*render-markdown.txt* For NVIM v0.11.3 Last change: 2025 August 17
22

33
==============================================================================
44
Table of Contents *render-markdown-table-of-contents*

lua/render-markdown/handler/yaml.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@ function M.parse(ctx)
1212
'yaml',
1313
[[
1414
(block_sequence_item) @bullet
15+
16+
((double_quote_scalar) @link
17+
(#lua-match? @link "^\"%[%[.+%]%]\"$"))
1518
]]
1619
)
1720
---@type table<string, render.md.Render>
1821
local renders = {
1922
bullet = require('render-markdown.render.yaml.bullet'),
23+
link = require('render-markdown.render.common.wiki'),
2024
}
2125
local context = Context.get(ctx.buf)
2226
if context:skip(context.config.yaml) then

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.7.4'
8+
M.version = '8.7.5'
99

1010
function M.check()
1111
M.start('versions')
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
local Base = require('render-markdown.render.base')
2+
local str = require('render-markdown.lib.str')
3+
4+
---@class render.md.render.common.Wiki: render.md.Render
5+
---@field private config render.md.link.wiki.Config
6+
local Render = setmetatable({}, Base)
7+
Render.__index = Render
8+
9+
---@protected
10+
---@return boolean
11+
function Render:setup()
12+
local config = self.context.config.link
13+
if self.context:skip(config) then
14+
return false
15+
end
16+
self.config = config.wiki
17+
return true
18+
end
19+
20+
---@protected
21+
function Render:run()
22+
local _, _, pre, text, post = self.node.text:find('^(.-%[+)(.-)(%]+.-)$')
23+
if not pre or not text or not post then
24+
return -- not inside square brackets
25+
end
26+
if #pre ~= #post then
27+
return -- pre and post are not balanced
28+
end
29+
30+
local row = self.node.start_row
31+
local start_col = self.node.start_col + #pre
32+
local end_col = self.node.end_col - #pre
33+
local destination, alias = unpack(str.split(text, '|', true))
34+
35+
-- hide opening & closing brackets
36+
self:hide(start_col - 2, 2)
37+
self:hide(end_col, 2)
38+
39+
---@type render.md.mark.Text
40+
local icon = { self.config.icon, self.config.highlight }
41+
self.context.config:set_link_text(destination, icon)
42+
local body = self.config.body({
43+
buf = self.context.buf,
44+
row = row,
45+
start_col = start_col - 2,
46+
end_col = end_col + 2,
47+
destination = destination,
48+
alias = alias,
49+
})
50+
if not body then
51+
-- add icon
52+
self.marks:add('link', row, start_col, {
53+
hl_mode = 'combine',
54+
virt_text = { icon },
55+
virt_text_pos = 'inline',
56+
})
57+
-- hide destination if there is an alias
58+
if alias then
59+
self:hide(start_col, #destination + 1)
60+
end
61+
else
62+
if type(body) == 'string' then
63+
icon[1] = icon[1] .. body
64+
else
65+
icon[1] = icon[1] .. body[1]
66+
icon[2] = body[2]
67+
end
68+
-- inline icon & body, hide original text
69+
self.marks:add('link', row, start_col, {
70+
end_col = end_col,
71+
hl_mode = 'combine',
72+
virt_text = { icon },
73+
virt_text_pos = 'inline',
74+
conceal = '',
75+
})
76+
end
77+
end
78+
79+
---@private
80+
---@param col integer
81+
---@param length integer
82+
function Render:hide(col, length)
83+
self.marks:add(true, self.node.start_row, col, {
84+
end_col = col + length,
85+
conceal = '',
86+
})
87+
end
88+
89+
return Render

lua/render-markdown/render/inline/shortcut.lua

Lines changed: 2 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
local Base = require('render-markdown.render.base')
2+
local Wiki = require('render-markdown.render.common.wiki')
23
local converter = require('render-markdown.lib.converter')
3-
local str = require('render-markdown.lib.str')
44

55
---@class render.md.render.inline.Shortcut: render.md.Render
66
---@field private config render.md.link.Config
@@ -34,7 +34,7 @@ function Render:run()
3434
local _, line = self.node:line('first', 0)
3535
local wiki_pattern = '[' .. self.node.text .. ']'
3636
if line and line:find(wiki_pattern, 1, true) then
37-
self:wiki_link()
37+
Wiki:execute(self.context, self.marks, self.node)
3838
return
3939
end
4040
local _, _, text = self.node.text:find('^%[%^(.+)%]$')
@@ -44,64 +44,6 @@ function Render:run()
4444
end
4545
end
4646

47-
---@private
48-
function Render:wiki_link()
49-
local config = self.config.wiki
50-
local sections = str.split(self.node.text:sub(2, -2), '|', true)
51-
---@type render.md.link.Context
52-
local ctx = {
53-
buf = self.context.buf,
54-
row = self.node.start_row,
55-
start_col = self.node.start_col - 1,
56-
end_col = self.node.end_col + 1,
57-
destination = sections[1],
58-
alias = sections[2],
59-
}
60-
-- hide opening & closing outer brackets
61-
self:hide(ctx.start_col, 1)
62-
self:hide(ctx.end_col - 1, 1)
63-
---@type render.md.mark.Text
64-
local icon = { config.icon, config.highlight }
65-
self.context.config:set_link_text(ctx.destination, icon)
66-
local body = config.body(ctx)
67-
if not body then
68-
-- add icon
69-
self.marks:start('link', self.node, {
70-
hl_mode = 'combine',
71-
virt_text = { icon },
72-
virt_text_pos = 'inline',
73-
})
74-
-- hide destination if there is an alias
75-
if #sections > 1 then
76-
self:hide(ctx.start_col + 2, #ctx.destination + 1)
77-
end
78-
else
79-
if type(body) == 'string' then
80-
icon[1] = icon[1] .. body
81-
else
82-
icon[1] = icon[1] .. body[1]
83-
icon[2] = body[2]
84-
end
85-
-- inline icon & body, hide original text
86-
self.marks:over('link', self.node, {
87-
hl_mode = 'combine',
88-
virt_text = { icon },
89-
virt_text_pos = 'inline',
90-
conceal = '',
91-
}, { 0, 1, 0, -1 })
92-
end
93-
end
94-
95-
---@private
96-
---@param col integer
97-
---@param length integer
98-
function Render:hide(col, length)
99-
self.marks:add(true, self.node.start_row, col, {
100-
end_col = col + length,
101-
conceal = '',
102-
})
103-
end
104-
10547
---@private
10648
---@param text string
10749
function Render:footnote(text)

tests/ad_hoc_spec.lua

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,28 +50,28 @@ describe('ad hoc', function()
5050
it('wikilink', function()
5151
util.setup.text({ '[[Basic One]] Then normal text' })
5252
local marks = util.marks()
53-
marks:add({ 0, 0 }, { 0, 1 }, util.conceal())
54-
marks:add(0, 1, util.link('wiki'))
55-
marks:add({ 0, 0 }, { 12, 13 }, util.conceal())
53+
marks:add({ 0, 0 }, { 0, 2 }, util.conceal())
54+
marks:add(0, 2, util.link('wiki'))
55+
marks:add({ 0, 0 }, { 11, 13 }, util.conceal())
5656
util.assert_view(marks, { '󱗖 Basic One Then normal text' })
5757
end)
5858

5959
it('wikilink with alias', function()
6060
util.setup.text({ '[[Nickname|With Alias]] Something important' })
6161
local marks = util.marks()
62-
marks:add({ 0, 0 }, { 0, 1 }, util.conceal())
63-
marks:add(0, 1, util.link('wiki'))
62+
marks:add({ 0, 0 }, { 0, 2 }, util.conceal())
63+
marks:add(0, 2, util.link('wiki'))
6464
marks:add({ 0, 0 }, { 2, 11 }, util.conceal())
65-
marks:add({ 0, 0 }, { 22, 23 }, util.conceal())
65+
marks:add({ 0, 0 }, { 21, 23 }, util.conceal())
6666
util.assert_view(marks, { '󱗖 With Alias Something important' })
6767
end)
6868

6969
it('wikilink media', function()
7070
util.setup.text({ '![[test.png]]' })
7171
local marks = util.marks()
72-
marks:add({ 0, 0 }, { 1, 2 }, util.conceal())
73-
marks:add(0, 2, util.link('wiki'))
74-
marks:add({ 0, 0 }, { 12, 13 }, util.conceal())
72+
marks:add({ 0, 0 }, { 1, 3 }, util.conceal())
73+
marks:add(0, 3, util.link('wiki'))
74+
marks:add({ 0, 0 }, { 11, 13 }, util.conceal())
7575
util.assert_view(marks, { '󱗖 test.png' })
7676
end)
7777

0 commit comments

Comments
 (0)