import * as vscode from "vscode" ; import { CancellationToken , LanguageClient } from "vscode-languageclient" ; export class DocumentLinkProvider implements vscode . DocumentLinkProvider { /**
     * Handles creation of document links (https://code.visualstudio.com/api/references/vscode-api#DocumentLink)
     * for doc roles. Clicking on the target of a document link allows the user to open a text editor with its
     * corresponding file.
     */ private client : LanguageClient ; constructor ( client ) { this . client = client ; } public provideDocumentLinks ( document , token ) { let result = this . _findDocLinks ( document , token ) ; const directives = [ 'image' , 'figure' , 'include' , 'literalinclude' ] ; for ( const directive of directives ) { result = result . concat ( this . _findDirectiveLinks ( document , directive , token ) ) ; } return result ; } public async resolveDocumentLink ( link , token ) { const activeTextEditor = vscode . window . activeTextEditor ; if ( ! activeTextEditor ) { return undefined ; } const document = activeTextEditor . document ; const text = document . getText ( link . range ) ; const type = link . tooltip . indexOf ( 'document' ) > - 1 ? 'doc' : 'directive' ; link . target = await this . _findTargetUri ( document , text , type , token ) ; return link ; } private _findDocLinks ( document , token ) { const docText = document . getText ( ) ; const docRoles = docText . match ( /:doc:`.+?`/gs ) ; if ( docRoles === null ) { return [ ] ; } const doclinks = [ ] ; let docRoleOffsetStart = - 1 ; for ( const docRole of docRoles ) { if ( token . isCancellationRequested ) { return [ ] ; } docRoleOffsetStart = docText . indexOf ( docRole , docRoleOffsetStart + 1 ) ; let targetMatches = docRole . match ( /(?<=<)\S+(?=>)/ ) ; if ( targetMatches === null ) { targetMatches = docRole . match ( /(?<=`)\S+(?=`)/ ) ; } if ( ! targetMatches ) { continue ; } const target = targetMatches [ 0 ] ; const targetIndex = docRole . indexOf ( target ) ; const targetOffsetStart = docRoleOffsetStart + targetIndex ; const targetOffsetEnd = targetOffsetStart + target . length ; doclinks . push ( { range : new vscode . Range ( document . positionAt ( targetOffsetStart ) , document . positionAt ( targetOffsetEnd ) ) , tooltip : 'Click to open this document' } ) ; } return doclinks ; } private _findDirectiveLinks ( document , directive , token ) { const header = `.. ${ directive }::` ; const docText = document . getText ( ) ; const expression = new RegExp ( `${ header }\\s+<?([^\\n]+)>?(\\r)?\\n` , 'gs' ) ; const docRoles = docText . match ( expression ) ; if ( docRoles === null ) { return [ ] ; } const doclinks = [ ] ; let docRoleOffsetStart = - 1 ; for ( const docRole of docRoles ) { if ( token . isCancellationRequested ) { return [ ] ; } docRoleOffsetStart = docText . indexOf ( docRole , docRoleOffsetStart + 1 ) ; let target = docRole . substring ( header . length ) . trim ( ) ; target = target . startsWith ( '<' ) ? target . substring ( 1 , target . length - 1 ) : target ; const targetIndex = docRole . indexOf ( target ) ; const targetOffsetStart = docRoleOffsetStart + targetIndex ; const targetOffsetEnd = targetOffsetStart + target . length ; doclinks . push ( { range : new vscode . Range ( document . positionAt ( targetOffsetStart ) , document . positionAt ( targetOffsetEnd ) ) , tooltip : 'Click to open this file' } ) ; } return doclinks ; } private async _findTargetUri ( document , target , type , token ) { const file = await this . client . sendRequest ( 'textDocument/resolve' , { docPath : document . uri . fsPath , fileName : target , resolveType : type } , token ) ; return vscode . Uri . file ( file ) ; } }