@@ -108,18 +108,34 @@ function getWorkspaceFolder(document: ITextDocument) {
108108}
109109
110110export 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+
111121 /**
112122 * The original text of the link destination in code.
113123 */
114- readonly text : string ;
124+ readonly hrefText : string ;
115125
116126 /**
117127 * The original text of just the link's path in code.
118128 */
119129 readonly pathText : string ;
120130
121- readonly resource : vscode . Uri ;
131+ /**
132+ * The range of the path.
133+ */
122134 readonly hrefRange : vscode . Range ;
135+
136+ /**
137+ * The range of the fragment within the path.
138+ */
123139 readonly fragmentRange : vscode . Range | undefined ;
124140}
125141
@@ -145,32 +161,37 @@ function extractDocumentLink(
145161 document : ITextDocument ,
146162 pre : string ,
147163 rawLink : string ,
148- matchIndex : number | undefined
164+ matchIndex : number ,
165+ fullMatch : string ,
149166) : MdLink | undefined {
150167 const isAngleBracketLink = rawLink . startsWith ( '<' ) ;
151168 const link = stripAngleBrackets ( rawLink ) ;
152169
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 ;
156171 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 ) ;
171173 } catch {
172174 return undefined ;
173175 }
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+ } ;
174195}
175196
176197function getFragmentRange ( text : string , start : vscode . Position , end : vscode . Position ) : vscode . Range | undefined {
@@ -278,13 +299,28 @@ class NoLinkRanges {
278299 /**
279300 * Inline code spans where links should not be detected
280301 */
281- public readonly inline : Map < /* line number */ number , readonly vscode . Range [ ] >
302+ public readonly inline : Map < /* line number */ number , vscode . Range [ ] >
282303 ) { }
283304
284305 contains ( position : vscode . Position ) : boolean {
285306 return this . multiline . some ( interval => position . line >= interval [ 0 ] && position . line < interval [ 1 ] ) ||
286307 ! ! this . inline . get ( position . line ) ?. some ( inlineRange => inlineRange . contains ( position ) ) ;
287308 }
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+ }
288324}
289325
290326/**
@@ -302,9 +338,10 @@ export class MdLinkComputer {
302338 return [ ] ;
303339 }
304340
341+ const inlineLinks = Array . from ( this . getInlineLinks ( document , noLinkRanges ) ) ;
305342 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 ) ) ) ,
308345 ...this . getLinkDefinitions ( document , noLinkRanges ) ,
309346 ...this . getAutoLinks ( document , noLinkRanges ) ,
310347 ] ) ;
@@ -313,13 +350,13 @@ export class MdLinkComputer {
313350 private * getInlineLinks ( document : ITextDocument , noLinkRanges : NoLinkRanges ) : Iterable < MdLink > {
314351 const text = document . getText ( ) ;
315352 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 ] ) ;
317354 if ( matchLinkData && ! noLinkRanges . contains ( matchLinkData . source . hrefRange . start ) ) {
318355 yield matchLinkData ;
319356
320357 // Also check link destination for links
321358 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 ] ) ;
323360 if ( innerData ) {
324361 yield innerData ;
325362 }
@@ -328,77 +365,83 @@ export class MdLinkComputer {
328365 }
329366 }
330367
331- private * getAutoLinks ( document : ITextDocument , noLinkRanges : NoLinkRanges ) : Iterable < MdLink > {
368+ private * getAutoLinks ( document : ITextDocument , noLinkRanges : NoLinkRanges ) : Iterable < MdLink > {
332369 const text = document . getText ( ) ;
333-
334370 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+
335377 const link = match [ 1 ] ;
336378 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 ;
355381 }
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+ } ;
356397 }
357398 }
358399
359400 private * getReferenceLinks ( document : ITextDocument , noLinkRanges : NoLinkRanges ) : Iterable < MdLink > {
360401 const text = document . getText ( ) ;
361402 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 ;
364410 let reference = match [ 4 ] ;
365411 if ( reference === '' ) { // [ref][],
366412 reference = match [ 3 ] ;
367413 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 ) ;
370416 } else if ( reference ) { // [text][ref]
371417 const pre = match [ 2 ] ;
372418 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 ) ;
375421 } else if ( match [ 5 ] ) { // [ref]
376422 reference = match [ 5 ] ;
377423 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 ) ;
380426 // See if link looks like a checkbox
381427 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 ) {
383429 continue ;
384430 }
385- linkEnd = document . positionAt ( offset + reference . length ) ;
431+ hrefEnd = document . positionAt ( offset + reference . length ) ;
386432 } else {
387433 continue ;
388434 }
389435
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 ) ;
395437 yield {
396438 kind : 'link' ,
397439 source : {
398- text : reference ,
440+ hrefText : reference ,
399441 pathText : reference ,
400442 resource : document . uri ,
401- hrefRange,
443+ range : new vscode . Range ( linkStart , linkEnd ) ,
444+ hrefRange : new vscode . Range ( hrefStart , hrefEnd ) ,
402445 fragmentRange : undefined ,
403446 } ,
404447 href : {
@@ -412,44 +455,41 @@ export class MdLinkComputer {
412455 private * getLinkDefinitions ( document : ITextDocument , noLinkRanges : NoLinkRanges ) : Iterable < MdLinkDefinition > {
413456 const text = document . getText ( ) ;
414457 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+
415464 const pre = match [ 1 ] ;
416465 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 ) {
437469 continue ;
438470 }
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+ } ;
453493 }
454494 }
455495}
0 commit comments