diff --git a/clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts b/clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts --- a/clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts +++ b/clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts @@ -2,6 +2,89 @@ import * as jsonc from "jsonc-parser"; import * as path from 'path'; import * as vscode from 'vscode'; +import * as vscodelc from 'vscode-languageclient'; + +// The information clangd sends when highlightings should be updated. +interface SemanticHighlightingParams { + // The information about the text document where these highlightings should be + // applied. + textDocument: {uri: string;}; + // All the lines of highlightings that should be applied. + lines: [ { + line: number; + tokens: string; + } ]; +} +// A single SemanticHighlightingToken that clangd sent. +interface SemanticHighlightingToken { + // Start column for this token. + character: number; + // The TextMate scope index to the clangd scopes. + scope: number; + length: number; +} +// All highlightings for line. +interface SemanticHighlightingInformation { + line: number; + tokens: SemanticHighlightingToken[]; +} + +export const NotificationType = new vscodelc.NotificationType<{}, void>( + 'textDocument/semanticHighlighting'); + +// The feature that should be registered in the vscode lsp for enabling +// experimental semantic highlighting. +export class SemanticHighlightingFeature implements vscodelc.StaticFeature { + scopeLookupTable: string[][]; + fillClientCapabilities(capabilities: vscodelc.ClientCapabilities) { + // Extend the ClientCapabilities type and add semantic highlighting + // capability to the object. + const textDocumentCapabilities: vscodelc.TextDocumentClientCapabilities& + {semanticHighlightingCapabilities?: {semanticHighlighting : boolean}} = + capabilities.textDocument; + textDocumentCapabilities.semanticHighlightingCapabilities = { + semanticHighlighting : true, + }; + } + + initialize(capabilities: vscodelc.ServerCapabilities, + documentSelector: vscodelc.DocumentSelector|undefined) { + // The semantic highlighting capability information is in the capabilities + // object but to access the data we must first extend the ServerCapabilities + // type. + const serverCapabilities: vscodelc.ServerCapabilities& + {semanticHighlighting?: {scopes : string[][]}} = capabilities; + if (!serverCapabilities.semanticHighlighting) + return; + this.scopeLookupTable = serverCapabilities.semanticHighlighting.scopes; + } + + handleNotification(params: SemanticHighlightingParams) { + const tokenLines = + params.lines.map((line): SemanticHighlightingInformation => { + return { line: line.line, tokens: decodeTokens(line.tokens), } + }); + } +} + +// Converts a string of base64 encoded tokens into the corresponding array of +// HighlightingTokens. +export function decodeTokens(tokens: string): SemanticHighlightingToken[] { + const scopeMask = 0xFFFF; + const lenShift = 0x10; + const uint32Size = 4; + const buf = Buffer.from(tokens, 'base64'); + const retTokens = []; + for (let i = 0, end = buf.length / uint32Size; i < end; i += 2) { + const start = buf.readUInt32BE(i * uint32Size); + const lenKind = buf.readUInt32BE((i + 1) * uint32Size); + const scope = lenKind & scopeMask; + const len = lenKind >>> lenShift; + retTokens.push({character : start, scope : scope, length : len}); + } + + return retTokens; +} // A rule for how to color TextMate scopes. interface TokenColorRule { diff --git a/clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts b/clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts --- a/clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts +++ b/clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts @@ -3,7 +3,7 @@ import * as TM from '../src/semantic-highlighting'; -suite('TextMate Tests', () => { +suite('SemanticHighlighting Tests', () => { test('Parses arrays of textmate themes.', async () => { const themePath = path.join(__dirname, '../../test/assets/includeTheme.jsonc'); @@ -15,4 +15,24 @@ assert.deepEqual(getScopeRule('b'), {scope : 'b', textColor : '#000'}); assert.deepEqual(getScopeRule('c'), {scope : 'c', textColor : '#bcd'}); }); + test('Decodes tokens correctly', () => { + const testCases: string[] = [ + 'AAAAAAABAAA=', 'AAAAAAADAAkAAAAEAAEAAA==', + 'AAAAAAADAAkAAAAEAAEAAAAAAAoAAQAA' + ]; + const expected = [ + [ {character : 0, scope : 0, length : 1} ], + [ + {character : 0, scope : 9, length : 3}, + {character : 4, scope : 0, length : 1} + ], + [ + {character : 0, scope : 9, length : 3}, + {character : 4, scope : 0, length : 1}, + {character : 10, scope : 0, length : 1} + ] + ]; + testCases.forEach((testCase, i) => assert.deepEqual( + TM.decodeTokens(testCase), expected[i])); + }); });