summaryrefslogtreecommitdiffstats
path: root/clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts
diff options
context:
space:
mode:
Diffstat (limited to 'clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts')
-rw-r--r--clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts118
1 files changed, 114 insertions, 4 deletions
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
index a403db2c75b..21bce9cac45 100644
--- a/clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts
+++ b/clang-tools-extra/clangd/clients/clangd-vscode/src/semantic-highlighting.ts
@@ -34,6 +34,13 @@ interface SemanticHighlightingToken {
// The TextMate scope index to the clangd scope lookup table.
scopeIndex: number;
}
+// A line of decoded highlightings from the data clangd sent.
+export 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.
@@ -47,8 +54,8 @@ export class SemanticHighlightingFeature implements vscodelc.StaticFeature {
// The TextMate scope lookup table. A token with scope index i has the scopes
// on index i in the lookup table.
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.
@@ -61,9 +68,10 @@ export class SemanticHighlightingFeature implements vscodelc.StaticFeature {
}
async loadCurrentTheme() {
- this.themeRuleMatcher = new ThemeRuleMatcher(
+ const themeRuleMatcher = new ThemeRuleMatcher(
await loadTheme(vscode.workspace.getConfiguration('workbench')
.get<string>('colorTheme')));
+ this.highlighter.initialize(themeRuleMatcher);
}
initialize(capabilities: vscodelc.ServerCapabilities,
@@ -76,10 +84,18 @@ export class SemanticHighlightingFeature implements vscodelc.StaticFeature {
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 +117,100 @@ export function decodeTokens(tokens: string): SemanticHighlightingToken[] {
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. A
+ // SemanticHighlightingToken with scopeIndex i should have the decoration at
+ // index i in this list.
+ private decorationTypes: vscode.TextEditorDecorationType[] = [];
+ // The clangd TextMate scope lookup table.
+ private scopeLookupTable: string[][];
+ constructor(scopeLookupTable: string[][]) {
+ this.scopeLookupTable = scopeLookupTable;
+ }
+ // This function must be called at least once or no highlightings will be
+ // done. Sets the theme that is used when highlighting. Also triggers a
+ // recolorization for all current highlighters. Should be called whenever the
+ // theme changes and has been loaded. Should also be called when the first
+ // theme is loaded.
+ public initialize(themeRuleMatcher: ThemeRuleMatcher) {
+ this.decorationTypes.forEach((t) => t.dispose());
+ this.decorationTypes = this.scopeLookupTable.map((scopes) => {
+ const options: vscode.DecorationRenderOptions = {
+ // If there exists no rule for this scope the matcher returns an empty
+ // color. That's ok because vscode does not do anything when applying
+ // empty decorations.
+ 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,
+ highlightingLines: SemanticHighlightingLine[]) {
+ if (!this.files.has(fileUri)) {
+ this.files.set(fileUri, new Map());
+ }
+ const fileHighlightings = this.files.get(fileUri);
+ highlightingLines.forEach((line) => fileHighlightings.set(line.line, line));
+ this.applyHighlights(fileUri);
+ }
+
+ // Gets the uris as strings for the currently visible text editors.
+ protected getVisibleTextEditorUris(): string[] {
+ 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.length)
+ // Can't apply any decorations when there is no theme loaded.
+ 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.
OpenPOWER on IntegriCloud