@@ -108,18 +108,34 @@ function getWorkspaceFolder(document: ITextDocument) {
108
108
}
109
109
110
110
export interface MdLinkSource {
111
+ /**
112
+ * The full range of the link.
113
+ */
114
+ readonly range : vscode . Range ;
115
+
116
+ /**
117
+ * The file where the link is defined.
118
+ */
119
+ readonly resource : vscode . Uri ;
120
+
111
121
/**
112
122
* The original text of the link destination in code.
113
123
*/
114
- readonly text : string ;
124
+ readonly hrefText : string ;
115
125
116
126
/**
117
127
* The original text of just the link's path in code.
118
128
*/
119
129
readonly pathText : string ;
120
130
121
- readonly resource : vscode . Uri ;
131
+ /**
132
+ * The range of the path.
133
+ */
122
134
readonly hrefRange : vscode . Range ;
135
+
136
+ /**
137
+ * The range of the fragment within the path.
138
+ */
123
139
readonly fragmentRange : vscode . Range | undefined ;
124
140
}
125
141
@@ -145,32 +161,37 @@ function extractDocumentLink(
145
161
document : ITextDocument ,
146
162
pre : string ,
147
163
rawLink : string ,
148
- matchIndex : number | undefined
164
+ matchIndex : number ,
165
+ fullMatch : string ,
149
166
) : MdLink | undefined {
150
167
const isAngleBracketLink = rawLink . startsWith ( '<' ) ;
151
168
const link = stripAngleBrackets ( rawLink ) ;
152
169
153
- const offset = ( matchIndex || 0 ) + pre . length + ( isAngleBracketLink ? 1 : 0 ) ;
154
- const linkStart = document . positionAt ( offset ) ;
155
- const linkEnd = document . positionAt ( offset + link . length ) ;
170
+ let linkTarget : ExternalHref | InternalHref | undefined ;
156
171
try {
157
- const linkTarget = resolveLink ( document , link ) ;
158
- if ( ! linkTarget ) {
159
- return undefined ;
160
- }
161
- return {
162
- kind : 'link' ,
163
- href : linkTarget ,
164
- source : {
165
- text : link ,
166
- resource : document . uri ,
167
- hrefRange : new vscode . Range ( linkStart , linkEnd ) ,
168
- ...getLinkSourceFragmentInfo ( document , link , linkStart , linkEnd ) ,
169
- }
170
- } ;
172
+ linkTarget = resolveLink ( document , link ) ;
171
173
} catch {
172
174
return undefined ;
173
175
}
176
+ if ( ! linkTarget ) {
177
+ return undefined ;
178
+ }
179
+
180
+ const linkStart = document . positionAt ( matchIndex ) ;
181
+ const linkEnd = linkStart . translate ( 0 , fullMatch . length ) ;
182
+ const hrefStart = linkStart . translate ( 0 , pre . length + ( isAngleBracketLink ? 1 : 0 ) ) ;
183
+ const hrefEnd = hrefStart . translate ( 0 , link . length ) ;
184
+ return {
185
+ kind : 'link' ,
186
+ href : linkTarget ,
187
+ source : {
188
+ hrefText : link ,
189
+ resource : document . uri ,
190
+ range : new vscode . Range ( linkStart , linkEnd ) ,
191
+ hrefRange : new vscode . Range ( hrefStart , hrefEnd ) ,
192
+ ...getLinkSourceFragmentInfo ( document , link , hrefStart , hrefEnd ) ,
193
+ }
194
+ } ;
174
195
}
175
196
176
197
function getFragmentRange ( text : string , start : vscode . Position , end : vscode . Position ) : vscode . Range | undefined {
@@ -278,13 +299,28 @@ class NoLinkRanges {
278
299
/**
279
300
* Inline code spans where links should not be detected
280
301
*/
281
- public readonly inline : Map < /* line number */ number , readonly vscode . Range [ ] >
302
+ public readonly inline : Map < /* line number */ number , vscode . Range [ ] >
282
303
) { }
283
304
284
305
contains ( position : vscode . Position ) : boolean {
285
306
return this . multiline . some ( interval => position . line >= interval [ 0 ] && position . line < interval [ 1 ] ) ||
286
307
! ! this . inline . get ( position . line ) ?. some ( inlineRange => inlineRange . contains ( position ) ) ;
287
308
}
309
+
310
+ concatInline ( inlineRanges : Iterable < vscode . Range > ) : NoLinkRanges {
311
+ const newInline = new Map ( this . inline ) ;
312
+ for ( const range of inlineRanges ) {
313
+ for ( let line = range . start . line ; line <= range . end . line ; ++ line ) {
314
+ let entry = newInline . get ( line ) ;
315
+ if ( ! entry ) {
316
+ entry = [ ] ;
317
+ newInline . set ( line , entry ) ;
318
+ }
319
+ entry . push ( range ) ;
320
+ }
321
+ }
322
+ return new NoLinkRanges ( this . multiline , newInline ) ;
323
+ }
288
324
}
289
325
290
326
/**
@@ -302,9 +338,10 @@ export class MdLinkComputer {
302
338
return [ ] ;
303
339
}
304
340
341
+ const inlineLinks = Array . from ( this . getInlineLinks ( document , noLinkRanges ) ) ;
305
342
return Array . from ( [
306
- ...this . getInlineLinks ( document , noLinkRanges ) ,
307
- ...this . getReferenceLinks ( document , noLinkRanges ) ,
343
+ ...inlineLinks ,
344
+ ...this . getReferenceLinks ( document , noLinkRanges . concatInline ( inlineLinks . map ( x => x . source . range ) ) ) ,
308
345
...this . getLinkDefinitions ( document , noLinkRanges ) ,
309
346
...this . getAutoLinks ( document , noLinkRanges ) ,
310
347
] ) ;
@@ -313,13 +350,13 @@ export class MdLinkComputer {
313
350
private * getInlineLinks ( document : ITextDocument , noLinkRanges : NoLinkRanges ) : Iterable < MdLink > {
314
351
const text = document . getText ( ) ;
315
352
for ( const match of text . matchAll ( linkPattern ) ) {
316
- const matchLinkData = extractDocumentLink ( document , match [ 1 ] , match [ 2 ] , match . index ) ;
353
+ const matchLinkData = extractDocumentLink ( document , match [ 1 ] , match [ 2 ] , match . index ?? 0 , match [ 0 ] ) ;
317
354
if ( matchLinkData && ! noLinkRanges . contains ( matchLinkData . source . hrefRange . start ) ) {
318
355
yield matchLinkData ;
319
356
320
357
// Also check link destination for links
321
358
for ( const innerMatch of match [ 1 ] . matchAll ( linkPattern ) ) {
322
- const innerData = extractDocumentLink ( document , innerMatch [ 1 ] , innerMatch [ 2 ] , ( match . index ?? 0 ) + ( innerMatch . index ?? 0 ) ) ;
359
+ const innerData = extractDocumentLink ( document , innerMatch [ 1 ] , innerMatch [ 2 ] , ( match . index ?? 0 ) + ( innerMatch . index ?? 0 ) , innerMatch [ 0 ] ) ;
323
360
if ( innerData ) {
324
361
yield innerData ;
325
362
}
@@ -328,77 +365,83 @@ export class MdLinkComputer {
328
365
}
329
366
}
330
367
331
- private * getAutoLinks ( document : ITextDocument , noLinkRanges : NoLinkRanges ) : Iterable < MdLink > {
368
+ private * getAutoLinks ( document : ITextDocument , noLinkRanges : NoLinkRanges ) : Iterable < MdLink > {
332
369
const text = document . getText ( ) ;
333
-
334
370
for ( const match of text . matchAll ( autoLinkPattern ) ) {
371
+ const linkOffset = ( match . index ?? 0 ) ;
372
+ const linkStart = document . positionAt ( linkOffset ) ;
373
+ if ( noLinkRanges . contains ( linkStart ) ) {
374
+ continue ;
375
+ }
376
+
335
377
const link = match [ 1 ] ;
336
378
const linkTarget = resolveLink ( document , link ) ;
337
- if ( linkTarget ) {
338
- const offset = ( match . index ?? 0 ) + 1 ;
339
- const linkStart = document . positionAt ( offset ) ;
340
- const linkEnd = document . positionAt ( offset + link . length ) ;
341
- const hrefRange = new vscode . Range ( linkStart , linkEnd ) ;
342
- if ( noLinkRanges . contains ( hrefRange . start ) ) {
343
- continue ;
344
- }
345
- yield {
346
- kind : 'link' ,
347
- href : linkTarget ,
348
- source : {
349
- text : link ,
350
- resource : document . uri ,
351
- hrefRange : new vscode . Range ( linkStart , linkEnd ) ,
352
- ...getLinkSourceFragmentInfo ( document , link , linkStart , linkEnd ) ,
353
- }
354
- } ;
379
+ if ( ! linkTarget ) {
380
+ continue ;
355
381
}
382
+
383
+ const linkEnd = linkStart . translate ( 0 , match [ 0 ] . length ) ;
384
+ const hrefStart = linkStart . translate ( 0 , 1 ) ;
385
+ const hrefEnd = hrefStart . translate ( 0 , link . length ) ;
386
+ yield {
387
+ kind : 'link' ,
388
+ href : linkTarget ,
389
+ source : {
390
+ hrefText : link ,
391
+ resource : document . uri ,
392
+ hrefRange : new vscode . Range ( hrefStart , hrefEnd ) ,
393
+ range : new vscode . Range ( linkStart , linkEnd ) ,
394
+ ...getLinkSourceFragmentInfo ( document , link , hrefStart , hrefEnd ) ,
395
+ }
396
+ } ;
356
397
}
357
398
}
358
399
359
400
private * getReferenceLinks ( document : ITextDocument , noLinkRanges : NoLinkRanges ) : Iterable < MdLink > {
360
401
const text = document . getText ( ) ;
361
402
for ( const match of text . matchAll ( referenceLinkPattern ) ) {
362
- let linkStart : vscode . Position ;
363
- let linkEnd : vscode . Position ;
403
+ const linkStart = document . positionAt ( match . index ?? 0 ) ;
404
+ if ( noLinkRanges . contains ( linkStart ) ) {
405
+ continue ;
406
+ }
407
+
408
+ let hrefStart : vscode . Position ;
409
+ let hrefEnd : vscode . Position ;
364
410
let reference = match [ 4 ] ;
365
411
if ( reference === '' ) { // [ref][],
366
412
reference = match [ 3 ] ;
367
413
const offset = ( ( match . index ?? 0 ) + match [ 1 ] . length ) + 1 ;
368
- linkStart = document . positionAt ( offset ) ;
369
- linkEnd = document . positionAt ( offset + reference . length ) ;
414
+ hrefStart = document . positionAt ( offset ) ;
415
+ hrefEnd = document . positionAt ( offset + reference . length ) ;
370
416
} else if ( reference ) { // [text][ref]
371
417
const pre = match [ 2 ] ;
372
418
const offset = ( ( match . index ?? 0 ) + match [ 1 ] . length ) + pre . length ;
373
- linkStart = document . positionAt ( offset ) ;
374
- linkEnd = document . positionAt ( offset + reference . length ) ;
419
+ hrefStart = document . positionAt ( offset ) ;
420
+ hrefEnd = document . positionAt ( offset + reference . length ) ;
375
421
} else if ( match [ 5 ] ) { // [ref]
376
422
reference = match [ 5 ] ;
377
423
const offset = ( ( match . index ?? 0 ) + match [ 1 ] . length ) + 1 ;
378
- linkStart = document . positionAt ( offset ) ;
379
- const line = document . lineAt ( linkStart . line ) ;
424
+ hrefStart = document . positionAt ( offset ) ;
425
+ const line = document . lineAt ( hrefStart . line ) ;
380
426
// See if link looks like a checkbox
381
427
const checkboxMatch = line . text . match ( / ^ \s * [ \- \* ] \s * \[ x \] / i) ;
382
- if ( checkboxMatch && linkStart . character <= checkboxMatch [ 0 ] . length ) {
428
+ if ( checkboxMatch && hrefStart . character <= checkboxMatch [ 0 ] . length ) {
383
429
continue ;
384
430
}
385
- linkEnd = document . positionAt ( offset + reference . length ) ;
431
+ hrefEnd = document . positionAt ( offset + reference . length ) ;
386
432
} else {
387
433
continue ;
388
434
}
389
435
390
- const hrefRange = new vscode . Range ( linkStart , linkEnd ) ;
391
- if ( noLinkRanges . contains ( hrefRange . start ) ) {
392
- continue ;
393
- }
394
-
436
+ const linkEnd = linkStart . translate ( 0 , match [ 0 ] . length ) ;
395
437
yield {
396
438
kind : 'link' ,
397
439
source : {
398
- text : reference ,
440
+ hrefText : reference ,
399
441
pathText : reference ,
400
442
resource : document . uri ,
401
- hrefRange,
443
+ range : new vscode . Range ( linkStart , linkEnd ) ,
444
+ hrefRange : new vscode . Range ( hrefStart , hrefEnd ) ,
402
445
fragmentRange : undefined ,
403
446
} ,
404
447
href : {
@@ -412,44 +455,41 @@ export class MdLinkComputer {
412
455
private * getLinkDefinitions ( document : ITextDocument , noLinkRanges : NoLinkRanges ) : Iterable < MdLinkDefinition > {
413
456
const text = document . getText ( ) ;
414
457
for ( const match of text . matchAll ( definitionPattern ) ) {
458
+ const offset = ( match . index ?? 0 ) ;
459
+ const linkStart = document . positionAt ( offset ) ;
460
+ if ( noLinkRanges . contains ( linkStart ) ) {
461
+ continue ;
462
+ }
463
+
415
464
const pre = match [ 1 ] ;
416
465
const reference = match [ 2 ] ;
417
- const link = match [ 3 ] . trim ( ) ;
418
- const offset = ( match . index || 0 ) + pre . length ;
419
-
420
- const refStart = document . positionAt ( ( match . index ?? 0 ) + 1 ) ;
421
- const refRange = new vscode . Range ( refStart , refStart . translate ( { characterDelta : reference . length } ) ) ;
422
-
423
- let linkStart : vscode . Position ;
424
- let linkEnd : vscode . Position ;
425
- let text : string ;
426
- if ( angleBracketLinkRe . test ( link ) ) {
427
- linkStart = document . positionAt ( offset + 1 ) ;
428
- linkEnd = document . positionAt ( offset + link . length - 1 ) ;
429
- text = link . substring ( 1 , link . length - 1 ) ;
430
- } else {
431
- linkStart = document . positionAt ( offset ) ;
432
- linkEnd = document . positionAt ( offset + link . length ) ;
433
- text = link ;
434
- }
435
- const hrefRange = new vscode . Range ( linkStart , linkEnd ) ;
436
- if ( noLinkRanges . contains ( hrefRange . start ) ) {
466
+ const rawLinkText = match [ 3 ] . trim ( ) ;
467
+ const target = resolveLink ( document , rawLinkText ) ;
468
+ if ( ! target ) {
437
469
continue ;
438
470
}
439
- const target = resolveLink ( document , text ) ;
440
- if ( target ) {
441
- yield {
442
- kind : 'definition' ,
443
- source : {
444
- text : link ,
445
- resource : document . uri ,
446
- hrefRange,
447
- ...getLinkSourceFragmentInfo ( document , link , linkStart , linkEnd ) ,
448
- } ,
449
- ref : { text : reference , range : refRange } ,
450
- href : target ,
451
- } ;
452
- }
471
+
472
+ const isAngleBracketLink = angleBracketLinkRe . test ( rawLinkText ) ;
473
+ const linkText = stripAngleBrackets ( rawLinkText ) ;
474
+ const hrefStart = linkStart . translate ( 0 , pre . length + ( isAngleBracketLink ? 1 : 0 ) ) ;
475
+ const hrefEnd = hrefStart . translate ( 0 , linkText . length ) ;
476
+ const hrefRange = new vscode . Range ( hrefStart , hrefEnd ) ;
477
+
478
+ const refStart = linkStart . translate ( 0 , 1 ) ;
479
+ const refRange = new vscode . Range ( refStart , refStart . translate ( { characterDelta : reference . length } ) ) ;
480
+ const linkEnd = linkStart . translate ( 0 , match [ 0 ] . length ) ;
481
+ yield {
482
+ kind : 'definition' ,
483
+ source : {
484
+ hrefText : linkText ,
485
+ resource : document . uri ,
486
+ range : new vscode . Range ( linkStart , linkEnd ) ,
487
+ hrefRange,
488
+ ...getLinkSourceFragmentInfo ( document , rawLinkText , hrefStart , hrefEnd ) ,
489
+ } ,
490
+ ref : { text : reference , range : refRange } ,
491
+ href : target ,
492
+ } ;
453
493
}
454
494
}
455
495
}
0 commit comments