license import { ILinkProvider , ILink , Terminal , IViewportRange } from 'xterm' ; interface ILinkProviderOptions { hover ? ( event , text , location ) : void ; leave ? ( event , text ) : void ; } export class WebLinkProvider implements ILinkProvider { constructor ( private readonly _terminal , private readonly _regex , private readonly _handler , private readonly _options = { } ) { } public provideLinks ( y , callback ) { const links = LinkComputer . computeLink ( y , this . _regex , this . _terminal , this . _handler ) ; callback ( this . _addCallbacks ( links ) ) ; } private _addCallbacks ( links ) { return links . map ( link => { link . leave = this . _options . leave ; link . hover = ( event , uri ) : void => { if ( this . _options . hover ) { const { range } = link ; this . _options . hover ( event , uri , range ) ; } } ; return link ; } ) ; } } export class LinkComputer { public static computeLink ( y , regex , terminal , activate ) { const rex = new RegExp ( regex . source , ( regex . flags || '' ) + 'g' ) ; const [ line , startLineIndex ] = LinkComputer . _translateBufferLineToStringWithWrap ( y - 1 , false , terminal ) ; let match ; let stringIndex = - 1 ; const result = [ ] ; while ( ( match = rex . exec ( line ) ) !== null ) { const text = match [ 1 ] ; if ( ! text ) { console . log ( 'match found without corresponding matchIndex' ) ; break ; } stringIndex = line . indexOf ( text , stringIndex + 1 ) ; rex . lastIndex = stringIndex + text . length ; if ( stringIndex < 0 ) { break ; } let endX = stringIndex + text . length ; let endY = startLineIndex + 1 ; while ( endX > terminal . cols ) { endX -= terminal . cols ; endY ++ ; } const range = { start : { x : stringIndex + 1 , y : startLineIndex + 1 } , end : { x : endX , y : endY } } ; result . push ( { range , text , activate } ) ; } return result ; } param line param trimRight param terminal private static _translateBufferLineToStringWithWrap ( lineIndex , trimRight , terminal ) { let lineString = '' ; let lineWrapsToNext ; let prevLinesToWrap ; do { const line = terminal . buffer . active . getLine ( lineIndex ) ; if ( ! line ) { break ; } if ( line . isWrapped ) { lineIndex -- ; } prevLinesToWrap = line . isWrapped ; } while ( prevLinesToWrap ) ; const startLineIndex = lineIndex ; do { const nextLine = terminal . buffer . active . getLine ( lineIndex + 1 ) ; lineWrapsToNext = nextLine ? nextLine . isWrapped : false ; const line = terminal . buffer . active . getLine ( lineIndex ) ; if ( ! line ) { break ; } lineString += line . translateToString ( ! lineWrapsToNext && trimRight ) . substring ( 0 , terminal . cols ) ; lineIndex ++ ; } while ( lineWrapsToNext ) ; return [ lineString , startLineIndex ] ; } }