import ts from 'typescript' ; import { CancellationToken , Range , SemanticTokens , SemanticTokensBuilder } from 'vscode-languageserver' ; import { Document , mapRangeToOriginal } from '../../../lib/documents' ; import { SemanticTokensProvider } from '../../interfaces' ; import { SvelteSnapshotFragment } from '../DocumentSnapshot' ; import { LSAndTSDocResolver } from '../LSAndTSDocResolver' ; import { convertToTextSpan } from '../utils' ; import { isInGeneratedCode } from './utils' ; const CONTENT_LENGTH_LIMIT = 50000 ; export class SemanticTokensProviderImpl implements SemanticTokensProvider { constructor ( private readonly lsAndTsDocResolver ) { } async getSemanticTokens ( textDocument , range ? , cancellationToken ? ) { const { lang , tsDoc } = await this . lsAndTsDocResolver . getLSAndTSDoc ( textDocument ) ; const fragment = await tsDoc . getFragment ( ) ; if ( ( ! range && fragment . text . length > CONTENT_LENGTH_LIMIT ) || cancellationToken ?. isCancellationRequested ) { return null ; } if ( ! textDocument . scriptInfo && ! textDocument . moduleScriptInfo ) { return null ; } const textSpan = range ? convertToTextSpan ( range , fragment ) : { start : 0 , length : tsDoc . parserError ? fragment . text . length : fragment . text . lastIndexOf ( 'return { props:' ) || fragment . text . length } ; const { spans } = lang . getEncodedSemanticClassifications ( tsDoc . filePath , textSpan , ts . SemanticClassificationFormat . TwentyTwenty ) ; const data = [ ] ; let index = 0 ; while ( index < spans . length ) { const generatedOffset = spans [ index ++ ] ; const generatedLength = spans [ index ++ ] ; const encodedClassification = spans [ index ++ ] ; const classificationType = this . getTokenTypeFromClassification ( encodedClassification ) ; if ( classificationType < 0 ) { continue ; } const originalPosition = this . mapToOrigin ( textDocument , fragment , generatedOffset , generatedLength ) ; if ( ! originalPosition ) { continue ; } const [ line , character , length ] = originalPosition ; if ( length <= 0 ) { continue ; } const modifier = this . getTokenModifierFromClassification ( encodedClassification ) ; data . push ( [ line , character , length , classificationType , modifier ] ) ; } const sorted = data . sort ( ( a , b ) => { const [ lineA , charA ] = a ; const [ lineB , charB ] = b ; return lineA - lineB || charA - charB ; } ) ; const builder = new SemanticTokensBuilder ( ) ; sorted . forEach ( ( tokenData ) => builder . push ( ... tokenData ) ) ; return builder . build ( ) ; } private mapToOrigin ( document , fragment , generatedOffset , generatedLength ) { if ( isInGeneratedCode ( fragment . text , generatedOffset , generatedOffset + generatedLength ) ) { return ; } const range = { start : fragment . positionAt ( generatedOffset ) , end : fragment . positionAt ( generatedOffset + generatedLength ) } ; const { start : startPosition , end : endPosition } = mapRangeToOriginal ( fragment , range ) ; if ( startPosition . line < 0 || endPosition . line < 0 ) { return ; } const startOffset = document . offsetAt ( startPosition ) ; const endOffset = document . offsetAt ( endPosition ) ; return [ startPosition . line , startPosition . character , endOffset - startOffset ] ; } /**
     *  TSClassification = (TokenType + 1) << TokenEncodingConsts.typeOffset + TokenModifier
     */ private getTokenTypeFromClassification ( tsClassification ) { return ( tsClassification >> TokenEncodingConsts . typeOffset ) - 1 ; } private getTokenModifierFromClassification ( tsClassification ) { return tsClassification & TokenEncodingConsts . modifierMask ; } } const enum TokenEncodingConsts { typeOffset = 8 , modifierMask = ( 1 << typeOffset ) - 1 }