jvikstrom updated this revision to Diff 216624.
jvikstrom added a comment.
Added missing protected and comment.
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D66219/new/
https://reviews.llvm.org/D66219
Files:
clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts
clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts
clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts
Index: clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts
===================================================================
--- clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts
+++ clang-tools-extra/clangd/clients/clangd-vscode/test/semantic-highlighting.test.ts
@@ -1,5 +1,6 @@
import * as assert from 'assert';
import * as path from 'path';
+import * as vscode from 'vscode';
import * as SM from '../src/semantic-highlighting';
@@ -57,4 +58,71 @@
assert.deepEqual(tm.getBestThemeRule('variable.other.parameter.cpp').scope,
'variable.other.parameter');
});
+ test('Colorizer groups decorations correctly', async () => {
+ // Helper for creating a vscode Range.
+ const createRange = (line: number, startCharacter: number,
+ length: number) =>
+ new vscode.Range(new vscode.Position(line, startCharacter),
+ new vscode.Position(line, startCharacter + length));
+ const scopeTable = [
+ [ 'variable' ], [ 'entity.type.function' ],
+ [ 'entity.type.function.method' ]
+ ];
+ const rules = [
+ {scope : 'variable', foreground : '1'},
+ {scope : 'entity.type', foreground : '2'},
+ ];
+ class MockHighlighter extends SM.Highlighter {
+ applicationUriHistory: string[] = [];
+ applyHighlights(fileUri: string) {
+ this.applicationUriHistory.push(fileUri);
+ }
+ getDecorationRanges(fileUri: string) {
+ return super.getDecorationRanges(fileUri);
+ }
+ getVisibleTextEditorUris() { return [ 'a', 'b' ]; }
+ }
+ const highlighter = new MockHighlighter(scopeTable);
+ const tm = new SM.ThemeRuleMatcher(rules);
+ // Recolorizes when initialized.
+ highlighter.highlight('a', []);
+ assert.deepEqual(highlighter.applicationUriHistory, [ 'a' ]);
+ highlighter.initialize(tm);
+ assert.deepEqual(highlighter.applicationUriHistory, [ 'a', 'a' ]);
+ // Groups decorations into the scopes used.
+ let line = [
+ {character : 1, length : 2, scopeIndex : 1},
+ {character : 5, length : 2, scopeIndex : 1},
+ {character : 10, length : 2, scopeIndex : 2}
+ ];
+ highlighter.highlight(
+ 'a', [ {line : 1, tokens : line}, {line : 2, tokens : line} ]);
+ assert.deepEqual(highlighter.applicationUriHistory, [ 'a', 'a', 'a' ]);
+ assert.deepEqual(highlighter.getDecorationRanges('a'), [
+ [],
+ [
+ createRange(1, 1, 2), createRange(1, 5, 2), createRange(2, 1, 2),
+ createRange(2, 5, 2)
+ ],
+ [ createRange(1, 10, 2), createRange(2, 10, 2) ],
+ ]);
+ // Keeps state separate between files.
+ highlighter.highlight('b', [
+ {line : 1, tokens : [ {character : 1, length : 1, scopeIndex : 0} ]}
+ ]);
+ assert.deepEqual(highlighter.applicationUriHistory, [ 'a', 'a', 'a', 'b' ]);
+ assert.deepEqual(highlighter.getDecorationRanges('b'),
+ [ [ createRange(1, 1, 1) ], [], [] ]);
+ // Does full colorizations.
+ highlighter.highlight('a', [
+ {line : 1, tokens : [ {character : 2, length : 1, scopeIndex : 0} ]}
+ ]);
+ assert.deepEqual(highlighter.applicationUriHistory,
+ [ 'a', 'a', 'a', 'b', 'a' ]);
+ assert.deepEqual(highlighter.getDecorationRanges('a'), [
+ [ createRange(1, 2, 1) ],
+ [ createRange(2, 1, 2), createRange(2, 5, 2) ],
+ [ createRange(2, 10, 2) ],
+ ]);
+ });
});
Index: clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts
===================================================================
--- clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts
+++ clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts
@@ -34,6 +34,13 @@
// The TextMate scope index to the clangd scope lookup table.
scopeIndex: number;
}
+// A line of decoded highlightings from the data clangd sent.
+interface SemanticHighlightingLine {
+ // The zero-based line position in the text document.
+ line: number;
+ // All SemanticHighlightingTokens on the line.
+ tokens: SemanticHighlightingToken[];
+}
// Language server push notification providing the semantic highlighting
// information for a text document.
@@ -49,6 +56,8 @@
scopeLookupTable: string[][];
// The rules for the current theme.
themeRuleMatcher: ThemeRuleMatcher;
+ // The object that applies the highlightings clangd sends.
+ highlighter: Highlighter;
fillClientCapabilities(capabilities: vscodelc.ClientCapabilities) {
// Extend the ClientCapabilities type and add semantic highlighting
// capability to the object.
@@ -64,6 +73,7 @@
this.themeRuleMatcher = new ThemeRuleMatcher(
await loadTheme(vscode.workspace.getConfiguration('workbench')
.get<string>('colorTheme')));
+ this.highlighter.initialize(this.themeRuleMatcher);
}
initialize(capabilities: vscodelc.ServerCapabilities,
@@ -76,10 +86,18 @@
if (!serverCapabilities.semanticHighlighting)
return;
this.scopeLookupTable = serverCapabilities.semanticHighlighting.scopes;
+ // Important that highlighter is created before the theme is loading as
+ // otherwise it could try to update the themeRuleMatcher without the
+ // highlighter being created.
+ this.highlighter = new Highlighter(this.scopeLookupTable);
this.loadCurrentTheme();
}
- handleNotification(params: SemanticHighlightingParams) {}
+ handleNotification(params: SemanticHighlightingParams) {
+ const lines: SemanticHighlightingLine[] = params.lines.map(
+ (line) => ({line : line.line, tokens : decodeTokens(line.tokens)}));
+ this.highlighter.highlight(params.textDocument.uri, lines);
+ }
}
// Converts a string of base64 encoded tokens into the corresponding array of
@@ -101,6 +119,89 @@
return retTokens;
}
+// The main class responsible for processing of highlightings that clangd
+// sends.
+export class Highlighter {
+ // Maps uris with currently open TextDocuments to the current highlightings.
+ private files: Map<string, Map<number, SemanticHighlightingLine>> = new Map();
+ // DecorationTypes for the current theme that are used when highlighting.
+ private decorationTypes: vscode.TextEditorDecorationType[];
+ // The clangd TextMate scope lookup table.
+ private scopeLookupTable: string[][];
+ constructor(scopeLookupTable: string[][]) {
+ this.scopeLookupTable = scopeLookupTable;
+ }
+ // Update the themeRuleMatcher that is used when highlighting. Also triggers a
+ // recolorization for all current highlighters. Safe to call multiple times.
+ public initialize(themeRuleMatcher: ThemeRuleMatcher) {
+ this.decorationTypes = this.scopeLookupTable.map((scopes) => {
+ const options: vscode.DecorationRenderOptions = {
+ color : themeRuleMatcher.getBestThemeRule(scopes[0]).foreground,
+ // If the rangeBehavior is set to Open in any direction the
+ // highlighting becomes weird in certain cases.
+ rangeBehavior : vscode.DecorationRangeBehavior.ClosedClosed,
+ };
+ return vscode.window.createTextEditorDecorationType(options);
+ });
+ this.getVisibleTextEditorUris().forEach((fileUri) => {
+ // A TextEditor might not be a cpp file. So we must check we have
+ // highlightings for the file before applying them.
+ if (this.files.has(fileUri))
+ this.applyHighlights(fileUri);
+ })
+ }
+
+ // Adds incremental highlightings to the current highlightings for the file
+ // with fileUri. Also applies the highlightings to any associated
+ // TextEditor(s).
+ public highlight(fileUri: string, tokens: SemanticHighlightingLine[]) {
+ if (!this.files.has(fileUri)) {
+ this.files.set(fileUri, new Map());
+ }
+ const fileHighlightings = this.files.get(fileUri);
+ tokens.forEach((line) => fileHighlightings.set(line.line, line));
+ this.applyHighlights(fileUri);
+ }
+
+ // Exists to make the initialize method testable.
+ protected getVisibleTextEditorUris() {
+ return vscode.window.visibleTextEditors.map((e) =>
+ e.document.uri.toString());
+ }
+
+ // Returns the ranges that should be used when decorating. Index i in the
+ // range array has the decoration type at index i of this.decorationTypes.
+ protected getDecorationRanges(fileUri: string): vscode.Range[][] {
+ const lines: SemanticHighlightingLine[] =
+ Array.from(this.files.get(fileUri).values());
+ const decorations: vscode.Range[][] = this.decorationTypes.map(() => []);
+ lines.forEach((line) => {
+ line.tokens.forEach((token) => {
+ decorations[token.scopeIndex].push(new vscode.Range(
+ new vscode.Position(line.line, token.character),
+ new vscode.Position(line.line, token.character + token.length)));
+ });
+ });
+ return decorations;
+ }
+
+ // Applies all the highlightings currently stored for a file with fileUri.
+ protected applyHighlights(fileUri: string) {
+ if (!this.decorationTypes)
+ return;
+ // This must always do a full re-highlighting due to the fact that
+ // TextEditorDecorationType are very expensive to create (which makes
+ // incremental updates infeasible). For this reason one
+ // TextEditorDecorationType is used per scope.
+ const ranges = this.getDecorationRanges(fileUri);
+ vscode.window.visibleTextEditors.forEach((e) => {
+ if (e.document.uri.toString() !== fileUri)
+ return;
+ this.decorationTypes.forEach((d, i) => e.setDecorations(d, ranges[i]));
+ });
+ }
+}
+
// A rule for how to color TextMate scopes.
interface TokenColorRule {
// A TextMate scope that specifies the context of the token, e.g.
Index: clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts
===================================================================
--- clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts
+++ clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts
@@ -1,6 +1,6 @@
import * as vscode from 'vscode';
import * as vscodelc from 'vscode-languageclient';
-
+import * as SM from './semantic-highlighting';
/**
* Method to get workspace configuration option
* @param option name of the option (e.g. for clangd.path should be path)
@@ -91,6 +91,16 @@
const clangdClient = new vscodelc.LanguageClient(
'Clang Language Server', serverOptions, clientOptions);
+ const semanticHighlightingFeature = new SM.SemanticHighlightingFeature();
+ clangdClient.registerFeature(semanticHighlightingFeature);
+ // The notification handler must be registered after the client is ready or
+ // the client will crash.
+ clangdClient.onReady().then(
+ () => clangdClient.onNotification(
+ SM.NotificationType,
+ semanticHighlightingFeature.handleNotification.bind(
+ semanticHighlightingFeature)));
+
console.log('Clang Language Server is now active!');
context.subscriptions.push(clangdClient.start());
context.subscriptions.push(vscode.commands.registerCommand(
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits