1
0
Fork 0
mirror of synced 2024-06-26 02:31:09 -04:00
ultimate-vim/sources_non_forked/coc.nvim/src/model/semanticTokensBuilder.ts
2022-07-20 13:20:15 +08:00

209 lines
6.9 KiB
TypeScript

'use strict'
import { Range, SemanticTokens, SemanticTokensLegend } from "vscode-languageserver-protocol"
function isStringArray(value: any): value is string[] {
return Array.isArray(value) && (value as any[]).every(elem => typeof elem === 'string')
}
function isStrArrayOrUndefined(arg: any): arg is string[] | undefined {
return ((typeof arg === 'undefined') || isStringArray(arg))
}
/**
* A semantic tokens builder can help with creating a `SemanticTokens` instance
* which contains delta encoded semantic tokens.
*/
export class SemanticTokensBuilder {
private _prevLine: number
private _prevChar: number
private _dataIsSortedAndDeltaEncoded: boolean
private _data: number[]
private _dataLen: number
private _tokenTypeStrToInt: Map<string, number>
private _tokenModifierStrToInt: Map<string, number>
private _hasLegend: boolean
constructor(legend?: SemanticTokensLegend) {
this._prevLine = 0
this._prevChar = 0
this._dataIsSortedAndDeltaEncoded = true
this._data = []
this._dataLen = 0
this._tokenTypeStrToInt = new Map<string, number>()
this._tokenModifierStrToInt = new Map<string, number>()
this._hasLegend = false
if (legend) {
this._hasLegend = true
for (let i = 0, len = legend.tokenTypes.length; i < len; i++) {
this._tokenTypeStrToInt.set(legend.tokenTypes[i], i)
}
for (let i = 0, len = legend.tokenModifiers.length; i < len; i++) {
this._tokenModifierStrToInt.set(legend.tokenModifiers[i], i)
}
}
}
/**
* Add another token.
*
* @param line The token start line number (absolute value).
* @param char The token start character (absolute value).
* @param length The token length in characters.
* @param tokenType The encoded token type.
* @param tokenModifiers The encoded token modifiers.
*/
public push(line: number, char: number, length: number, tokenType: number, tokenModifiers?: number): void
/**
* Add another token. Use only when providing a legend.
*
* @param range The range of the token. Must be single-line.
* @param tokenType The token type.
* @param tokenModifiers The token modifiers.
*/
public push(range: Range, tokenType: string, tokenModifiers?: string[]): void
public push(arg0: any, arg1: any, arg2: any, arg3?: any, arg4?: any): void {
if (typeof arg0 === 'number' && typeof arg1 === 'number' && typeof arg2 === 'number' && typeof arg3 === 'number' && (typeof arg4 === 'number' || typeof arg4 === 'undefined')) {
if (typeof arg4 === 'undefined') {
arg4 = 0
}
// 1st overload
return this._pushEncoded(arg0, arg1, arg2, arg3, arg4)
}
if (Range.is(arg0) && typeof arg1 === 'string' && isStrArrayOrUndefined(arg2)) {
// 2nd overload
return this._push(arg0, arg1, arg2)
}
throw new Error('Illegal argument')
}
private _push(range: Range, tokenType: string, tokenModifiers?: string[]): void {
if (!this._hasLegend) {
throw new Error('Legend must be provided in constructor')
}
if (range.start.line !== range.end.line) {
throw new Error('`range` cannot span multiple lines')
}
if (!this._tokenTypeStrToInt.has(tokenType)) {
throw new Error('`tokenType` is not in the provided legend')
}
const line = range.start.line
const char = range.start.character
const length = range.end.character - range.start.character
const nTokenType = this._tokenTypeStrToInt.get(tokenType)!
let nTokenModifiers = 0
if (tokenModifiers) {
for (const tokenModifier of tokenModifiers) {
if (!this._tokenModifierStrToInt.has(tokenModifier)) {
throw new Error('`tokenModifier` is not in the provided legend')
}
const nTokenModifier = this._tokenModifierStrToInt.get(tokenModifier)!
nTokenModifiers |= (1 << nTokenModifier) >>> 0
}
}
this._pushEncoded(line, char, length, nTokenType, nTokenModifiers)
}
private _pushEncoded(line: number, char: number, length: number, tokenType: number, tokenModifiers: number): void {
if (this._dataIsSortedAndDeltaEncoded && (line < this._prevLine || (line === this._prevLine && char < this._prevChar))) {
// push calls were ordered and are no longer ordered
this._dataIsSortedAndDeltaEncoded = false
// Remove delta encoding from data
const tokenCount = (this._data.length / 5) | 0
let prevLine = 0
let prevChar = 0
for (let i = 0; i < tokenCount; i++) {
let line = this._data[5 * i]
let char = this._data[5 * i + 1]
if (line === 0) {
// on the same line as previous token
line = prevLine
char += prevChar
} else {
// on a different line than previous token
line += prevLine
}
this._data[5 * i] = line
this._data[5 * i + 1] = char
prevLine = line
prevChar = char
}
}
let pushLine = line
let pushChar = char
if (this._dataIsSortedAndDeltaEncoded && this._dataLen > 0) {
pushLine -= this._prevLine
if (pushLine === 0) {
pushChar -= this._prevChar
}
}
this._data[this._dataLen++] = pushLine
this._data[this._dataLen++] = pushChar
this._data[this._dataLen++] = length
this._data[this._dataLen++] = tokenType
this._data[this._dataLen++] = tokenModifiers
this._prevLine = line
this._prevChar = char
}
private static _sortAndDeltaEncode(data: number[]): number[] {
let pos: number[] = []
const tokenCount = (data.length / 5) | 0
for (let i = 0; i < tokenCount; i++) {
pos[i] = i
}
pos.sort((a, b) => {
const aLine = data[5 * a]
const bLine = data[5 * b]
if (aLine === bLine) {
const aChar = data[5 * a + 1]
const bChar = data[5 * b + 1]
return aChar - bChar
}
return aLine - bLine
})
const result = new Array<number>(data.length)
let prevLine = 0
let prevChar = 0
for (let i = 0; i < tokenCount; i++) {
const srcOffset = 5 * pos[i]
const line = data[srcOffset + 0]
const char = data[srcOffset + 1]
const length = data[srcOffset + 2]
const tokenType = data[srcOffset + 3]
const tokenModifiers = data[srcOffset + 4]
const pushLine = line - prevLine
const pushChar = (pushLine === 0 ? char - prevChar : char)
const dstOffset = 5 * i
result[dstOffset + 0] = pushLine
result[dstOffset + 1] = pushChar
result[dstOffset + 2] = length
result[dstOffset + 3] = tokenType
result[dstOffset + 4] = tokenModifiers
prevLine = line
prevChar = char
}
return result
}
/**
* Finish and create a `SemanticTokens` instance.
*/
public build(resultId?: string): SemanticTokens {
if (!this._dataIsSortedAndDeltaEncoded) {
return { data: SemanticTokensBuilder._sortAndDeltaEncode(this._data), resultId }
}
return { data: this._data, resultId }
}
}