/********************************************************************************
 * Copyright (C) 2020 TypeFox and others.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the Eclipse
 * Public License v. 2.0 are satisfied: GNU General Public License, version 2
 * with the GNU Classpath Exception which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 ********************************************************************************/ import { BinaryBuffer } from '@theia/core/lib/common/buffer' ; import * as theia from '@theia/plugin' ; import { URI } from '@theia/core/shared/vscode-uri' ; import { SemanticTokens , SemanticTokensEdit , SemanticTokensEdits } from '../types-impl' ; import { DocumentsExtImpl } from '../documents' ; import { toRange } from '../type-converters' ; import { encodeSemanticTokensDto } from '../../common/semantic-tokens-dto' ; import { Range } from '../../common/plugin-api-rpc-model' ; class SemanticTokensPreviousResult { constructor ( public readonly resultId , public readonly tokens ? ) { } } type RelaxedSemanticTokens = { readonly resultId ? : string ; readonly data : number [ ] ; } ; type RelaxedSemanticTokensEdit = { readonly start : number ; readonly deleteCount : number ; readonly data ? : number [ ] ; } ; type RelaxedSemanticTokensEdits = { readonly resultId ? : string ; readonly edits : RelaxedSemanticTokensEdit [ ] ; } ; type ProvidedSemanticTokens = theia . SemanticTokens | RelaxedSemanticTokens ; type ProvidedSemanticTokensEdits = theia . SemanticTokensEdits | RelaxedSemanticTokensEdits ; export class DocumentSemanticTokensAdapter { private readonly _previousResults : Map < number , SemanticTokensPreviousResult > ; private _nextResultId = 1 ; constructor ( private readonly _documents , private readonly _provider ) { this . _previousResults = new Map < number , SemanticTokensPreviousResult > ( ) ; } async provideDocumentSemanticTokens ( resource , previousResultId , token ) { const doc = this . _documents . getDocument ( resource ) ; const previousResult = ( previousResultId !== 0 ? this . _previousResults . get ( previousResultId ) : null ) ; let value ; if ( previousResult && typeof previousResult . resultId === 'string' && typeof this . _provider . provideDocumentSemanticTokensEdits === 'function' ) { value = await this . _provider . provideDocumentSemanticTokensEdits ( doc , previousResult . resultId , token ) ; } else { value = await this . _provider . provideDocumentSemanticTokens ( doc , token ) ; } if ( previousResult ) { this . _previousResults . delete ( previousResultId ) ; } if ( ! value ) { return null ; } value = DocumentSemanticTokensAdapter . _fixProvidedSemanticTokens ( value ) ; return this . _send ( DocumentSemanticTokensAdapter . _convertToEdits ( previousResult , value ) , value ) ; } async releaseDocumentSemanticColoring ( semanticColoringResultId ) { this . _previousResults . delete ( semanticColoringResultId ) ; } private static _fixProvidedSemanticTokens ( v ) { if ( DocumentSemanticTokensAdapter . _isSemanticTokens ( v ) ) { if ( DocumentSemanticTokensAdapter . _isCorrectSemanticTokens ( v ) ) { return v ; } return new SemanticTokens ( new Uint32Array ( v . data ) , v . resultId ) ; } else if ( DocumentSemanticTokensAdapter . _isSemanticTokensEdits ( v ) ) { if ( DocumentSemanticTokensAdapter . _isCorrectSemanticTokensEdits ( v ) ) { return v ; } return new SemanticTokensEdits ( v . edits . map ( edit => new SemanticTokensEdit ( edit . start , edit . deleteCount , edit . data ? new Uint32Array ( edit . data ) : edit . data ) ) , v . resultId ) ; } return v ; } private static _isSemanticTokens ( v ) { return v && ! ! ( ( v as ProvidedSemanticTokens ) . data ) ; } private static _isCorrectSemanticTokens ( v ) { return ( v . data instanceof Uint32Array ) ; } private static _isSemanticTokensEdits ( v ) { return v && Array . isArray ( ( v as ProvidedSemanticTokensEdits ) . edits ) ; } private static _isCorrectSemanticTokensEdits ( v ) { for ( const edit of v . edits ) { if ( ! ( edit . data instanceof Uint32Array ) ) { return false ; } } return true ; } private static _convertToEdits ( previousResult , newResult ) { if ( ! DocumentSemanticTokensAdapter . _isSemanticTokens ( newResult ) ) { return newResult ; } if ( ! previousResult || ! previousResult . tokens ) { return newResult ; } const oldData = previousResult . tokens ; const oldLength = oldData . length ; const newData = newResult . data ; const newLength = newData . length ; let commonPrefixLength = 0 ; const maxCommonPrefixLength = Math . min ( oldLength , newLength ) ; while ( commonPrefixLength < maxCommonPrefixLength && oldData [ commonPrefixLength ] === newData [ commonPrefixLength ] ) { commonPrefixLength ++ ; } if ( commonPrefixLength === oldLength && commonPrefixLength === newLength ) { return new SemanticTokensEdits ( [ ] , newResult . resultId ) ; } let commonSuffixLength = 0 ; const maxCommonSuffixLength = maxCommonPrefixLength - commonPrefixLength ; while ( commonSuffixLength < maxCommonSuffixLength && oldData [ oldLength - commonSuffixLength - 1 ] === newData [ newLength - commonSuffixLength - 1 ] ) { commonSuffixLength ++ ; } return new SemanticTokensEdits ( [ { start : commonPrefixLength , deleteCount : ( oldLength - commonPrefixLength - commonSuffixLength ) , data : newData . subarray ( commonPrefixLength , newLength - commonSuffixLength ) } ] , newResult . resultId ) ; } private _send ( value , original ) { if ( DocumentSemanticTokensAdapter . _isSemanticTokens ( value ) ) { const myId = this . _nextResultId ++ ; this . _previousResults . set ( myId , new SemanticTokensPreviousResult ( value . resultId , value . data ) ) ; return encodeSemanticTokensDto ( { id : myId , type : 'full' , data : value . data } ) ; } if ( DocumentSemanticTokensAdapter . _isSemanticTokensEdits ( value ) ) { const myId = this . _nextResultId ++ ; if ( DocumentSemanticTokensAdapter . _isSemanticTokens ( original ) ) { this . _previousResults . set ( myId , new SemanticTokensPreviousResult ( original . resultId , original . data ) ) ; } else { this . _previousResults . set ( myId , new SemanticTokensPreviousResult ( value . resultId ) ) ; } return encodeSemanticTokensDto ( { id : myId , type : 'delta' , deltas : ( value . edits || [ ] ) . map ( edit => ( { start : edit . start , deleteCount : edit . deleteCount , data : edit . data } ) ) } ) ; } return null ; } } export class DocumentRangeSemanticTokensAdapter { constructor ( private readonly _documents , private readonly _provider ) { } async provideDocumentRangeSemanticTokens ( resource , range , token ) { const doc = this . _documents . getDocument ( resource ) ; const value = await this . _provider . provideDocumentRangeSemanticTokens ( doc , toRange ( range ) , token ) ; if ( ! value ) { return null ; } return this . _send ( value ) ; } private _send ( value ) { return encodeSemanticTokensDto ( { id : 0 , type : 'full' , data : value . data } ) ; } }