@@ -8,6 +8,7 @@ import * as nls from 'vscode-nls';
8
8
import * as uri from 'vscode-uri' ;
9
9
import { OpenDocumentLinkCommand } from '../commands/openDocumentLink' ;
10
10
import { MarkdownEngine } from '../markdownEngine' ;
11
+ import { coalesce } from '../util/arrays' ;
11
12
import { getUriForLinkWithKnownExternalScheme , isOfScheme , Schemes } from '../util/schemes' ;
12
13
import { SkinnyTextDocument } from '../workspaceContents' ;
13
14
@@ -20,19 +21,28 @@ export interface ExternalLinkTarget {
20
21
21
22
export interface InternalLinkTarget {
22
23
readonly kind : 'internal' ;
23
-
24
- readonly fromResource : vscode . Uri ;
25
24
readonly path : vscode . Uri ;
26
25
readonly fragment : string ;
27
26
}
28
27
29
- export type LinkTarget = ExternalLinkTarget | InternalLinkTarget ;
28
+ export interface ReferenceLinkTarget {
29
+ readonly kind : 'reference' ;
30
+ readonly ref : string ;
31
+ }
32
+
33
+ export interface DefinitionLinkTarget {
34
+ readonly kind : 'definition' ;
35
+ readonly ref : string ;
36
+ readonly target : ExternalLinkTarget | InternalLinkTarget ;
37
+ }
38
+
39
+ export type LinkTarget = ExternalLinkTarget | InternalLinkTarget | ReferenceLinkTarget | DefinitionLinkTarget ;
30
40
31
41
32
42
function parseLink (
33
43
document : SkinnyTextDocument ,
34
44
link : string ,
35
- ) : LinkTarget | undefined {
45
+ ) : ExternalLinkTarget | InternalLinkTarget | undefined {
36
46
const cleanLink = stripAngleBrackets ( link ) ;
37
47
const externalSchemeUri = getUriForLinkWithKnownExternalScheme ( cleanLink ) ;
38
48
if ( externalSchemeUri ) {
@@ -75,7 +85,6 @@ function parseLink(
75
85
76
86
return {
77
87
kind : 'internal' ,
78
- fromResource : document . uri ,
79
88
path : resourceUri ,
80
89
fragment : tempUri . fragment ,
81
90
} ;
@@ -88,6 +97,7 @@ function getWorkspaceFolder(document: SkinnyTextDocument) {
88
97
89
98
export interface LinkData {
90
99
readonly target : LinkTarget ;
100
+ readonly sourceResource : vscode . Uri ;
91
101
readonly sourceRange : vscode . Range ;
92
102
}
93
103
@@ -107,6 +117,7 @@ function extractDocumentLink(
107
117
}
108
118
return {
109
119
target : linkTarget ,
120
+ sourceResource : document . uri ,
110
121
sourceRange : new vscode . Range ( linkStart , linkEnd )
111
122
} ;
112
123
} catch {
@@ -172,36 +183,63 @@ function isLinkInsideCode(code: CodeInDocument, link: LinkData) {
172
183
code . inline . some ( position => position . intersection ( link . sourceRange ) ) ;
173
184
}
174
185
175
- function createDocumentLink ( sourceRange : vscode . Range , target : LinkTarget ) {
176
- if ( target . kind === 'external' ) {
177
- return new vscode . DocumentLink ( sourceRange , target . uri ) ;
178
- } else {
179
-
180
- const uri = OpenDocumentLinkCommand . createCommandUri ( target . fromResource , target . path , target . fragment ) ;
181
- const documentLink = new vscode . DocumentLink ( sourceRange , uri ) ;
182
- documentLink . tooltip = localize ( 'documentLink.tooltip' , 'Follow link' ) ;
183
- return documentLink ;
184
- }
185
- }
186
-
187
186
export class MdLinkProvider implements vscode . DocumentLinkProvider {
187
+
188
188
constructor (
189
189
private readonly engine : MarkdownEngine
190
190
) { }
191
191
192
192
public async provideDocumentLinks (
193
193
document : SkinnyTextDocument ,
194
- _token : vscode . CancellationToken
194
+ token : vscode . CancellationToken
195
195
) : Promise < vscode . DocumentLink [ ] > {
196
- const text = document . getText ( ) ;
197
- const inlineLinks = await this . getInlineLinks ( text , document ) ;
198
- return [
199
- ...inlineLinks . map ( data => createDocumentLink ( data . sourceRange , data . target ) ) ,
200
- ...this . getReferenceLinks ( text , document )
201
- ] ;
196
+ const allLinks = await this . getAllLinks ( document ) ;
197
+ if ( token . isCancellationRequested ) {
198
+ return [ ] ;
199
+ }
200
+
201
+ const definitionSet = new LinkDefinitionSet ( allLinks ) ;
202
+ return coalesce ( allLinks
203
+ . map ( data => this . toValidDocumentLink ( data , definitionSet ) ) ) ;
204
+ }
205
+
206
+ private toValidDocumentLink ( link : LinkData , definitionSet : LinkDefinitionSet ) : vscode . DocumentLink | undefined {
207
+ switch ( link . target . kind ) {
208
+ case 'external' : {
209
+ return new vscode . DocumentLink ( link . sourceRange , link . target . uri ) ;
210
+ }
211
+ case 'internal' : {
212
+ const uri = OpenDocumentLinkCommand . createCommandUri ( link . sourceResource , link . target . path , link . target . fragment ) ;
213
+ const documentLink = new vscode . DocumentLink ( link . sourceRange , uri ) ;
214
+ documentLink . tooltip = localize ( 'documentLink.tooltip' , 'Follow link' ) ;
215
+ return documentLink ;
216
+ }
217
+ case 'reference' : {
218
+ const def = definitionSet . lookup ( link . target . ref ) ;
219
+ if ( def ) {
220
+ return new vscode . DocumentLink (
221
+ link . sourceRange ,
222
+ vscode . Uri . parse ( `command:_markdown.moveCursorToPosition?${ encodeURIComponent ( JSON . stringify ( [ def . sourceRange . start . line , def . sourceRange . start . character ] ) ) } ` ) ) ;
223
+ } else {
224
+ return undefined ;
225
+ }
226
+ }
227
+ case 'definition' :
228
+ return this . toValidDocumentLink ( { sourceRange : link . sourceRange , sourceResource : link . sourceResource , target : link . target . target } , definitionSet ) ;
229
+ }
230
+ }
231
+
232
+ public async getAllLinks ( document : SkinnyTextDocument ) : Promise < LinkData [ ] > {
233
+ return Array . from ( [
234
+ ...( await this . getInlineLinks ( document ) ) ,
235
+ ...this . getReferenceLinks ( document ) ,
236
+ ...this . getDefinitionLinks ( document ) ,
237
+ ] ) ;
202
238
}
203
239
204
- public async getInlineLinks ( text : string , document : SkinnyTextDocument ) : Promise < LinkData [ ] > {
240
+ private async getInlineLinks ( document : SkinnyTextDocument ) : Promise < LinkData [ ] > {
241
+ const text = document . getText ( ) ;
242
+
205
243
const results : LinkData [ ] = [ ] ;
206
244
const codeInDocument = await findCode ( document , this . engine ) ;
207
245
for ( const match of text . matchAll ( linkPattern ) ) {
@@ -217,8 +255,8 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
217
255
return results ;
218
256
}
219
257
220
- public * getReferenceLinks ( text : string , document : SkinnyTextDocument ) : Iterable < vscode . DocumentLink > {
221
- const definitions = this . getDefinitions ( text , document ) ;
258
+ private * getReferenceLinks ( document : SkinnyTextDocument ) : Iterable < LinkData > {
259
+ const text = document . getText ( ) ;
222
260
for ( const match of text . matchAll ( referenceLinkPattern ) ) {
223
261
let linkStart : vscode . Position ;
224
262
let linkEnd : vscode . Position ;
@@ -237,32 +275,19 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
237
275
continue ;
238
276
}
239
277
240
- try {
241
- const link = definitions . get ( reference ) ;
242
- if ( link ) {
243
- yield new vscode . DocumentLink (
244
- new vscode . Range ( linkStart , linkEnd ) ,
245
- vscode . Uri . parse ( `command:_markdown.moveCursorToPosition? ${ encodeURIComponent ( JSON . stringify ( [ link . linkRange . start . line , link . linkRange . start . character ] ) ) } ` ) ) ;
278
+ yield {
279
+ sourceRange : new vscode . Range ( linkStart , linkEnd ) ,
280
+ sourceResource : document . uri ,
281
+ target : {
282
+ kind : 'reference' ,
283
+ ref : reference ,
246
284
}
247
- } catch ( e ) {
248
- // noop
249
- }
250
- }
251
-
252
- for ( const definition of definitions . values ( ) ) {
253
- try {
254
- const target = parseLink ( document , definition . link ) ;
255
- if ( target ) {
256
- yield createDocumentLink ( definition . linkRange , target ) ;
257
- }
258
- } catch ( e ) {
259
- // noop
260
- }
285
+ } ;
261
286
}
262
287
}
263
288
264
- public getDefinitions ( text : string , document : SkinnyTextDocument ) : Map < string , { readonly link : string ; readonly linkRange : vscode . Range } > {
265
- const out = new Map < string , { link : string ; linkRange : vscode . Range } > ( ) ;
289
+ public * getDefinitionLinks ( document : SkinnyTextDocument ) : Iterable < LinkData > {
290
+ const text = document . getText ( ) ;
266
291
for ( const match of text . matchAll ( definitionPattern ) ) {
267
292
const pre = match [ 1 ] ;
268
293
const reference = match [ 2 ] ;
@@ -272,19 +297,50 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
272
297
if ( angleBracketLinkRe . test ( link ) ) {
273
298
const linkStart = document . positionAt ( offset + 1 ) ;
274
299
const linkEnd = document . positionAt ( offset + link . length - 1 ) ;
275
- out . set ( reference , {
276
- link : link . substring ( 1 , link . length - 1 ) ,
277
- linkRange : new vscode . Range ( linkStart , linkEnd )
278
- } ) ;
300
+ const target = parseLink ( document , link . substring ( 1 , link . length - 1 ) ) ;
301
+ if ( target ) {
302
+ yield {
303
+ sourceResource : document . uri ,
304
+ sourceRange : new vscode . Range ( linkStart , linkEnd ) ,
305
+ target : {
306
+ kind : 'definition' ,
307
+ ref : reference ,
308
+ target
309
+ }
310
+ } ;
311
+ }
279
312
} else {
280
313
const linkStart = document . positionAt ( offset ) ;
281
314
const linkEnd = document . positionAt ( offset + link . length ) ;
282
- out . set ( reference , {
283
- link : link ,
284
- linkRange : new vscode . Range ( linkStart , linkEnd )
285
- } ) ;
315
+ const target = parseLink ( document , link ) ;
316
+ if ( target ) {
317
+ yield {
318
+ sourceResource : document . uri ,
319
+ sourceRange : new vscode . Range ( linkStart , linkEnd ) ,
320
+ target : {
321
+ kind : 'definition' ,
322
+ ref : reference ,
323
+ target,
324
+ }
325
+ } ;
326
+ }
286
327
}
287
328
}
288
- return out ;
329
+ }
330
+ }
331
+
332
+ export class LinkDefinitionSet {
333
+ private readonly _map = new Map < string , LinkData > ( ) ;
334
+
335
+ constructor ( links : Iterable < LinkData > ) {
336
+ for ( const link of links ) {
337
+ if ( link . target . kind === 'definition' ) {
338
+ this . _map . set ( link . target . ref , link ) ;
339
+ }
340
+ }
341
+ }
342
+
343
+ public lookup ( ref : string ) : LinkData | undefined {
344
+ return this . _map . get ( ref ) ;
289
345
}
290
346
}
0 commit comments