import { dirname , join } from 'path' ; import * as vscode from 'vscode' ; import { Extension } from '../../extension' ; const foreSearchChars = 800 ; const tblPrintLines = 3 ; interface ParseContext { lineMatch : RegExpExecArray ; type : string ; label : string ; doc : vscode . TextDocument ; } export class Crossref { extension : Extension ; lineRegex : RegExp ; regexes : { fig : RegExp ; eq : RegExp ; sec : RegExp ; tbl : RegExp ; lst : RegExp ; } ; constructor ( extension ) { this . extension = extension ; this . lineRegex = /.*\{.*#(fig|tbl|eq|sec|lst):(\w+).*\}.*/g ; this . regexes = { fig : /!\[(.*)\]\((.+)\)\{/ , eq : /\$\$\s*\{/ , sec : /#*\s*(.*)\s*\{/ , tbl : /:\s*(.*)\s*\{/ , lst : /```.*\{/ , } ; } private parseFig ( ctx ) { let match = this . regexes . fig . exec ( ctx . lineMatch . toString ( ) ) ; if ( ! match ) return null ; const title = match [ 1 ] ; const documentation = new vscode . MarkdownString ( `![](${ join ( dirname ( ctx . doc . fileName ) , match [ 2 ] ) }|"width=300")` ) ; return { label : `${ ctx . type }:${ ctx . label }` , detail : title , documentation , kind : vscode . CompletionItemKind . Reference , } ; } private parseSec ( ctx ) { let match = this . regexes . sec . exec ( ctx . lineMatch . toString ( ) ) ; if ( ! match ) return null ; const title = match [ 1 ] ; return { label : `${ ctx . type }:${ ctx . label }` , detail : title , kind : vscode . CompletionItemKind . Reference , } ; } private parseTbl ( ctx ) { let match = this . regexes . tbl . exec ( ctx . lineMatch . toString ( ) ) ; if ( ! match ) return null ; const title = match [ 1 ] ; let documentation = new vscode . MarkdownString ( '' ) ; const doc = ctx . doc . getText ( ) ; const searchStart = Math . max ( ctx . lineMatch . index - foreSearchChars , 0 ) ; const sig = doc . substring ( searchStart , ctx . lineMatch . index ) . lastIndexOf ( '|:-' ) ; if ( sig > 0 ) { const begin = doc . lastIndexOf ( '\n\n' , sig + searchStart ) ; let cursor = begin ; for ( let i = 0 ; i < tblPrintLines + 2 ; i ++ ) { const pos = doc . indexOf ( '\n' , cursor + 1 ) ; if ( pos < 0 ) break ; else cursor = pos ; } documentation = new vscode . MarkdownString ( doc . substring ( begin + 2 , cursor ) ) ; } return { label : `${ ctx . type }:${ ctx . label }` , detail : title , documentation , kind : vscode . CompletionItemKind . Reference , } ; } private parseLst ( ctx ) { let match = this . regexes . lst . exec ( ctx . lineMatch . toString ( ) ) ; if ( ! match ) return null ; let documentation = new vscode . MarkdownString ( '' ) ; const doc = ctx . doc . getText ( ) ; const end = doc . indexOf ( '```' , ctx . lineMatch . index + 1 ) ; const langMatch = /\{.*\.(\w+).*\}/ . exec ( ctx . lineMatch . toString ( ) ) ; const lang = langMatch ? langMatch [ 1 ] : '' ; const titleMatch = /\{.*caption="(.+)".*\}/ . exec ( ctx . lineMatch . toString ( ) ) ; const title = titleMatch ? titleMatch [ 1 ] : ctx . label ; if ( end > 0 ) { documentation = new vscode . MarkdownString ( '``` ' + lang + ' ' + doc . substring ( ctx . lineMatch . index + ctx . lineMatch [ 0 ] . length , end ) + '\n```' ) ; } return { label : `${ ctx . type }:${ ctx . label }` , documentation , detail : title , kind : vscode . CompletionItemKind . Reference , } ; } private parseEq ( ctx ) { let match = this . regexes . eq . exec ( ctx . lineMatch . toString ( ) ) ; if ( ! match ) return null ; const doc = ctx . doc . getText ( ) ; const matchIndex = ctx . lineMatch . index + match . index ; const searchStart = Math . max ( matchIndex - foreSearchChars , 0 ) ; const begin = doc . substring ( searchStart , matchIndex ) . lastIndexOf ( '$$' ) + searchStart ; if ( begin < 0 ) { return null ; } return { label : `${ ctx . type }:${ ctx . label }` , detail : ctx . label , documentation : new vscode . MarkdownString ( doc . substring ( begin + 2 , matchIndex ) ) , kind : vscode . CompletionItemKind . Reference , } ; } provide ( args ? ) { const mode = vscode . workspace . getConfiguration ( 'PandocCiter' ) . get ( 'CrossRefMode' , 'full' ) ; if ( mode === 'none' ) return [ ] ; const targets = [ ] ; let match ; const parsers = { fig : this . parseFig . bind ( this ) , sec : this . parseSec . bind ( this ) , tbl : this . parseTbl . bind ( this ) , lst : this . parseLst . bind ( this ) , eq : this . parseEq . bind ( this ) , } ; while ( ( match = this . lineRegex . exec ( args . document . getText ( ) ) ) !== null ) { const type = match [ 1 ] ; const label = match [ 2 ] ; switch ( mode ) { case 'full' : const parser = parsers [ type ] ; if ( parser ) { let item = parser ( { lineMatch : match , type , label , doc : args . document , } ) ; if ( item ) { targets . push ( item ) ; } } break ; case 'minimal' : targets . push ( { label : `${ match [ 1 ] }:${ match [ 2 ] }` , kind : vscode . CompletionItemKind . Reference , } ) ; break ; } } return targets ; } }