Skip to content

Commit b6b903c

Browse files
feat: improve autolink handling
## Details Problem mentioned here: #244 For autolinks (emails & bare URLs) just like with wiki links we conceal all of the text then inline a modified version with our icon using a single extmark. This works but for longer strings can make things look choppy when removing the mark under the cursor row. To improve this for autolinks we'll instead split the one mark into 4 separate smaller ones: - Conceal the starting '<' - Conceal the ending '>' - Inline the icon at the start - Apply highlight over the link text Doing the same thing for wiki links would be more complicated to support aliases so will leave those as is for now.
1 parent da70762 commit b6b903c

File tree

9 files changed

+83
-88
lines changed

9 files changed

+83
-88
lines changed

lua/render-markdown/health.lua

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

66
---@private
7-
M.version = '7.6.7'
7+
M.version = '7.6.8'
88

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

lua/render-markdown/render/base.lua

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,19 @@ function Base:checkbox_scope(paragraph, highlight)
6161
end
6262

6363
---@protected
64+
---@param icon string
65+
---@param highlight string
6466
---@param destination string
65-
---@return render.md.LinkComponent?
66-
function Base:link_component(destination)
67-
local link = self.config.link
68-
local link_components = Iter.table.filter(link.custom, function(link_component)
69-
return destination:find(link_component.pattern) ~= nil
67+
---@return string, string
68+
function Base:from_destination(icon, highlight, destination)
69+
local components = Iter.table.filter(self.config.link.custom, function(component)
70+
return destination:find(component.pattern) ~= nil
7071
end)
71-
table.sort(link_components, function(a, b)
72+
table.sort(components, function(a, b)
7273
return Str.width(a.pattern) < Str.width(b.pattern)
7374
end)
74-
return link_components[#link_components]
75+
local component = components[#components] or {}
76+
return component.icon or icon, component.highlight or highlight
7577
end
7678

7779
---@protected

lua/render-markdown/render/link.lua

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

33
---@class render.md.data.Link
4-
---@field text string
4+
---@field icon string
55
---@field highlight string
6-
---@field conceal boolean
6+
---@field autolink boolean
77

88
---@class render.md.render.Link: render.md.Renderer
9-
---@field private link render.md.Link
109
---@field private data render.md.data.Link
1110
local Render = setmetatable({}, Base)
1211
Render.__index = Render
1312

1413
---@return boolean
1514
function Render:setup()
16-
self.link = self.config.link
17-
if not self.link.enabled then
15+
local link = self.config.link
16+
if not link.enabled then
1817
return false
1918
end
2019

20+
local icon, highlight, autolink = link.hyperlink, link.highlight, false
2121
if self.node.type == 'email_autolink' then
22-
local email = self.node.text:sub(2, -2)
23-
self.data = {
24-
text = self.link.email .. email,
25-
highlight = self.link.highlight,
26-
conceal = true,
27-
}
28-
elseif self.node.type == 'full_reference_link' then
29-
self.data = {
30-
text = self.link.hyperlink,
31-
highlight = self.link.highlight,
32-
conceal = false,
33-
}
22+
icon = link.email
23+
autolink = true
3424
elseif self.node.type == 'image' then
35-
self.data = {
36-
text = self.link.image,
37-
highlight = self.link.highlight,
38-
conceal = false,
39-
}
25+
icon = link.image
4026
elseif self.node.type == 'inline_link' then
4127
local destination = self.node:child('link_destination')
42-
local component = destination ~= nil and self:link_component(destination.text) or nil
43-
local text, highlight = self.link.hyperlink, nil
44-
if component ~= nil then
45-
text, highlight = component.icon, component.highlight
28+
if destination ~= nil then
29+
icon, highlight = self:from_destination(icon, highlight, destination.text)
4630
end
47-
self.data = {
48-
text = text,
49-
highlight = highlight or self.link.highlight,
50-
conceal = false,
51-
}
5231
elseif self.node.type == 'uri_autolink' then
5332
local destination = self.node.text:sub(2, -2)
54-
local component = self:link_component(destination)
55-
local text, highlight = self.link.hyperlink, nil
56-
if component ~= nil then
57-
text, highlight = component.icon, component.highlight
58-
end
59-
self.data = {
60-
text = text .. destination,
61-
highlight = highlight or self.link.highlight,
62-
conceal = true,
63-
}
64-
else
65-
return false
33+
icon, highlight = self:from_destination(icon, highlight, destination)
34+
autolink = true
6635
end
36+
self.data = { icon = icon, highlight = highlight, autolink = autolink }
6737

6838
return true
6939
end
7040

7141
function Render:render()
7242
self.marks:add_over('link', self.node, {
73-
virt_text = { { self.data.text, self.data.highlight } },
43+
virt_text = { { self.data.icon, self.data.highlight } },
7444
virt_text_pos = 'inline',
75-
conceal = self.data.conceal and '' or nil,
45+
})
46+
if self.data.autolink then
47+
self:hide_bracket(self.node.start_col)
48+
self.marks:add_over('link', self.node, {
49+
hl_group = self.data.highlight,
50+
})
51+
self:hide_bracket(self.node.end_col - 1)
52+
end
53+
end
54+
55+
---@private
56+
---@param col integer
57+
function Render:hide_bracket(col)
58+
self.marks:add(true, self.node.start_row, col, {
59+
end_col = col + 1,
60+
conceal = '',
7661
})
7762
end
7863

lua/render-markdown/render/shortcut.lua

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,9 @@ function Render:wiki_link()
9999
end
100100

101101
local parts = Str.split(self.node.text:sub(2, -2), '|')
102-
local component = self:link_component(parts[1])
103-
local icon, highlight = self.link.wiki.icon, nil
104-
if component ~= nil then
105-
icon, highlight = component.icon, component.highlight
106-
end
102+
local icon, highlight = self:from_destination(self.link.wiki.icon, self.link.wiki.highlight, parts[1])
107103
self.marks:add_over('link', self.node, {
108-
virt_text = { { icon .. parts[#parts], highlight or self.link.wiki.highlight } },
104+
virt_text = { { icon .. parts[#parts], highlight } },
109105
virt_text_pos = 'inline',
110106
conceal = '',
111107
}, { 0, -1, 0, 1 })

tests/ad_hoc_spec.lua

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,35 @@ end
3232
---@param row integer
3333
---@param start_col integer
3434
---@param end_col integer
35-
---@param link_text string
35+
---@param text string
3636
---@param highlight 'Link'|'UncycloLink'
37-
---@param conceal string?
37+
---@param conceal? string
3838
---@return render.md.MarkInfo
39-
local function link(row, start_col, end_col, link_text, highlight, conceal)
39+
local function link(row, start_col, end_col, text, highlight, conceal)
4040
---@type render.md.MarkInfo
4141
return {
4242
row = { row, row },
4343
col = { start_col, end_col },
44-
virt_text = { { link_text, util.hl(highlight) } },
44+
virt_text = { { text, util.hl(highlight) } },
4545
virt_text_pos = 'inline',
4646
conceal = conceal,
4747
}
4848
end
4949

50+
---@param row integer
51+
---@param start_col integer
52+
---@param end_col integer
53+
---@param icon string
54+
---@return render.md.MarkInfo[]
55+
local function auto_link(row, start_col, end_col, icon)
56+
return {
57+
util.conceal(row, start_col, start_col + 1),
58+
link(row, start_col, end_col, icon, 'Link', nil),
59+
util.highlight(row, start_col, end_col, 'Link'),
60+
util.conceal(row, end_col - 1, end_col),
61+
}
62+
end
63+
5064
describe('ad_hoc.md', function()
5165
it('custom', function()
5266
util.setup('tests/data/ad_hoc.md')
@@ -71,12 +85,12 @@ describe('ad_hoc.md', function()
7185

7286
vim.list_extend(expected, {
7387
util.bullet(row:increment(), 0, 1),
74-
link(row:get(), 2, 20, '󰀓 [email protected]', 'Link', ''),
88+
auto_link(row:get(), 2, 20, '󰀓 '),
7589
})
7690

7791
vim.list_extend(expected, {
7892
util.bullet(row:increment(), 0, 1),
79-
link(row:get(), 2, 26, '󰊤 http://www.github.com/', 'Link', ''),
93+
auto_link(row:get(), 2, 26, '󰊤 '),
8094
})
8195

8296
vim.list_extend(expected, {

tests/custom_handler_spec.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('custom_handler.md', function()
2626
local expected, row = {}, util.row()
2727
vim.list_extend(expected, {
2828
util.heading(row:get(), 1), -- Heading
29-
util.inline_code(row:increment(2), 0, 8), -- Inline code
29+
util.highlight(row:increment(2), 0, 8, 'CodeInline'), -- Inline code
3030
{}, -- No backslash escapes
3131
})
3232

@@ -72,7 +72,7 @@ describe('custom_handler.md', function()
7272
local expected, row = {}, util.row()
7373
vim.list_extend(expected, {
7474
util.heading(row:get(), 1), -- Heading
75-
util.inline_code(row:increment(2), 0, 8), -- Inline code
75+
util.highlight(row:increment(2), 0, 8, 'CodeInline'), -- Inline code
7676
{ util.conceal(row:increment(2), 0, 1), util.conceal(row:get(), 7, 8) }, -- Backslash escapes
7777
})
7878

tests/list_table_spec.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ describe('list_table.md', function()
1414
util.bullet(row:increment(2), 0, 1),
1515
util.link(row:get(), 20, 47, 'web'),
1616
util.bullet(row:increment(), 0, 1),
17-
util.inline_code(row:get(), 20, 28),
17+
util.highlight(row:get(), 20, 28, 'CodeInline'),
1818
util.bullet(row:increment(), 2, 2, 2),
1919
util.bullet(row:increment(), 4, 2),
2020
util.bullet(row:increment(), 6, 3),
@@ -31,7 +31,7 @@ describe('list_table.md', function()
3131
vim.list_extend(expected, {
3232
util.table_border(row:increment(2), true, { 8, 15, 7, 6 }),
3333
util.table_pipe(row:get(), 0, true),
34-
util.inline_code(row:get(), 2, 8),
34+
util.highlight(row:get(), 2, 8, 'CodeInline'),
3535
util.table_padding(row:get(), 9, 2),
3636
util.table_pipe(row:get(), 9, true),
3737
util.table_padding(row:get(), 11, 3),
@@ -43,7 +43,7 @@ describe('list_table.md', function()
4343
table.insert(expected, util.table_delimiter(row:increment(), { { 1, 7 }, { 1, 13, 1 }, { 6, 1 }, 6 }))
4444
vim.list_extend(expected, {
4545
util.table_pipe(row:increment(), 0, false),
46-
util.inline_code(row:get(), 2, 8),
46+
util.highlight(row:get(), 2, 8, 'CodeInline'),
4747
util.table_padding(row:get(), 9, 2),
4848
util.table_pipe(row:get(), 9, false),
4949
util.table_padding(row:get(), 11, 4),

tests/table_spec.lua

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ describe('table.md', function()
1414
util.table_pipe(row:get(), 0, true),
1515
util.table_pipe(row:get(), 12, true),
1616
util.table_padding(row:get(), 14, 13),
17-
util.inline_code(row:get(), 14, 25),
17+
util.highlight(row:get(), 14, 25, 'CodeInline'),
1818
util.conceal(row:get(), 26, 37),
1919
util.table_pipe(row:get(), 37, true),
2020
util.table_delimiter(row:increment(), { 11, { 23, 1 } }),
2121
util.table_pipe(row:increment(), 0, false),
22-
util.inline_code(row:get(), 2, 12),
22+
util.highlight(row:get(), 2, 12, 'CodeInline'),
2323
util.table_padding(row:get(), 13, 2),
2424
util.table_pipe(row:get(), 13, false),
2525
util.table_padding(row:get(), 15, 16),
@@ -78,12 +78,12 @@ describe('table.md', function()
7878
util.table_border(row:increment(2), true, { 11, 11 }),
7979
util.table_pipe(row:get(), 0, true),
8080
util.table_pipe(row:get(), 12, true),
81-
util.inline_code(row:get(), 14, 25),
81+
util.highlight(row:get(), 14, 25, 'CodeInline'),
8282
util.conceal(row:get(), 26, 37),
8383
util.table_pipe(row:get(), 37, true),
8484
util.table_delimiter(row:increment(), { 11, { 10, 1 } }, 13),
8585
util.table_pipe(row:increment(), 0, false),
86-
util.inline_code(row:get(), 2, 12),
86+
util.highlight(row:get(), 2, 12, 'CodeInline'),
8787
util.table_padding(row:get(), 13, 2),
8888
util.table_pipe(row:get(), 13, false),
8989
util.table_padding(row:get(), 15, 3),
@@ -141,11 +141,11 @@ describe('table.md', function()
141141
util.heading(row:get(), 1),
142142
util.table_pipe(row:increment(2), 0, true),
143143
util.table_pipe(row:get(), 12, true),
144-
util.inline_code(row:get(), 14, 25),
144+
util.highlight(row:get(), 14, 25, 'CodeInline'),
145145
util.table_pipe(row:get(), 37, true),
146146
util.table_delimiter(row:increment(), { 11, { 23, 1 } }),
147147
util.table_pipe(row:increment(), 0, false),
148-
util.inline_code(row:get(), 2, 12),
148+
util.highlight(row:get(), 2, 12, 'CodeInline'),
149149
util.table_pipe(row:get(), 13, false),
150150
util.link(row:get(), 15, 38, 'web'),
151151
util.table_pipe(row:get(), 39, false),
@@ -211,10 +211,10 @@ describe('table.md', function()
211211
util.heading(row:get(), 1),
212212
util.table_border(row:increment(2), true, { 11, 24 }),
213213
table_row(row:get(), 38, '│ Heading 1 │ `Heading 2` │', true),
214-
util.inline_code(row:get(), 14, 25),
214+
util.highlight(row:get(), 14, 25, 'CodeInline'),
215215
util.table_delimiter(row:increment(), { 11, { 23, 1 } }),
216216
table_row(row:increment(), 40, '│ `Item 行` │ [link](https://行.com) │', false),
217-
util.inline_code(row:get(), 2, 12),
217+
util.highlight(row:get(), 2, 12, 'CodeInline'),
218218
util.link(row:get(), 15, 38, 'web'),
219219
table_row(row:increment(), 39, '│ &lt;1&gt; │ ==Itém 2== │', false),
220220
util.inline_highlight(row:get(), 14, 25),

tests/util.lua

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ function M.heading(row, level)
142142
local background_mark = {
143143
row = { row, row + 1 },
144144
col = { 0, 0 },
145-
hl_group = background,
146145
hl_eol = true,
146+
hl_group = background,
147147
}
148148
return { sign_mark, icon_mark, background_mark }
149149
end
@@ -181,14 +181,15 @@ end
181181
---@param row integer
182182
---@param start_col integer
183183
---@param end_col integer
184+
---@param highlight string
184185
---@return render.md.MarkInfo
185-
function M.inline_code(row, start_col, end_col)
186+
function M.highlight(row, start_col, end_col, highlight)
186187
---@type render.md.MarkInfo
187188
return {
188189
row = { row, row },
189190
col = { start_col, end_col },
190191
hl_eol = false,
191-
hl_group = M.hl('CodeInline'),
192+
hl_group = M.hl(highlight),
192193
}
193194
end
194195

@@ -197,14 +198,11 @@ end
197198
---@param end_col integer
198199
---@return render.md.MarkInfo[]
199200
function M.inline_highlight(row, start_col, end_col)
200-
---@type render.md.MarkInfo
201-
local mark = {
202-
row = { row, row },
203-
col = { start_col, end_col },
204-
hl_eol = false,
205-
hl_group = M.hl('InlineHighlight'),
201+
return {
202+
M.conceal(row, start_col, start_col + 2),
203+
M.highlight(row, start_col, end_col, 'InlineHighlight'),
204+
M.conceal(row, end_col - 2, end_col),
206205
}
207-
return { M.conceal(row, start_col, start_col + 2), mark, M.conceal(row, end_col - 2, end_col) }
208206
end
209207

210208
---@param row integer

0 commit comments

Comments
 (0)