Skip to content

Commit d5b57b3

Browse files
feat: Allow custom link icons based on lua patterns
## Details Requested: #117 Similar to callouts and checkbox states this change allows link destinations to be matched against configured patterns when selecting an icon rather than using the same one everytime. For instance web links now use a little globe rather than the default chain link icon. More defaults can be added in if requested, for now mostly to allow individuals to use as they wish.
1 parent 9cdfae2 commit d5b57b3

File tree

12 files changed

+186
-80
lines changed

12 files changed

+186
-80
lines changed

README.md

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -405,10 +405,20 @@ require('render-markdown').setup({
405405
enabled = true,
406406
-- Inlined with 'image' elements
407407
image = '󰥶 ',
408-
-- Inlined with 'inline_link' elements
408+
-- Fallback icon for 'inline_link' elements
409409
hyperlink = '󰌹 ',
410-
-- Applies to the inlined icon
410+
-- Applies to the fallback inlined icon
411411
highlight = 'RenderMarkdownLink',
412+
-- Define custom destination patterns so icons can quickly inform you of what a link
413+
-- contains. Applies to 'inline_link' and wikilink nodes.
414+
-- Can specify as many additional values as you like following the 'web' pattern below
415+
-- The key in this case 'web' is for healthcheck and to allow users to change its values
416+
-- 'pattern': Matched against the destination text see :h lua-pattern
417+
-- 'icon': Gets inlined before the link text
418+
-- 'highlight': Highlight for the 'icon'
419+
custom = {
420+
web = { pattern = '^http[s]?://', icon = '󰖟 ', highlight = 'RenderMarkdownLink' },
421+
},
412422
},
413423
sign = {
414424
-- Turn on / off sign rendering
@@ -728,10 +738,20 @@ require('render-markdown').setup({
728738
enabled = true,
729739
-- Inlined with 'image' elements
730740
image = '󰥶 ',
731-
-- Inlined with 'inline_link' elements
741+
-- Fallback icon for 'inline_link' elements
732742
hyperlink = '󰌹 ',
733-
-- Applies to the inlined icon
743+
-- Applies to the fallback inlined icon
734744
highlight = 'RenderMarkdownLink',
745+
-- Define custom destination patterns so icons can quickly inform you of what a link
746+
-- contains. Applies to 'inline_link' and wikilink nodes.
747+
-- Can specify as many additional values as you like following the 'web' pattern below
748+
-- The key in this case 'web' is for healthcheck and to allow users to change its values
749+
-- 'pattern': Matched against the destination text see :h lua-pattern
750+
-- 'icon': Gets inlined before the link text
751+
-- 'highlight': Highlight for the 'icon'
752+
custom = {
753+
web = { pattern = '^http[s]?://', icon = '󰖟 ', highlight = 'RenderMarkdownLink' },
754+
},
735755
},
736756
})
737757
```

doc/render-markdown.txt

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -438,10 +438,20 @@ Full Default Configuration ~
438438
enabled = true,
439439
-- Inlined with 'image' elements
440440
image = '󰥶 ',
441-
-- Inlined with 'inline_link' elements
441+
-- Fallback icon for 'inline_link' elements
442442
hyperlink = '󰌹 ',
443-
-- Applies to the inlined icon
443+
-- Applies to the fallback inlined icon
444444
highlight = 'RenderMarkdownLink',
445+
-- Define custom destination patterns so icons can quickly inform you of what a link
446+
-- contains. Applies to 'inline_link' and wikilink nodes.
447+
-- Can specify as many additional values as you like following the 'web' pattern below
448+
-- The key in this case 'web' is for healthcheck and to allow users to change its values
449+
-- 'pattern': Matched against the destination text see :h lua-pattern
450+
-- 'icon': Gets inlined before the link text
451+
-- 'highlight': Highlight for the 'icon'
452+
custom = {
453+
web = { pattern = '^http[s]?://', icon = '󰖟 ', highlight = 'RenderMarkdownLink' },
454+
},
445455
},
446456
sign = {
447457
-- Turn on / off sign rendering
@@ -768,10 +778,20 @@ LINKS *render-markdown-setup-links*
768778
enabled = true,
769779
-- Inlined with 'image' elements
770780
image = '󰥶 ',
771-
-- Inlined with 'inline_link' elements
781+
-- Fallback icon for 'inline_link' elements
772782
hyperlink = '󰌹 ',
773-
-- Applies to the inlined icon
783+
-- Applies to the fallback inlined icon
774784
highlight = 'RenderMarkdownLink',
785+
-- Define custom destination patterns so icons can quickly inform you of what a link
786+
-- contains. Applies to 'inline_link' and wikilink nodes.
787+
-- Can specify as many additional values as you like following the 'web' pattern below
788+
-- The key in this case 'web' is for healthcheck and to allow users to change its values
789+
-- 'pattern': Matched against the destination text see :h lua-pattern
790+
-- 'icon': Gets inlined before the link text
791+
-- 'highlight': Highlight for the 'icon'
792+
custom = {
793+
web = { pattern = '^http[s]?://', icon = '󰖟 ', highlight = 'RenderMarkdownLink' },
794+
},
775795
},
776796
})
777797
<

lua/render-markdown/component.lua

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,16 @@ function M.checkbox(config, text, comparison)
4949
return nil
5050
end
5151

52+
---@param config render.md.BufferConfig
53+
---@param text string
54+
---@return render.md.LinkComponent?
55+
function M.link(config, text)
56+
for _, component in pairs(config.link.custom) do
57+
if text:find(component.pattern) then
58+
return component
59+
end
60+
end
61+
return nil
62+
end
63+
5264
return M

lua/render-markdown/handler/markdown_inline.lua

Lines changed: 78 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,34 @@ local str = require('render-markdown.str')
77
local ts = require('render-markdown.ts')
88
local util = require('render-markdown.util')
99

10-
---@class render.md.handler.MarkdownInline: render.md.Handler
11-
local M = {}
10+
---@class render.md.handler.buf.MarkdownInline
11+
---@field private buf integer
12+
---@field private config render.md.BufferConfig
13+
local Handler = {}
14+
Handler.__index = Handler
1215

13-
---@param root TSNode
1416
---@param buf integer
17+
---@return render.md.handler.buf.MarkdownInline
18+
function Handler.new(buf)
19+
local self = setmetatable({}, Handler)
20+
self.buf = buf
21+
self.config = state.get_config(buf)
22+
return self
23+
end
24+
25+
---@param root TSNode
1526
---@return render.md.Mark[]
16-
function M.parse(root, buf)
17-
local config = state.get_config(buf)
27+
function Handler:parse(root)
1828
local marks = {}
19-
context.get(buf):query(root, state.inline_query, function(capture, node)
20-
local info = ts.info(node, buf)
29+
context.get(self.buf):query(root, state.inline_query, function(capture, node)
30+
local info = ts.info(node, self.buf)
2131
logger.debug_node_info(capture, info)
2232
if capture == 'code' then
23-
list.add_mark(marks, M.code(config, info))
33+
list.add_mark(marks, self:code(info))
2434
elseif capture == 'shortcut' then
25-
list.add_mark(marks, M.shortcut(config, buf, info))
35+
list.add_mark(marks, self:shortcut(info))
2636
elseif capture == 'link' then
27-
list.add_mark(marks, M.link(config, buf, info))
37+
list.add_mark(marks, self:link(info))
2838
else
2939
logger.unhandled_capture('inline', capture)
3040
end
@@ -33,11 +43,10 @@ function M.parse(root, buf)
3343
end
3444

3545
---@private
36-
---@param config render.md.BufferConfig
3746
---@param info render.md.NodeInfo
3847
---@return render.md.Mark?
39-
function M.code(config, info)
40-
local code = config.code
48+
function Handler:code(info)
49+
local code = self.config.code
4150
if not code.enabled then
4251
return nil
4352
end
@@ -58,37 +67,33 @@ function M.code(config, info)
5867
end
5968

6069
---@private
61-
---@param config render.md.BufferConfig
62-
---@param buf integer
6370
---@param info render.md.NodeInfo
6471
---@return render.md.Mark?
65-
function M.shortcut(config, buf, info)
66-
local callout = component.callout(config, info.text, 'exact')
72+
function Handler:shortcut(info)
73+
local callout = component.callout(self.config, info.text, 'exact')
6774
if callout ~= nil then
68-
return M.callout(config, buf, info, callout)
75+
return self:callout(info, callout)
6976
end
70-
local checkbox = component.checkbox(config, info.text, 'exact')
77+
local checkbox = component.checkbox(self.config, info.text, 'exact')
7178
if checkbox ~= nil then
72-
return M.checkbox(config, info, checkbox)
79+
return self:checkbox(info, checkbox)
7380
end
74-
local line = vim.api.nvim_buf_get_lines(buf, info.start_row, info.start_row + 1, false)[1]
81+
local line = vim.api.nvim_buf_get_lines(self.buf, info.start_row, info.start_row + 1, false)[1]
7582
if line:find('[' .. info.text .. ']', 1, true) ~= nil then
76-
return M.wiki_link(config, info)
83+
return self:wiki_link(info)
7784
end
7885
return nil
7986
end
8087

8188
---@private
82-
---@param config render.md.BufferConfig
83-
---@param buf integer
8489
---@param info render.md.NodeInfo
8590
---@param callout render.md.CustomComponent
8691
---@return render.md.Mark?
87-
function M.callout(config, buf, info, callout)
92+
function Handler:callout(info, callout)
8893
---Support for overriding title: https://help.obsidian.md/Editing+and+formatting/Callouts#Change+the+title
8994
---@return string, string?
9095
local function custom_title()
91-
local content = ts.parent(buf, info, 'inline')
96+
local content = ts.parent(self.buf, info, 'inline')
9297
if content ~= nil then
9398
local line = str.split(content.text, '\n')[1]
9499
if #line > #callout.raw and vim.startswith(line:lower(), callout.raw:lower()) then
@@ -100,7 +105,7 @@ function M.callout(config, buf, info, callout)
100105
return callout.rendered, nil
101106
end
102107

103-
if not config.quote.enabled then
108+
if not self.config.quote.enabled then
104109
return nil
105110
end
106111
local text, conceal = custom_title()
@@ -120,16 +125,12 @@ function M.callout(config, buf, info, callout)
120125
end
121126

122127
---@private
123-
---@param config render.md.BufferConfig
124128
---@param info render.md.NodeInfo
125129
---@param checkbox render.md.CustomComponent
126130
---@return render.md.Mark?
127-
function M.checkbox(config, info, checkbox)
128-
if not config.checkbox.enabled then
129-
return nil
130-
end
131+
function Handler:checkbox(info, checkbox)
131132
-- Requires inline extmarks
132-
if not util.has_10 then
133+
if not self.config.checkbox.enabled or not util.has_10 then
133134
return nil
134135
end
135136
---@type render.md.Mark
@@ -148,20 +149,16 @@ function M.checkbox(config, info, checkbox)
148149
end
149150

150151
---@private
151-
---@param config render.md.BufferConfig
152152
---@param info render.md.NodeInfo
153153
---@return render.md.Mark?
154-
function M.wiki_link(config, info)
155-
local link = config.link
156-
if not link.enabled then
157-
return nil
158-
end
154+
function Handler:wiki_link(info)
159155
-- Requires inline extmarks
160-
if not util.has_10 then
156+
if not self.config.link.enabled or not util.has_10 then
161157
return nil
162158
end
163159
local text = info.text:sub(2, -2)
164-
local elements = str.split(text, '|')
160+
local parts = str.split(text, '|')
161+
local icon, highlight = self:destination_info(parts[1])
165162
---@type render.md.Mark
166163
return {
167164
conceal = true,
@@ -170,37 +167,38 @@ function M.wiki_link(config, info)
170167
opts = {
171168
end_row = info.end_row,
172169
end_col = info.end_col + 1,
173-
virt_text = { { link.hyperlink .. elements[#elements], link.highlight } },
170+
virt_text = { { icon .. parts[#parts], highlight } },
174171
virt_text_pos = 'inline',
175172
conceal = '',
176173
},
177174
}
178175
end
179176

180177
---@private
181-
---@param config render.md.BufferConfig
182-
---@param buf integer
183178
---@param info render.md.NodeInfo
184179
---@return render.md.Mark?
185-
function M.link(config, buf, info)
186-
local link = config.link
187-
if not link.enabled then
188-
return nil
189-
end
180+
function Handler:link(info)
181+
local link = self.config.link
190182
-- Requires inline extmarks
191-
if not util.has_10 then
183+
if not link.enabled or not util.has_10 then
192184
return nil
193185
end
194-
local icon = nil
195-
if vim.tbl_contains({ 'inline_link', 'full_reference_link' }, info.type) then
196-
icon = link.hyperlink
186+
local icon, highlight
187+
if info.type == 'inline_link' then
188+
local destination = ts.child(self.buf, info, 'link_destination')
189+
if destination ~= nil then
190+
icon, highlight = self:destination_info(destination.text)
191+
else
192+
icon, highlight = link.hyperlink, link.highlight
193+
end
194+
elseif info.type == 'full_reference_link' then
195+
icon, highlight = link.hyperlink, link.highlight
197196
elseif info.type == 'image' then
198-
icon = link.image
199-
end
200-
if icon == nil then
197+
icon, highlight = link.image, link.highlight
198+
else
201199
return nil
202200
end
203-
context.get(buf):add_link(info, icon)
201+
context.get(self.buf):add_link(info, icon)
204202
---@type render.md.Mark
205203
return {
206204
conceal = true,
@@ -209,10 +207,32 @@ function M.link(config, buf, info)
209207
opts = {
210208
end_row = info.end_row,
211209
end_col = info.end_col,
212-
virt_text = { { icon, link.highlight } },
210+
virt_text = { { icon, highlight } },
213211
virt_text_pos = 'inline',
214212
},
215213
}
216214
end
217215

216+
---@private
217+
---@param destination string
218+
---@return string, string
219+
function Handler:destination_info(destination)
220+
local link_component = component.link(self.config, destination)
221+
if link_component == nil then
222+
return self.config.link.hyperlink, self.config.link.highlight
223+
else
224+
return link_component.icon, link_component.highlight
225+
end
226+
end
227+
228+
---@class render.md.handler.MarkdownInline: render.md.Handler
229+
local M = {}
230+
231+
---@param root TSNode
232+
---@param buf integer
233+
---@return render.md.Mark[]
234+
function M.parse(root, buf)
235+
return Handler.new(buf):parse(root)
236+
end
237+
218238
return M

lua/render-markdown/health.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ local M = {}
55

66
---@private
77
---@type string
8-
M.version = '5.1.3'
8+
M.version = '5.1.4'
99

1010
function M.check()
1111
vim.health.start('markdown.nvim [version]')

0 commit comments

Comments
 (0)