11--- @class OrgLinkHighlighter : OrgMarkupHighlighter
22--- @field private markup OrgMarkupHighlighter
33--- @field private has_extmark_url_support boolean
4+ --- @field private last_start_node_id ? string
45local OrgLink = {
56 valid_capture_names = {
67 [' link.start' ] = true ,
4445function OrgLink :_parse_start_node (node )
4546 local node_type = node :type ()
4647 local next_sibling = node :next_sibling ()
48+ local prev_sibling = node :prev_sibling ()
4749
50+ -- Start of link
4851 if next_sibling and next_sibling :type () == ' [' then
49- local id = table.concat ({ ' link' , node_type }, ' _' )
52+ local id = table.concat ({ ' link' , ' [ ' }, ' _' )
5053 local seek_id = table.concat ({ ' link' , ' ]' }, ' _' )
54+ self .last_start_node_id = node :id ()
5155 return {
5256 type = ' link' ,
5357 id = id ,
5458 char = node_type ,
5559 seek_id = seek_id ,
5660 nestable = false ,
5761 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+ },
5884 node = node ,
5985 }
6086 end
6894function OrgLink :_parse_end_node (node )
6995 local node_type = node :type ()
7096 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' , ' ]' }, ' _' )
73102 local seek_id = table.concat ({ ' link' , ' [' }, ' _' )
74103 return {
75104 type = ' link' ,
@@ -79,9 +108,34 @@ function OrgLink:_parse_end_node(node)
79108 range = self .markup :node_to_range (node ),
80109 nestable = false ,
81110 node = node ,
111+ metadata = {
112+ type = ' link_end_alias_start' ,
113+ start_node_id = self .last_start_node_id ,
114+ },
82115 }
83116 end
84117
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+
85139 return false
86140end
87141
@@ -97,17 +151,50 @@ function OrgLink:is_valid_end_node(entry)
97151 return entry .type == ' link' and entry .id == ' link_]'
98152end
99153
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+
100162--- @param highlights OrgMarkupHighlight[]
101163--- @param bufnr number
102164function OrgLink :highlight (highlights , bufnr )
103165 local namespace = self .markup .highlighter .namespace
104166 local ephemeral = self .markup :use_ephemeral ()
105167
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
111198
112199 local link_opts = {
113200 ephemeral = ephemeral ,
@@ -116,43 +203,83 @@ function OrgLink:highlight(highlights, bufnr)
116203 priority = 110 ,
117204 }
118205
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+ })
121217 end
122218
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
136240
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::
142243 end
143244end
144245
145246--- @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
147248--- @return OrgMarkupPreparedHighlight[]
148249function OrgLink :prepare_highlights (highlights , source_getter_fn )
149250 local ephemeral = self .markup :use_ephemeral ()
150251 local extmarks = {}
151252
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
156283
157284 local link_opts = {
158285 ephemeral = ephemeral ,
@@ -161,8 +288,45 @@ function OrgLink:prepare_highlights(highlights, source_getter_fn)
161288 priority = 110 ,
162289 }
163290
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+ })
166330 end
167331
168332 table.insert (extmarks , {
@@ -174,30 +338,7 @@ function OrgLink:prepare_highlights(highlights, source_getter_fn)
174338 priority = link_opts .priority ,
175339 url = link_opts .url ,
176340 })
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::
201342 end
202343
203344 return extmarks
0 commit comments