1
1
--- @class OrgLinkHighlighter : OrgMarkupHighlighter
2
2
--- @field private markup OrgMarkupHighlighter
3
3
--- @field private has_extmark_url_support boolean
4
+ --- @field private last_start_node_id ? string
4
5
local OrgLink = {
5
6
valid_capture_names = {
6
7
[' link.start' ] = true ,
44
45
function OrgLink :_parse_start_node (node )
45
46
local node_type = node :type ()
46
47
local next_sibling = node :next_sibling ()
48
+ local prev_sibling = node :prev_sibling ()
47
49
50
+ -- Start of link
48
51
if next_sibling and next_sibling :type () == ' [' then
49
- local id = table.concat ({ ' link' , node_type }, ' _' )
52
+ local id = table.concat ({ ' link' , ' [ ' }, ' _' )
50
53
local seek_id = table.concat ({ ' link' , ' ]' }, ' _' )
54
+ self .last_start_node_id = node :id ()
51
55
return {
52
56
type = ' link' ,
53
57
id = id ,
54
58
char = node_type ,
55
59
seek_id = seek_id ,
56
60
nestable = false ,
57
61
range = self .markup :node_to_range (node ),
62
+ metadata = {
63
+ type = ' link_start' ,
64
+ },
65
+ node = node ,
66
+ }
67
+ end
68
+
69
+ -- Start of link alias
70
+ if prev_sibling and prev_sibling :type () == ' ]' then
71
+ local id = table.concat ({ ' link' , ' [' }, ' _' )
72
+ local seek_id = table.concat ({ ' link' , ' ]' }, ' _' )
73
+ return {
74
+ type = ' link' ,
75
+ id = id ,
76
+ char = node_type ,
77
+ seek_id = seek_id ,
78
+ nestable = true ,
79
+ range = self .markup :node_to_range (node ),
80
+ metadata = {
81
+ type = ' link_alias_start' ,
82
+ start_node_id = self .last_start_node_id ,
83
+ },
58
84
node = node ,
59
85
}
60
86
end
68
94
function OrgLink :_parse_end_node (node )
69
95
local node_type = node :type ()
70
96
local prev_sibling = node :prev_sibling ()
71
- if prev_sibling and prev_sibling :type () == ' ]' then
72
- local id = table.concat ({ ' link' , node_type }, ' _' )
97
+ local next_sibling = node :next_sibling ()
98
+
99
+ -- End of link, start of alias
100
+ if next_sibling and next_sibling :type () == ' [' then
101
+ local id = table.concat ({ ' link' , ' ]' }, ' _' )
73
102
local seek_id = table.concat ({ ' link' , ' [' }, ' _' )
74
103
return {
75
104
type = ' link' ,
@@ -79,9 +108,34 @@ function OrgLink:_parse_end_node(node)
79
108
range = self .markup :node_to_range (node ),
80
109
nestable = false ,
81
110
node = node ,
111
+ metadata = {
112
+ type = ' link_end_alias_start' ,
113
+ start_node_id = self .last_start_node_id ,
114
+ },
82
115
}
83
116
end
84
117
118
+ -- End of link
119
+ if prev_sibling and prev_sibling :type () == ' ]' then
120
+ local id = table.concat ({ ' link' , ' ]' }, ' _' )
121
+ local seek_id = table.concat ({ ' link' , ' [' }, ' _' )
122
+ local result = {
123
+ type = ' link' ,
124
+ id = id ,
125
+ char = node_type ,
126
+ seek_id = seek_id ,
127
+ range = self .markup :node_to_range (node ),
128
+ nestable = false ,
129
+ metadata = {
130
+ type = ' link_end' ,
131
+ },
132
+ node = node ,
133
+ }
134
+ result .metadata .start_node_id = self .last_start_node_id
135
+ self .last_start_node_id = nil
136
+ return result
137
+ end
138
+
85
139
return false
86
140
end
87
141
@@ -97,17 +151,50 @@ function OrgLink:is_valid_end_node(entry)
97
151
return entry .type == ' link' and entry .id == ' link_]'
98
152
end
99
153
154
+ function OrgLink :_get_url (bufnr , line , start_col , end_col )
155
+ if not self .has_extmark_url_support then
156
+ return nil
157
+ end
158
+
159
+ return vim .api .nvim_buf_get_text (bufnr , line , start_col , line , end_col , {})[1 ]
160
+ end
161
+
100
162
--- @param highlights OrgMarkupHighlight[]
101
163
--- @param bufnr number
102
164
function OrgLink :highlight (highlights , bufnr )
103
165
local namespace = self .markup .highlighter .namespace
104
166
local ephemeral = self .markup :use_ephemeral ()
105
167
106
- for _ , entry in ipairs (highlights ) do
107
- local link =
108
- vim .api .nvim_buf_get_text (bufnr , entry .from .line , entry .from .start_col , entry .from .line , entry .to .end_col , {})[1 ]
109
- local alias = link :find (' %]%[' ) or 1
110
- local link_end = link :find (' %]%[' ) or (link :len () - 1 )
168
+ for i , entry in ipairs (highlights ) do
169
+ local prev_entry = highlights [i - 1 ]
170
+ local next_entry = highlights [i + 1 ]
171
+ if not entry .metadata .start_node_id then
172
+ goto continue
173
+ end
174
+
175
+ -- Alias without the valid end link
176
+ if
177
+ entry .metadata .type == ' link_end_alias_start'
178
+ and (
179
+ not next_entry
180
+ or next_entry .metadata .type ~= ' link_end'
181
+ or entry .metadata .start_node_id ~= next_entry .metadata .start_node_id
182
+ )
183
+ then
184
+ goto continue
185
+ end
186
+
187
+ -- End node without the valid alias
188
+ if
189
+ entry .metadata .type == ' link_end'
190
+ and (
191
+ prev_entry
192
+ and prev_entry .metadata .type == ' link_end_alias_start'
193
+ and prev_entry .metadata .start_node_id ~= entry .metadata .start_node_id
194
+ )
195
+ then
196
+ goto continue
197
+ end
111
198
112
199
local link_opts = {
113
200
ephemeral = ephemeral ,
@@ -116,43 +203,83 @@ function OrgLink:highlight(highlights, bufnr)
116
203
priority = 110 ,
117
204
}
118
205
119
- if self .has_extmark_url_support then
120
- link_opts .url = alias > 1 and link :sub (3 , alias - 1 ) or link :sub (3 , - 3 )
206
+ if entry .metadata .type == ' link_end_alias_start' then
207
+ link_opts .url = self :_get_url (bufnr , entry .from .line , entry .from .start_col + 2 , entry .to .end_col - 1 )
208
+ link_opts .spell = false
209
+ entry .url = link_opts .url
210
+ -- Conceal the whole target (marked with << and >>)
211
+ -- <<[[https://neovim.io][>>Neovim]]
212
+ vim .api .nvim_buf_set_extmark (bufnr , namespace , entry .from .line , entry .from .start_col , {
213
+ ephemeral = ephemeral ,
214
+ end_col = entry .to .end_col + 1 ,
215
+ conceal = ' ' ,
216
+ })
121
217
end
122
218
123
- vim .api .nvim_buf_set_extmark (bufnr , namespace , entry .from .line , entry .from .start_col , link_opts )
124
-
125
- vim .api .nvim_buf_set_extmark (bufnr , namespace , entry .from .line , entry .from .start_col , {
126
- ephemeral = ephemeral ,
127
- end_col = entry .from .start_col + 1 + alias ,
128
- conceal = ' ' ,
129
- })
130
-
131
- vim .api .nvim_buf_set_extmark (bufnr , namespace , entry .from .line , entry .from .start_col + 2 , {
132
- ephemeral = ephemeral ,
133
- end_col = entry .from .start_col - 1 + link_end ,
134
- spell = false ,
135
- })
219
+ if entry .metadata .type == ' link_end' then
220
+ if prev_entry and prev_entry .metadata .type == ' link_end_alias_start' then
221
+ link_opts .url = prev_entry .url
222
+ else
223
+ link_opts .url = self :_get_url (bufnr , entry .from .line , entry .from .start_col + 2 , entry .to .end_col - 2 )
224
+ -- Conceal the start marker (marked with << and >>)
225
+ -- <<[[>>https://neovim.io][Neovim]]
226
+ vim .api .nvim_buf_set_extmark (bufnr , namespace , entry .from .line , entry .from .start_col , {
227
+ ephemeral = ephemeral ,
228
+ end_col = entry .from .start_col + 2 ,
229
+ conceal = ' ' ,
230
+ })
231
+ end
232
+ -- Conceal the end marker (marked with << and >>)
233
+ -- [[https://neovim.io][Neovim<<]]>>
234
+ vim .api .nvim_buf_set_extmark (bufnr , namespace , entry .from .line , entry .to .end_col - 2 , {
235
+ ephemeral = ephemeral ,
236
+ end_col = entry .to .end_col ,
237
+ conceal = ' ' ,
238
+ })
239
+ end
136
240
137
- vim .api .nvim_buf_set_extmark (bufnr , namespace , entry .from .line , entry .to .end_col - 2 , {
138
- ephemeral = ephemeral ,
139
- end_col = entry .to .end_col ,
140
- conceal = ' ' ,
141
- })
241
+ vim .api .nvim_buf_set_extmark (bufnr , namespace , entry .from .line , entry .from .start_col , link_opts )
242
+ :: continue::
142
243
end
143
244
end
144
245
145
246
--- @param highlights OrgMarkupHighlight[]
146
- --- @param source_getter_fn fun ( highlight : OrgMarkupHighlight ): string
247
+ --- @param source_getter_fn fun ( start_col : number , end_col : number ): string
147
248
--- @return OrgMarkupPreparedHighlight[]
148
249
function OrgLink :prepare_highlights (highlights , source_getter_fn )
149
250
local ephemeral = self .markup :use_ephemeral ()
150
251
local extmarks = {}
151
252
152
- for _ , entry in ipairs (highlights ) do
153
- local link = source_getter_fn (entry )
154
- local alias = link :find (' %]%[' ) or 1
155
- local link_end = link :find (' %]%[' ) or (link :len () - 1 )
253
+ for i , entry in ipairs (highlights ) do
254
+ local prev_entry = highlights [i - 1 ]
255
+ local next_entry = highlights [i + 1 ]
256
+ if not entry .metadata .start_node_id then
257
+ goto continue
258
+ end
259
+
260
+ -- Alias without the valid end link
261
+ if
262
+ entry .metadata .type == ' link_end_alias_start'
263
+ and (
264
+ not next_entry
265
+ or next_entry .metadata .type ~= ' link_end'
266
+ or entry .metadata .start_node_id ~= next_entry .metadata .start_node_id
267
+ )
268
+ then
269
+ goto continue
270
+ end
271
+
272
+ -- End node without the valid alias
273
+ if
274
+ entry .metadata .type == ' link_end'
275
+ and (
276
+ prev_entry
277
+ and prev_entry .metadata .type == ' link_end_alias_start'
278
+ and prev_entry .metadata .start_node_id ~= entry .metadata .start_node_id
279
+ )
280
+ then
281
+ goto continue
282
+ end
156
283
157
284
local link_opts = {
158
285
ephemeral = ephemeral ,
@@ -161,8 +288,45 @@ function OrgLink:prepare_highlights(highlights, source_getter_fn)
161
288
priority = 110 ,
162
289
}
163
290
164
- if self .has_extmark_url_support then
165
- link_opts .url = alias > 1 and link :sub (3 , alias - 1 ) or link :sub (3 , - 3 )
291
+ if entry .metadata .type == ' link_end_alias_start' then
292
+ link_opts .url = source_getter_fn (entry .from .end_col + 2 , entry .to .end_col - 1 )
293
+ link_opts .spell = false
294
+ entry .url = link_opts .url
295
+ -- Conceal the whole target (marked with << and >>)
296
+ -- <<[[https://neovim.io][>>Neovim]]
297
+ table.insert (extmarks , {
298
+ start_line = entry .from .line ,
299
+ start_col = entry .from .start_col ,
300
+ end_col = entry .to .end_col + 1 ,
301
+ ephemeral = ephemeral ,
302
+ conceal = ' ' ,
303
+ })
304
+ end
305
+
306
+ if entry .metadata .type == ' link_end' then
307
+ if prev_entry and prev_entry .metadata .type == ' link_end_alias_start' then
308
+ link_opts .url = prev_entry .url
309
+ else
310
+ link_opts .url = source_getter_fn (entry .from .end_col + 2 , entry .to .end_col - 2 )
311
+ -- Conceal the start marker (marked with << and >>)
312
+ -- <<[[>>https://neovim.io][Neovim]]
313
+ table.insert (extmarks , {
314
+ start_line = entry .from .line ,
315
+ start_col = entry .from .start_col ,
316
+ end_col = entry .from .start_col + 2 ,
317
+ ephemeral = ephemeral ,
318
+ conceal = ' ' ,
319
+ })
320
+ end
321
+ -- Conceal the end marker (marked with << and >>)
322
+ -- [[https://neovim.io][Neovim<<]]>>
323
+ table.insert (extmarks , {
324
+ start_line = entry .from .line ,
325
+ start_col = entry .to .end_col - 2 ,
326
+ end_col = entry .to .end_col ,
327
+ ephemeral = ephemeral ,
328
+ conceal = ' ' ,
329
+ })
166
330
end
167
331
168
332
table.insert (extmarks , {
@@ -174,30 +338,7 @@ function OrgLink:prepare_highlights(highlights, source_getter_fn)
174
338
priority = link_opts .priority ,
175
339
url = link_opts .url ,
176
340
})
177
-
178
- table.insert (extmarks , {
179
- start_line = entry .from .line ,
180
- start_col = entry .from .start_col ,
181
- end_col = entry .from .start_col + 1 + alias ,
182
- ephemeral = ephemeral ,
183
- conceal = ' ' ,
184
- })
185
-
186
- table.insert (extmarks , {
187
- start_line = entry .from .line ,
188
- start_col = entry .from .start_col + 2 ,
189
- end_col = entry .from .start_col - 1 + link_end ,
190
- ephemeral = ephemeral ,
191
- spell = false ,
192
- })
193
-
194
- table.insert (extmarks , {
195
- start_line = entry .from .line ,
196
- start_col = entry .to .end_col - 2 ,
197
- end_col = entry .to .end_col ,
198
- ephemeral = ephemeral ,
199
- conceal = ' ' ,
200
- })
341
+ :: continue::
201
342
end
202
343
203
344
return extmarks
0 commit comments