diff --git a/Extension/package.json b/Extension/package.json index 3de5ed6740..f69f9662b8 100644 --- a/Extension/package.json +++ b/Extension/package.json @@ -12,7 +12,7 @@ }, "license": "SEE LICENSE IN LICENSE.txt", "engines": { - "vscode": "^1.43.0" + "vscode": "^1.44.0" }, "bugs": { "url": "https://github.com/Microsoft/vscode-cpptools/issues", @@ -1539,16 +1539,108 @@ "configurationDefaults": { "[cpp]": { "editor.wordBasedSuggestions": false, - "editor.suggest.insertMode": "replace" + "editor.suggest.insertMode": "replace", + "editor.semanticHighlighting.enabled": true }, "[c]": { "editor.wordBasedSuggestions": false, - "editor.suggest.insertMode": "replace" + "editor.suggest.insertMode": "replace", + "editor.semanticHighlighting.enabled": true }, "[Log]": { "editor.wordWrap": "off" } - } + }, + "semanticTokenTypes": [ + { + "id": "referenceType", + "superType": "class", + "description": "A C++/CLI reference type." + }, + { + "id": "cliProperty", + "superType": "property", + "description": "A C++/CLI property." + }, + { + "id": "genericType", + "superType": "class", + "description": "A C++/CLI generic type." + }, + { + "id": "valueType", + "superType": "class", + "description": "A C++/CLI value type." + }, + { + "id": "templateFunction", + "superType": "function", + "description": "A template function." + }, + { + "id": "templateType", + "superType": "class", + "description": "A template type." + }, + { + "id": "operatorOverload", + "superType": "operator", + "description": "An overloaded operator." + }, + { + "id": "memberOperatorOverload", + "superType": "operator", + "description": "An overloaded operator member function." + }, + { + "id": "newOperator", + "superType": "operator", + "description": "A C++ new or delete operator." + }, + { + "id": "customLiteral", + "superType": "number", + "description": "A user-defined literal." + }, + { + "id": "numberLiteral", + "superType": "number", + "description": "A user-defined literal number." + }, + { + "id": "stringLiteral", + "superType": "string", + "description": "A user-defined literal string." + } + ], + "semanticTokenModifiers": [ + { + "id": "global", + "description": "Annotates a symbol that is declared in global scope." + }, + { + "id": "local", + "description": "Annotates a symbol that is declared in local scope." + } + ], + "semanticTokenScopes": [ + { + "scopes": { + "referenceType": [ "entity.name.type.class.reference" ], + "cliProperty": [ "variable.other.property.cli" ], + "genericType": [ "entity.name.type.class.generic" ], + "valueType": [ "entity.name.type.class.value" ], + "templateFunction": [ "entity.name.function.templated" ], + "templateType": [ "entity.name.type.class.templated" ], + "operatorOverload": [ "entity.name.function.operator" ], + "memberOperatorOverload": [ "entity.name.function.operator.member" ], + "newOperator": [ "keyword.operator.new" ], + "numberLiteral": [ "entity.name.operator.custom-literal.number" ], + "customLiteral": [ "entity.name.operator.custom-literal" ], + "stringLiteral": [ "entity.name.operator.custom-literal.string" ] + } + } + ] }, "scripts": { "vscode:prepublish": "yarn run compile", @@ -1579,7 +1671,7 @@ "@types/plist": "^3.0.2", "@types/semver": "^7.1.0", "@types/tmp": "^0.1.0", - "@types/vscode": "1.43.0", + "@types/vscode": "1.44.0", "@types/webpack": "^4.39.0", "@types/which": "^1.3.2", "@types/yauzl": "^2.9.1", diff --git a/Extension/package.nls.json b/Extension/package.nls.json index e9925dde32..8e8228a00c 100644 --- a/Extension/package.nls.json +++ b/Extension/package.nls.json @@ -64,7 +64,7 @@ "c_cpp.configuration.updateChannel.description": "Set to \"Insiders\" to automatically download and install the latest Insiders builds of the extension, which include upcoming features and bug fixes.", "c_cpp.configuration.experimentalFeatures.description": "Controls whether \"experimental\" features are usable.", "c_cpp.configuration.suggestSnippets.description": "If true, snippets are provided by the language server.", - "c_cpp.configuration.enhancedColorization.description": "If enabled, code is colorized based on IntelliSense. This setting has no effect if IntelliSense is disabled or if using the Default High Contrast theme.", + "c_cpp.configuration.enhancedColorization.description": "If enabled, code is colorized based on IntelliSense. This setting only applies if intelliSenseEngine is set to \"Default\".", "c_cpp.configuration.codeFolding.description": "If enabled, code folding ranges are provided by the language server.", "c_cpp.configuration.vcpkg.enabled.markdownDescription": "Enable integration services for the [vcpkg dependency manager](https://aka.ms/vcpkg/).", "c_cpp.configuration.renameRequiresIdentifier.description": "If true, 'Rename Symbol' will require a valid C/C++ identifier.", diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index d7ce8003dd..220281edbe 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -30,7 +30,6 @@ import { getCustomConfigProviders, CustomConfigurationProvider1, isSameProviderE import { ABTestSettings, getABTestSettings } from '../abTesting'; import * as fs from 'fs'; import * as os from 'os'; -import { TokenKind, ColorizationSettings, ColorizationState } from './colorization'; import * as refs from './references'; import * as nls from 'vscode-nls'; import { lookupString, localizedStringCount } from '../nativeStrings'; @@ -54,17 +53,12 @@ let diagnosticsChannel: vscode.OutputChannel; let outputChannel: vscode.OutputChannel; let debugChannel: vscode.OutputChannel; let diagnosticsCollection: vscode.DiagnosticCollection; -const workspaceColorizationState: Map = new Map(); let workspaceDisposables: vscode.Disposable[] = []; let workspaceReferences: refs.ReferencesManager; export function disposeWorkspaceData(): void { workspaceDisposables.forEach((d) => d.dispose()); workspaceDisposables = []; - - workspaceColorizationState.forEach(colorizationState => { - colorizationState.dispose(); - }); } function logTelemetry(notificationBody: TelemetryPayload): void { @@ -141,28 +135,6 @@ function publishDiagnostics(params: PublishDiagnosticsParams): void { diagnosticsCollection.set(realUri, diagnostics); } -function updateSemanticColorizationRegions(params: SemanticColorizationRegionsParams): void { - const colorizationState: ColorizationState | undefined = workspaceColorizationState.get(params.uri); - if (colorizationState) { - // Convert the params to vscode.Range's before passing to colorizationState.updateSemantic() - const semanticRanges: vscode.Range[][] = new Array(TokenKind.Count); - for (let i: number = 0; i < TokenKind.Count; i++) { - semanticRanges[i] = []; - } - params.regions.forEach(element => { - const newRange: vscode.Range = new vscode.Range(element.range.start.line, element.range.start.character, element.range.end.line, element.range.end.character); - semanticRanges[element.kind].push(newRange); - }); - const inactiveRanges: vscode.Range[] = []; - params.inactiveRegions.forEach(element => { - const newRange: vscode.Range = new vscode.Range(element.startLine, 0, element.endLine, 0); - inactiveRanges.push(newRange); - }); - colorizationState.updateSemantic(params.uri, semanticRanges, inactiveRanges, params.editVersion); - languageClient.sendNotification(SemanticColorizationRegionsReceiptNotification, { uri: params.uri }); - } -} - interface WorkspaceFolderParams { workspaceFolderUri?: string; } @@ -204,21 +176,20 @@ interface FileChangedParams extends WorkspaceFolderParams { uri: string; } -interface SemanticColorizationRegionsParams { - uri: string; - regions: InputColorizationRegion[]; - inactiveRegions: InputRegion[]; - editVersion: number; -} - interface InputRegion { startLine: number; endLine: number; } -interface InputColorizationRegion { - range: Range; - kind: number; +interface DecorationRangesPair { + decoration: vscode.TextEditorDecorationType; + ranges: vscode.Range[]; +} + +interface InactiveRegionParams { + uri: string; + fileVersion: number; + regions: InputRegion[]; } // Need to convert vscode.Uri to a string before sending it to the language server. @@ -251,19 +222,6 @@ interface GetDiagnosticsResult { diagnostics: string; } -interface DidChangeVisibleRangesParams { - uri: string; - ranges: Range[]; -} - -interface SemanticColorizationRegionsReceiptParams { - uri: string; -} - -interface ColorThemeChangedParams { - name: string; -} - interface Diagnostic { range: Range; code?: number | string; @@ -343,7 +301,7 @@ interface GetFoldingRangesParams { id: number; } -export enum FoldingRangeKind { +enum FoldingRangeKind { None = 0, Comment = 1, Imports = 2, @@ -353,7 +311,7 @@ export enum FoldingRangeKind { interface FoldingRange { kind: FoldingRangeKind; range: Range; -}; +} interface GetFoldingRangesResult { canceled: boolean; @@ -364,6 +322,62 @@ interface AbortRequestParams { id: number; } +interface GetSemanticTokensParams { + uri: string; + id: number; +} + +interface SemanticToken { + line: number; + character: number; + length: number; + type: number; + modifiers?: number; +} + +interface GetSemanticTokensResult { + fileVersion: number; + canceled: boolean; + tokens: SemanticToken[]; +} + +enum SemanticTokenTypes { + // These are camelCase as the enum names are used directly as strings in our legend. + macro = 0, + enumMember = 1, + variable = 2, + parameter = 3, + type = 4, + referenceType = 5, + valueType = 6, + function = 7, + member = 8, + property = 9, + cliProperty = 10, + event = 11, + genericType = 12, + templateFunction = 13, + templateType = 14, + namespace = 15, + label = 16, + customLiteral = 17, + numberLiteral = 18, + stringLiteral = 19, + operatorOverload = 20, + memberOperatorOverload = 21, + newOperator = 22 +} + +enum SemanticTokenModifiers { + // These are camelCase as the enum names are used directly as strings in our legend. + // eslint-disable-next-line no-bitwise + static = (1 << 0), + // eslint-disable-next-line no-bitwise + global = (1 << 1), + // eslint-disable-next-line no-bitwise + local = (1 << 2) +} + // Requests const QueryCompilerDefaultsRequest: RequestType = new RequestType('cpptools/queryCompilerDefaults'); const QueryTranslationUnitSourceRequest: RequestType = new RequestType('cpptools/queryTranslationUnitSource'); @@ -373,6 +387,7 @@ const GetCodeActionsRequest: RequestType = new RequestType('cpptools/getDocumentSymbols'); const GetSymbolInfoRequest: RequestType = new RequestType('cpptools/getWorkspaceSymbols'); const GetFoldingRangesRequest: RequestType = new RequestType('cpptools/getFoldingRanges'); +const GetSemanticTokensRequest: RequestType = new RequestType('cpptools/getSemanticTokens'); // Notifications to the server const DidOpenNotification: NotificationType = new NotificationType('textDocument/didOpen'); @@ -393,9 +408,6 @@ const CustomBrowseConfigurationNotification: NotificationType = new NotificationType('cpptools/clearCustomConfigurations'); const ClearCustomBrowseConfigurationNotification: NotificationType = new NotificationType('cpptools/clearCustomBrowseConfiguration'); const RescanFolderNotification: NotificationType = new NotificationType('cpptools/rescanFolder'); -const DidChangeVisibleRangesNotification: NotificationType = new NotificationType('cpptools/didChangeVisibleRanges'); -const SemanticColorizationRegionsReceiptNotification: NotificationType = new NotificationType('cpptools/semanticColorizationRegionsReceipt'); -const ColorThemeChangedNotification: NotificationType = new NotificationType('cpptools/colorThemeChanged'); const RequestReferencesNotification: NotificationType = new NotificationType('cpptools/requestReferences'); const CancelReferencesNotification: NotificationType = new NotificationType('cpptools/cancelReferences'); const FinishedRequestCustomConfig: NotificationType = new NotificationType('cpptools/finishedRequestCustomConfig'); @@ -411,7 +423,7 @@ const ReportTagParseStatusNotification: NotificationType = new NotificationType('cpptools/reportStatus'); const DebugProtocolNotification: NotificationType = new NotificationType('cpptools/debugProtocol'); const DebugLogNotification: NotificationType = new NotificationType('cpptools/debugLog'); -const SemanticColorizationRegionsNotification: NotificationType = new NotificationType('cpptools/semanticColorizationRegions'); +const InactiveRegionNotification: NotificationType = new NotificationType('cpptools/inactiveRegions'); const CompileCommandsPathsNotification: NotificationType = new NotificationType('cpptools/compileCommandsPaths'); const ReferencesNotification: NotificationType = new NotificationType('cpptools/references'); const ReportReferencesProgressNotification: NotificationType = new NotificationType('cpptools/reportReferencesProgress'); @@ -491,7 +503,6 @@ export interface Client { onDidCloseTextDocument(document: vscode.TextDocument): void; onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]): void; onDidChangeTextDocument(textDocumentChangeEvent: vscode.TextDocumentChangeEvent): void; - onDidChangeTextEditorVisibleRanges(textEditorVisibleRangesChangeEvent: vscode.TextEditorVisibleRangesChangeEvent): void; onRegisterCustomConfigurationProvider(provider: CustomConfigurationProvider1): Thenable; updateCustomConfigurations(requestingProvider?: CustomConfigurationProvider1): Thenable; updateCustomBrowseConfiguration(requestingProvider?: CustomConfigurationProvider1): Thenable; @@ -589,25 +600,63 @@ class FoldingRangeProvider implements vscode.FoldingRangeProvider { } } +class SemanticTokensProvider implements vscode.DocumentSemanticTokensProvider { + private client: DefaultClient; + constructor(client: DefaultClient) { + this.client = client; + } + + public async provideDocumentSemanticTokens(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { + return new Promise((resolve, reject) => { + this.client.notifyWhenReady(() => { + const uriString: string = document.uri.toString(); + const id: number = ++abortRequestId; + const params: GetSemanticTokensParams = { + id: id, + uri: uriString + }; + this.client.languageClient.sendRequest(GetSemanticTokensRequest, params) + .then((tokensResult) => { + if (tokensResult.canceled) { + reject(); + } else { + if (tokensResult.fileVersion !== this.client.openFileVersions.get(uriString)) { + reject(); + } else { + const builder: vscode.SemanticTokensBuilder = new vscode.SemanticTokensBuilder(this.client.semanticTokensLegend); + tokensResult.tokens.forEach((token) => { + builder.push(token.line, token.character, token.length, token.type, token.modifiers); + }); + resolve(builder.build()); + } + } + }); + token.onCancellationRequested(e => this.client.abortRequest(id)); + }); + }); + } +} + export class DefaultClient implements Client { private innerLanguageClient?: LanguageClient; // The "client" that launches and communicates with our language "server" process. private disposables: vscode.Disposable[] = []; private codeFoldingProviderDisposable: vscode.Disposable | undefined; + private semanticTokensProviderDisposable: vscode.Disposable | undefined; private innerConfiguration?: configs.CppProperties; private rootPathFileWatcher?: vscode.FileSystemWatcher; private rootFolder?: vscode.WorkspaceFolder; private storagePath: string; private trackedDocuments = new Set(); private isSupported: boolean = true; - private colorizationSettings: ColorizationSettings; - private openFileVersions = new Map(); - private visibleRanges = new Map(); + private inactiveRegionsDecorations = new Map(); + public openFileVersions = new Map(); private settingsTracker: SettingsTracker; private configurationProvider?: string; private documentSelector: DocumentFilter[] = [ { scheme: 'file', language: 'cpp' }, { scheme: 'file', language: 'c' } ]; + public semanticTokensLegend: vscode.SemanticTokensLegend | undefined; // The "model" that is displayed via the UI (status bar). private model: ClientModel = new ClientModel(); @@ -691,7 +740,6 @@ export class DefaultClient implements Client { this.storagePath = storagePath; const rootUri: vscode.Uri | undefined = this.RootUri; this.settingsTracker = getTracker(rootUri); - this.colorizationSettings = new ColorizationSettings(rootUri); try { let firstClient: boolean = false; if (!languageClient || languageClientCrashedNeedsRestart) { @@ -1046,6 +1094,24 @@ export class DefaultClient implements Client { } } + // Semantic token types are identified by indexes in this list of types, in the legend. + const tokenTypesLegend: string[] = []; + for (const e in SemanticTokenTypes) { + // An enum is actually a set of mappings from key <=> value. Enumerate over only the names. + // This allow us to represent the constants using an enum, which we can match in native code. + if (isNaN(Number(e))) { + tokenTypesLegend.push(e); + } + } + // Semantic token modifiers are bit indexes corresponding to the indexes in this list of modifiers in the legend. + const tokenModifiersLegend: string[] = []; + for (const e in SemanticTokenModifiers) { + if (isNaN(Number(e))) { + tokenModifiersLegend.push(e); + } + } + this.semanticTokensLegend = new vscode.SemanticTokensLegend(tokenTypesLegend, tokenModifiersLegend); + if (firstClient) { workspaceReferences = new refs.ReferencesManager(this); @@ -1070,6 +1136,9 @@ export class DefaultClient implements Client { if (settings.codeFolding) { this.codeFoldingProviderDisposable = vscode.languages.registerFoldingRangeProvider(this.documentSelector, new FoldingRangeProvider(this)); } + if (settings.enhancedColorization && this.semanticTokensLegend) { + this.semanticTokensProviderDisposable = vscode.languages.registerDocumentSemanticTokensProvider(this.documentSelector, new SemanticTokensProvider(this), this.semanticTokensLegend); + } // Listen for messages from the language server. this.registerNotifications(); @@ -1085,7 +1154,6 @@ export class DefaultClient implements Client { vscode.window.showErrorMessage(localize("unable.to.start", "Unable to start the C/C++ language server. IntelliSense features will be disabled. Error: {0}", String(err))); } })); - this.colorizationSettings.reload(); } catch (err) { this.isSupported = false; // Running on an OS we don't support yet. if (!failureMessageShown) { @@ -1324,44 +1392,6 @@ export class DefaultClient implements Client { this.sendAllSettings(); const changedSettings: { [key: string]: string } = this.settingsTracker.getChangedSettings(); this.notifyWhenReady(() => { - const colorizationNeedsReload: boolean = isFirstClient && (event.affectsConfiguration("workbench.colorTheme") - || event.affectsConfiguration("editor.tokenColorCustomizations")); - - const colorizationNeedsRefresh: boolean = colorizationNeedsReload - || event.affectsConfiguration("C_Cpp.enhancedColorization", this.RootUri) - || event.affectsConfiguration("C_Cpp.dimInactiveRegions", this.RootUri) - || event.affectsConfiguration("C_Cpp.inactiveRegionOpacity", this.RootUri) - || event.affectsConfiguration("C_Cpp.inactiveRegionForegroundColor", this.RootUri) - || event.affectsConfiguration("C_Cpp.inactiveRegionBackgroundColor", this.RootUri); - - if (isFirstClient) { - const colorThemeChanged: boolean = event.affectsConfiguration("workbench.colorTheme"); - if (colorThemeChanged) { - const otherSettings: OtherSettings = new OtherSettings(); - this.languageClient.sendNotification(ColorThemeChangedNotification, { name: otherSettings.colorTheme }); - } - } - - if (colorizationNeedsReload) { - this.colorizationSettings.reload(); - } - if (colorizationNeedsRefresh) { - const processedUris: vscode.Uri[] = []; - for (const e of vscode.window.visibleTextEditors) { - const uri: vscode.Uri = e.document.uri; - - // Make sure we don't process the same file multiple times. - // colorizationState.onSettingsChanged ensures all visible text editors for that file get - // refreshed, after it creates a set of decorators to be shared by all visible instances of the file. - if (!processedUris.find(e => e === uri)) { - processedUris.push(uri); - const colorizationState: ColorizationState | undefined = workspaceColorizationState.get(uri.toString()); - if (colorizationState) { - colorizationState.onSettingsChanged(uri); - } - } - } - } if (Object.keys(changedSettings).length > 0) { if (changedSettings["commentContinuationPatterns"]) { updateLanguageConfigurations(); @@ -1375,6 +1405,15 @@ export class DefaultClient implements Client { this.codeFoldingProviderDisposable = undefined; } } + if (changedSettings["enhancedColorization"]) { + const settings: CppSettings = new CppSettings(); + if (settings.enhancedColorization && this.semanticTokensLegend) { + this.semanticTokensProviderDisposable = vscode.languages.registerDocumentSemanticTokensProvider(this.documentSelector, new SemanticTokensProvider(this), this.semanticTokensLegend); ; + } else if (this.semanticTokensProviderDisposable) { + this.semanticTokensProviderDisposable.dispose(); + this.semanticTokensProviderDisposable = undefined; + } + } this.configuration.onDidChangeSettings(); telemetry.logLanguageServerEvent("CppSettingsChange", changedSettings, undefined); } @@ -1382,14 +1421,22 @@ export class DefaultClient implements Client { return changedSettings; } - private editVersion: number = 0; + public onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]): void { + const settings: CppSettings = new CppSettings(this.RootUri); + if (settings.dimInactiveRegions) { + // Apply text decorations to inactive regions + for (const e of editors) { + const valuePair: DecorationRangesPair | undefined = this.inactiveRegionsDecorations.get(e.document.uri.toString()); + if (valuePair) { + e.setDecorations(valuePair.decoration, valuePair.ranges); // VSCode clears the decorations when the text editor becomes invisible + } + } + } + } public onDidChangeTextDocument(textDocumentChangeEvent: vscode.TextDocumentChangeEvent): void { - // Increment editVersion for every call to onDidChangeTextDocument, regardless of whether the file is handled - this.editVersion++; if (textDocumentChangeEvent.document.uri.scheme === "file") { if (textDocumentChangeEvent.document.languageId === "cpp" || textDocumentChangeEvent.document.languageId === "c") { - // If any file has changed, we need to abort the current rename operation if (renamePending) { this.cancelReferences(); @@ -1399,16 +1446,6 @@ export class DefaultClient implements Client { const newVersion: number = textDocumentChangeEvent.document.version; if (oldVersion === undefined || newVersion > oldVersion) { this.openFileVersions.set(textDocumentChangeEvent.document.uri.toString(), newVersion); - try { - const colorizationState: ColorizationState | undefined = workspaceColorizationState.get(textDocumentChangeEvent.document.uri.toString()); - if (colorizationState) { - // Adjust colorization ranges after this edit. (i.e. if a line was added, push decorations after it down one line) - colorizationState.addEdits(textDocumentChangeEvent.contentChanges, this.editVersion); - } - } catch (e) { - // Ensure an exception does not prevent pass-through to native handler, or editVersion could become inconsistent - console.log(e.toString()); - } } } } @@ -1417,80 +1454,13 @@ export class DefaultClient implements Client { public onDidOpenTextDocument(document: vscode.TextDocument): void { if (document.uri.scheme === "file") { this.openFileVersions.set(document.uri.toString(), document.version); - workspaceColorizationState.set(document.uri.toString(), new ColorizationState(document.uri, this.colorizationSettings)); - this.sendVisibleRanges(document.uri); } } public onDidCloseTextDocument(document: vscode.TextDocument): void { - workspaceColorizationState.delete(document.uri.toString()); this.openFileVersions.delete(document.uri.toString()); } - public onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]): void { - const processedUris: vscode.Uri[] = []; - editors.forEach(editor => { - if (editor.document.uri.scheme === "file") { - const colorizationState: ColorizationState | undefined = workspaceColorizationState.get(editor.document.uri.toString()); - if (colorizationState) { - colorizationState.refresh(editor); - if (!processedUris.find(uri => uri === editor.document.uri)) { - processedUris.push(editor.document.uri); - this.sendVisibleRanges(editor.document.uri); - } - } - } - }); - } - - public sendVisibleRanges(uri: vscode.Uri): void { - const ranges: Range[] = []; - // Get ranges from all editors matching this URI - const editors: vscode.TextEditor[] = vscode.window.visibleTextEditors.filter(e => e.document.uri === uri); - for (const e of editors) { - e.visibleRanges.forEach(range => ranges.push(Range.create(range.start.line, range.start.character, range.end.line, range.end.character))); - } - - // Only send ranges if they have actually changed. - let isSame: boolean = false; - const savedRanges: Range[] | undefined = this.visibleRanges.get(uri.toString()); - if (savedRanges) { - if (ranges.length === savedRanges.length) { - isSame = true; - for (let i: number = 0; i < ranges.length; i++) { - if (ranges[i] !== savedRanges[i]) { - isSame = false; - break; - } - } - } - } else { - isSame = ranges.length === 0; - } - if (!isSame) { - this.visibleRanges.set(uri.toString(), ranges); - const params: DidChangeVisibleRangesParams = { - uri: uri.toString(), - ranges: ranges - }; - this.notifyWhenReady(() => this.languageClient.sendNotification(DidChangeVisibleRangesNotification, params)); - } - } - - public onDidChangeTextEditorVisibleRanges(textEditorVisibleRangesChangeEvent: vscode.TextEditorVisibleRangesChangeEvent): void { - this.notifyWhenReady(() => { - if (textEditorVisibleRangesChangeEvent.textEditor.document.uri.scheme === "file") { - if (vscode.window.activeTextEditor === textEditorVisibleRangesChangeEvent.textEditor) { - if (textEditorVisibleRangesChangeEvent.visibleRanges.length === 1) { - const visibleRangesLength: number = textEditorVisibleRangesChangeEvent.visibleRanges[0].end.line - textEditorVisibleRangesChangeEvent.visibleRanges[0].start.line; - workspaceReferences.updateVisibleRange(visibleRangesLength); - } - } - this.sendVisibleRanges(textEditorVisibleRangesChangeEvent.textEditor.document.uri); - } - }); - } - private registeredProviders: CustomConfigurationProvider1[] = []; public onRegisterCustomConfigurationProvider(provider: CustomConfigurationProvider1): Thenable { const onRegistered: () => void = () => { @@ -1937,7 +1907,7 @@ export class DefaultClient implements Client { this.languageClient.onNotification(LogTelemetryNotification, logTelemetry); this.languageClient.onNotification(ReportStatusNotification, (e) => this.updateStatus(e)); this.languageClient.onNotification(ReportTagParseStatusNotification, (e) => this.updateTagParseStatus(e)); - this.languageClient.onNotification(SemanticColorizationRegionsNotification, updateSemanticColorizationRegions); + this.languageClient.onNotification(InactiveRegionNotification, (e) => this.updateInactiveRegions(e)); this.languageClient.onNotification(CompileCommandsPathsNotification, (e) => this.promptCompileCommands(e)); this.languageClient.onNotification(ReferencesNotification, (e) => this.processReferencesResult(e.referencesResult)); this.languageClient.onNotification(ReportReferencesProgressNotification, (e) => this.handleReferencesProgress(e)); @@ -2148,6 +2118,55 @@ export class DefaultClient implements Client { this.model.tagParserStatus.Value = util.getLocalizedString(notificationBody); } + private updateInactiveRegions(params: InactiveRegionParams): void { + const settings: CppSettings = new CppSettings(this.RootUri); + const opacity: number | undefined = settings.inactiveRegionOpacity; + if (opacity !== null && opacity !== undefined) { + let backgroundColor: string | undefined = settings.inactiveRegionBackgroundColor; + if (backgroundColor === "") { + backgroundColor = undefined; + } + let color: string | undefined = settings.inactiveRegionForegroundColor; + if (color === "") { + color = undefined; + } + const decoration: vscode.TextEditorDecorationType = vscode.window.createTextEditorDecorationType({ + opacity: opacity.toString(), + backgroundColor: backgroundColor, + color: color, + rangeBehavior: vscode.DecorationRangeBehavior.OpenOpen + }); + // We must convert to vscode.Ranges in order to make use of the API's + const ranges: vscode.Range[] = []; + params.regions.forEach(element => { + const newRange: vscode.Range = new vscode.Range(element.startLine, 0, element.endLine, 0); + ranges.push(newRange); + }); + // Find entry for cached file and act accordingly + const valuePair: DecorationRangesPair | undefined = this.inactiveRegionsDecorations.get(params.uri); + if (valuePair) { + // Disposing of and resetting the decoration will undo previously applied text decorations + valuePair.decoration.dispose(); + valuePair.decoration = decoration; + // As vscode.TextEditor.setDecorations only applies to visible editors, we must cache the range for when another editor becomes visible + valuePair.ranges = ranges; + } else { // The entry does not exist. Make a new one + const toInsert: DecorationRangesPair = { + decoration: decoration, + ranges: ranges + }; + this.inactiveRegionsDecorations.set(params.uri, toInsert); + } + if (settings.dimInactiveRegions && params.fileVersion === this.openFileVersions.get(params.uri)) { + // Apply the decorations to all *visible* text editors + const editors: vscode.TextEditor[] = vscode.window.visibleTextEditors.filter(e => e.document.uri.toString() === params.uri); + for (const e of editors) { + e.setDecorations(decoration, ranges); + } + } + } + } + private promptCompileCommands(params: CompileCommandsPaths): void { if (!params.workspaceFolderUri) { return; @@ -2572,6 +2591,10 @@ export class DefaultClient implements Client { this.codeFoldingProviderDisposable.dispose(); this.codeFoldingProviderDisposable = undefined; } + if (this.semanticTokensProviderDisposable) { + this.semanticTokensProviderDisposable.dispose(); + this.semanticTokensProviderDisposable = undefined; + } this.model.dispose(); }); } @@ -2664,7 +2687,6 @@ class NullClient implements Client { onDidCloseTextDocument(document: vscode.TextDocument): void {} onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]): void {} onDidChangeTextDocument(textDocumentChangeEvent: vscode.TextDocumentChangeEvent): void {} - onDidChangeTextEditorVisibleRanges(textEditorVisibleRangesChangeEvent: vscode.TextEditorVisibleRangesChangeEvent): void {} onRegisterCustomConfigurationProvider(provider: CustomConfigurationProvider1): Thenable { return Promise.resolve(); } updateCustomConfigurations(requestingProvider?: CustomConfigurationProvider1): Thenable { return Promise.resolve(); } updateCustomBrowseConfiguration(requestingProvider?: CustomConfigurationProvider1): Thenable { return Promise.resolve(); } diff --git a/Extension/src/LanguageServer/colorization.ts b/Extension/src/LanguageServer/colorization.ts deleted file mode 100644 index afe9ed4fe8..0000000000 --- a/Extension/src/LanguageServer/colorization.ts +++ /dev/null @@ -1,671 +0,0 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All Rights Reserved. - * See 'LICENSE' in the project root for license information. - * ------------------------------------------------------------------------------------------ */ -'use strict'; - -import * as path from 'path'; -import * as vscode from 'vscode'; -import * as util from '../common'; -import { CppSettings, OtherSettings, TextMateRule, TextMateRuleSettings } from './settings'; -import * as jsonc from 'jsonc-parser'; -import * as plist from 'plist'; - -export enum TokenKind { - // These need to match the token_kind enum in the server - - // Semantic tokens - Macro, - Enumerator, - GlobalVariable, - LocalVariable, - Parameter, - Type, - RefType, - ValueType, - Function, - MemberFunction, - MemberField, - StaticMemberFunction, - StaticMemberField, - Property, - Event, - ClassTemplate, - GenericType, - FunctionTemplate, - Namespace, - Label, - UdlRaw, - UdlNumber, - UdlString, - OperatorFunction, - MemberOperator, - NewDelete, - - Count -} - -interface VersionedEdits { - editVersion: number; - changes: readonly vscode.TextDocumentContentChangeEvent[]; -} - -class ThemeStyle { - foreground?: string; - background?: string; - fontStyle?: string; -} - -export class ColorizationSettings { - private uri: vscode.Uri | undefined; - private pendingTask?: util.BlockingTask; - private editorBackground?: string; - - public themeStyleCMap: ThemeStyle[] = []; - public themeStyleCppMap: ThemeStyle[] = []; - - private static readonly scopeToTokenColorNameMap = new Map([ - ["comment", "comments"], - ["string", "strings"], - ["keyword.operator", "keywords"], - ["keyword.control", "keywords"], - ["constant.numeric", "numbers"], - ["entity.name.type", "types"], - ["entity.name.class", "types"], - ["entity.name.function", "functions"], - ["variable", "variables"] - ]); - - constructor(uri: vscode.Uri | undefined) { - this.uri = uri; - } - - // Given a TextMate rule 'settings' node, update a ThemeStyle to include any color or style information - private updateStyleFromTextMateRuleSettings(baseStyle: ThemeStyle, textMateRuleSettings: TextMateRuleSettings): void { - if (textMateRuleSettings.foreground) { - baseStyle.foreground = textMateRuleSettings.foreground; - } - if (!this.editorBackground || textMateRuleSettings.background && textMateRuleSettings.background.toUpperCase() !== this.editorBackground.toUpperCase()) { - baseStyle.background = textMateRuleSettings.background; - } - // Any (even empty) string for fontStyle removes inherited value - if (textMateRuleSettings.fontStyle) { - baseStyle.fontStyle = textMateRuleSettings.fontStyle; - } else if (textMateRuleSettings.fontStyle === "") { - baseStyle.fontStyle = undefined; - } - } - - // If the scope can be found in a set of TextMate rules, apply it to both C and Cpp ThemeStyle's - private findThemeStyleForScope(baseCStyle: ThemeStyle | undefined, baseCppStyle: ThemeStyle | undefined, scope: string, textMateRules: TextMateRule[] | undefined): void { - if (textMateRules) { - let match: TextMateRule | undefined = textMateRules.find(e => e.settings && (e.scope === scope || ((e.scope instanceof Array) && e.scope.indexOf(scope) > -1))); - if (match) { - if (baseCStyle) { - this.updateStyleFromTextMateRuleSettings(baseCStyle, match.settings); - } - if (baseCppStyle) { - this.updateStyleFromTextMateRuleSettings(baseCppStyle, match.settings); - } - } - - match = textMateRules.find(e => e.settings && (e.scope === "source " + scope || ((e.scope instanceof Array) && e.scope.indexOf("source " + scope) > -1))); - if (match) { - if (baseCStyle) { - this.updateStyleFromTextMateRuleSettings(baseCStyle, match.settings); - } - if (baseCppStyle) { - this.updateStyleFromTextMateRuleSettings(baseCppStyle, match.settings); - } - } - - if (baseCStyle) { - match = textMateRules.find(e => e.settings && (e.scope === "source.c " + scope || ((e.scope instanceof Array) && e.scope.indexOf("source.c " + scope) > -1))); - if (match) { - this.updateStyleFromTextMateRuleSettings(baseCStyle, match.settings); - } - } - - if (baseCppStyle) { - match = textMateRules.find(e => e.settings && (e.scope === "source.cpp " + scope || ((e.scope instanceof Array) && e.scope.indexOf("source.cpp " + scope) > -1))); - if (match) { - this.updateStyleFromTextMateRuleSettings(baseCppStyle, match.settings); - } - } - } - } - - // For a specific scope cascase all potential sources of style information to create a final ThemeStyle - private calculateThemeStyleForScope(baseCStyle: ThemeStyle | undefined, baseCppStyle: ThemeStyle | undefined, scope: string, themeName: string, themeTextMateRules: TextMateRule[][]): void { - // Search for settings with this scope in current theme - themeTextMateRules.forEach((rules) => { - this.findThemeStyleForScope(baseCStyle, baseCppStyle, scope, rules); - }); - - const otherSettings: OtherSettings = new OtherSettings(this.uri); - - // Next in priority would be a global user override of token color of the equivilent scope - const colorTokenName: string | undefined = ColorizationSettings.scopeToTokenColorNameMap.get(scope); - if (colorTokenName) { - const settingValue: string | undefined = otherSettings.getCustomColorToken(colorTokenName); - if (settingValue) { - if (baseCStyle) { - baseCStyle.foreground = settingValue; - } - if (baseCppStyle) { - baseCppStyle.foreground = settingValue; - } - } - } - - // Next in priority would be a global user override of this scope in textMateRules - this.findThemeStyleForScope(baseCStyle, baseCppStyle, scope, otherSettings.customTextMateRules); - - // Next in priority would be a theme-specific user override of token color of the equivilent scope - if (colorTokenName) { - const settingValue: string | undefined = otherSettings.getCustomThemeSpecificColorToken(colorTokenName, themeName); - if (settingValue) { - if (baseCStyle) { - baseCStyle.foreground = settingValue; - } - if (baseCppStyle) { - baseCppStyle.foreground = settingValue; - } - } - } - - // Next in priority would be a theme-specific user override of this scope in textMateRules - const textMateRules: TextMateRule[] | undefined = otherSettings.getCustomThemeSpecificTextMateRules(themeName); - this.findThemeStyleForScope(baseCStyle, baseCppStyle, scope, textMateRules); - } - - // For each level of the scope, look of style information - private calculateStyleForToken(tokenKind: TokenKind, scope: string, themeName: string, themeTextMateRules: TextMateRule[][]): void { - // Try scopes, from most general to most specific, apply style in cascading manner - const parts: string[] = scope.split("."); - let accumulatedScope: string = ""; - for (let i: number = 0; i < parts.length; i++) { - accumulatedScope += parts[i]; - this.calculateThemeStyleForScope(this.themeStyleCMap[tokenKind], this.themeStyleCppMap[tokenKind], accumulatedScope, themeName, themeTextMateRules); - this.calculateThemeStyleForScope(this.themeStyleCMap[tokenKind], undefined, accumulatedScope + ".c", themeName, themeTextMateRules); - this.calculateThemeStyleForScope(undefined, this.themeStyleCppMap[tokenKind], accumulatedScope + ".cpp", themeName, themeTextMateRules); - accumulatedScope += "."; - } - } - - public syncWithLoadingSettings(f: () => any): void { - this.pendingTask = new util.BlockingTask(f, this.pendingTask); - } - - public updateStyles(themeName: string, defaultStyle: ThemeStyle, textMateRules: TextMateRule[][]): void { - this.themeStyleCMap = new Array(TokenKind.Count); - this.themeStyleCppMap = new Array(TokenKind.Count); - - // Populate with unique objects, as they will be individual modified in place - for (let i: number = 0; i < TokenKind.Count; i++) { - this.themeStyleCMap[i] = {...defaultStyle}; - this.themeStyleCppMap[i] = {...defaultStyle}; - } - - this.calculateStyleForToken(TokenKind.Macro, "entity.name.function.preprocessor", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.Enumerator, "variable.other.enummember", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.GlobalVariable, "variable.other.global", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.LocalVariable, "variable.other.local", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.Parameter, "variable.parameter", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.Type, "entity.name.type.class", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.RefType, "entity.name.type.class.reference", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.ValueType, "entity.name.type.class.value", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.Function, "entity.name.function", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.MemberFunction, "entity.name.function.member", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.MemberField, "variable.other.property", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.StaticMemberFunction, "entity.name.function.member.static", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.StaticMemberField, "variable.other.property.static", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.Property, "variable.other.property.cli", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.Event, "variable.other.event", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.ClassTemplate, "entity.name.type.class.templated", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.GenericType, "entity.name.type.class.generic", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.FunctionTemplate, "entity.name.function.templated", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.Namespace, "entity.name.namespace", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.Label, "entity.name.label", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.UdlRaw, "entity.name.operator.custom-literal", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.UdlNumber, "entity.name.operator.custom-literal.number", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.UdlString, "entity.name.operator.custom-literal.string", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.OperatorFunction, "entity.name.function.operator", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.MemberOperator, "entity.name.function.operator.member", themeName, textMateRules); - this.calculateStyleForToken(TokenKind.NewDelete, "keyword.operator.new", themeName, textMateRules); - } - - public async loadTheme(themePath: string, defaultStyle: ThemeStyle): Promise { - let rules: TextMateRule[][] = []; - if (await util.checkFileExists(themePath)) { - const themeContentText: string = await util.readFileText(themePath); - let themeContent: any; - let textMateRules: TextMateRule[] | undefined; - if (themePath.endsWith("tmTheme")) { - themeContent = plist.parse(themeContentText); - if (themeContent) { - textMateRules = themeContent.settings; - } - } else { - themeContent = jsonc.parse(themeContentText); - if (themeContent) { - textMateRules = themeContent.tokenColors; - if (themeContent.include) { - // parse included theme file - const includedThemePath: string = path.join(path.dirname(themePath), themeContent.include); - rules = await this.loadTheme(includedThemePath, defaultStyle); - } - - if (themeContent.colors && themeContent.colors["editor.background"]) { - this.editorBackground = themeContent.colors["editor.background"]; - } - } - } - - if (textMateRules) { - // Convert comma delimited scopes into an array - textMateRules.forEach(e => { - if (e.scope && e.scope.includes(',')) { - e.scope = e.scope.split(',').map((s: string) => s.trim()); - } - }); - - const scopelessSetting: any = textMateRules.find(e => e.settings && !e.scope); - if (scopelessSetting) { - if (scopelessSetting.settings.background) { - this.editorBackground = scopelessSetting.settings.background; - } - this.updateStyleFromTextMateRuleSettings(defaultStyle, scopelessSetting.settings); - } - rules.push(textMateRules); - } - } - - return rules; - } - - public reload(): void { - const f: () => void = async () => { - const otherSettings: OtherSettings = new OtherSettings(this.uri); - const themeName: string | undefined = otherSettings.colorTheme; - if (themeName) { - // Enumerate through all extensions, looking for this theme. (Themes are implemented as extensions - even the default ones) - // Open each package.json to check for a theme path - for (let i: number = 0; i < vscode.extensions.all.length; i++) { - const extensionPath: string = vscode.extensions.all[i].extensionPath; - const extensionPackageJsonPath: string = path.join(extensionPath, "package.json"); - if (!await util.checkFileExists(extensionPackageJsonPath)) { - continue; - } - const packageJsonText: string = await util.readFileText(extensionPackageJsonPath); - const packageJson: any = jsonc.parse(packageJsonText); - if (packageJson.contributes && packageJson.contributes.themes) { - const foundTheme: any = packageJson.contributes.themes.find((e: any) => e.id === themeName || e.label === themeName); - if (foundTheme) { - const themeRelativePath: string = foundTheme.path; - const themeFullPath: string = path.join(extensionPath, themeRelativePath); - const defaultStyle: ThemeStyle = new ThemeStyle(); - const rulesSet: TextMateRule[][] = await this.loadTheme(themeFullPath, defaultStyle); - this.updateStyles(themeName, defaultStyle, rulesSet); - return; - } - } - } - } - }; - this.syncWithLoadingSettings(f); - } - - public static createDecorationFromThemeStyle(themeStyle: ThemeStyle): vscode.TextEditorDecorationType | undefined { - if (themeStyle && (themeStyle.foreground || themeStyle.background || themeStyle.fontStyle)) { - const options: vscode.DecorationRenderOptions = {}; - options.rangeBehavior = vscode.DecorationRangeBehavior.OpenOpen; - if (themeStyle.foreground) { - options.color = themeStyle.foreground; - } - if (themeStyle.background) { - options.backgroundColor = themeStyle.background; - } - if (themeStyle.fontStyle) { - const parts: string[] = themeStyle.fontStyle.split(" "); - parts.forEach((part) => { - switch (part) { - case "italic": - options.fontStyle = "italic"; - break; - case "bold": - options.fontWeight = "bold"; - break; - case "underline": - options.textDecoration = "underline"; - break; - default: - break; - } - }); - } - return vscode.window.createTextEditorDecorationType(options); - } - - return undefined; - } -} - -export class ColorizationState { - private uri: vscode.Uri; - private colorizationSettings: ColorizationSettings; - private decorations: (vscode.TextEditorDecorationType | undefined)[] = new Array(TokenKind.Count); - private semanticRanges: vscode.Range[][] = new Array(TokenKind.Count); - private inactiveDecoration: vscode.TextEditorDecorationType | undefined; - private inactiveRanges: vscode.Range[] = []; - private versionedEdits: VersionedEdits[] = []; - private currentSemanticVersion: number = 0; - private lastReceivedSemanticVersion: number = 0; - - public constructor(uri: vscode.Uri, colorizationSettings: ColorizationSettings) { - this.uri = uri; - this.colorizationSettings = colorizationSettings; - } - - private createColorizationDecorations(isCpp: boolean): void { - const settings: CppSettings = new CppSettings(this.uri); - if (settings.enhancedColorization) { - // Create new decorators - // The first decorator created takes precedence, so these need to be created in reverse order - for (let i: number = TokenKind.Count; i > 0;) { - i--; - let themeStyleMap: any; - if (isCpp) { - themeStyleMap = this.colorizationSettings.themeStyleCppMap; - } else { - themeStyleMap = this.colorizationSettings.themeStyleCMap; - } - this.decorations[i] = ColorizationSettings.createDecorationFromThemeStyle(themeStyleMap[i]); - } - } - if (settings.dimInactiveRegions) { - const opacity: number | undefined = settings.inactiveRegionOpacity; - if (opacity !== null && opacity !== undefined) { - let backgroundColor: string | undefined = settings.inactiveRegionBackgroundColor; - if (backgroundColor === "") { - backgroundColor = undefined; - } - let color: string | undefined = settings.inactiveRegionForegroundColor; - if (color === "") { - color = undefined; - } - this.inactiveDecoration = vscode.window.createTextEditorDecorationType({ - opacity: opacity.toString(), - backgroundColor: backgroundColor, - color: color, - rangeBehavior: vscode.DecorationRangeBehavior.OpenOpen - }); - } - } - } - - private disposeColorizationDecorations(): void { - // Dispose of all old decorations - if (this.inactiveDecoration) { - this.inactiveDecoration.dispose(); - this.inactiveDecoration = undefined; - } - for (let i: number = 0; i < TokenKind.Count; i++) { - const decoration: vscode.TextEditorDecorationType | undefined = this.decorations[i]; - if (decoration) { - decoration.dispose(); - this.decorations[i] = undefined; - } - } - } - - public dispose(): void { - this.disposeColorizationDecorations(); - } - - private refreshInner(e: vscode.TextEditor): void { - const settings: CppSettings = new CppSettings(this.uri); - if (settings.enhancedColorization) { - for (let i: number = 0; i < TokenKind.Count; i++) { - const decoration: vscode.TextEditorDecorationType | undefined = this.decorations[i]; - if (decoration) { - const ranges: vscode.Range[] = this.semanticRanges[i]; - if (ranges && ranges.length > 0) { - e.setDecorations(decoration, ranges); - } - } - } - } - - // Normally, decorators are honored in the order in which they were created, not the - // order in which they were applied. Decorators with opacity appear to be handled - // differently, in that the opacity is applied to overlapping decorators even if - // created afterwards. - if (settings.dimInactiveRegions && this.inactiveDecoration && this.inactiveRanges) { - e.setDecorations(this.inactiveDecoration, this.inactiveRanges); - } - } - - public refresh(e: vscode.TextEditor): void { - this.applyEdits(); - const f: () => void = async () => { - this.refreshInner(e); - }; - this.colorizationSettings.syncWithLoadingSettings(f); - } - - public onSettingsChanged(uri: vscode.Uri): void { - const f: () => void = async () => { - this.applyEdits(); - this.disposeColorizationDecorations(); - const isCpp: boolean = util.isEditorFileCpp(uri.toString()); - this.createColorizationDecorations(isCpp); - const editors: vscode.TextEditor[] = vscode.window.visibleTextEditors.filter(e => e.document.uri === uri); - for (const e of editors) { - this.refreshInner(e); - } - }; - this.colorizationSettings.syncWithLoadingSettings(f); - } - - // Utility function to convert a string and a start Position into a Range - private textToRange(text: string, startPosition: vscode.Position): vscode.Range { - const parts: string[] = text.split("\n"); - const addedLines: number = parts.length - 1; - const newStartLine: number = startPosition.line; - const newStartCharacter: number = startPosition.character; - const newEndLine: number = newStartLine + addedLines; - let newEndCharacter: number = parts[parts.length - 1].length; - if (newStartLine === newEndLine) { - newEndCharacter += newStartCharacter; - } - return new vscode.Range(newStartLine, newStartCharacter, newEndLine, newEndCharacter); - } - - // Utility function to shift a range back after removing content before it - private shiftRangeAfterRemove(range: vscode.Range, removeStartPosition: vscode.Position, removeEndPosition: vscode.Position): vscode.Range { - const lineDelta: number = removeStartPosition.line - removeEndPosition.line; - let startCharacterDelta: number = 0; - let endCharacterDelta: number = 0; - if (range.start.line === removeEndPosition.line) { - startCharacterDelta = removeStartPosition.character - removeEndPosition.character; - if (range.end.line === removeEndPosition.line) { - endCharacterDelta = startCharacterDelta; - } - } - const newStart: vscode.Position = range.start.translate(lineDelta, startCharacterDelta); - const newEnd: vscode.Position = range.end.translate(lineDelta, endCharacterDelta); - return new vscode.Range(newStart, newEnd); - } - - // Utility function to shift a range forward after inserting content before it - private shiftRangeAfterInsert(range: vscode.Range, insertStartPosition: vscode.Position, insertEndPosition: vscode.Position): vscode.Range { - const addedLines: number = insertEndPosition.line - insertStartPosition.line; - const newStartLine: number = range.start.line + addedLines; - const newEndLine: number = range.end.line + addedLines; - let newStartCharacter: number = range.start.character; - let newEndCharacter: number = range.end.character; - // If starts on the same line as replacement ended - if (insertEndPosition.line === newStartLine) { - let endOffsetLength: number = insertEndPosition.character; - // If insertRange starts and ends on the same line, only offset by it's length - if (insertEndPosition.line === insertStartPosition.line) { - endOffsetLength -= insertStartPosition.character; - } - newStartCharacter += endOffsetLength; - if (insertEndPosition.line === newEndLine) { - newEndCharacter += endOffsetLength; - } - } - return new vscode.Range(newStartLine, newStartCharacter, newEndLine, newEndCharacter); - } - - // Utility function to adjust a range to account for an insert and/or replace - private fixRange(range: vscode.Range, removeInsertStartPosition: vscode.Position, removeEndPosition: vscode.Position, insertEndPosition: vscode.Position): vscode.Range | undefined { - // If the replace/insert starts after this range ends, no adjustment is needed. - if (removeInsertStartPosition.isAfterOrEqual(range.end)) { - return range; - } - // Else, replace/insert range starts before this range ends. - - // If replace/insert starts before/where this range starts, we don't need to extend the existing range, but need to shift it - if (removeInsertStartPosition.isBeforeOrEqual(range.start)) { - - // If replace consumes the entire range, remove it - if (removeEndPosition.isAfterOrEqual(range.end)) { - return undefined; - } - - // If replace ends within this range, we need to trim it before we shift it - let newRange: vscode.Range; - if (removeEndPosition.isAfterOrEqual(range.start)) { - newRange = new vscode.Range(removeEndPosition, range.end); - } else { - newRange = range; - } - // Else, if replace ends before this range starts, we just need to shift it. - - newRange = this.shiftRangeAfterRemove(newRange, removeInsertStartPosition, removeEndPosition); - return this.shiftRangeAfterInsert(newRange, removeInsertStartPosition, insertEndPosition); - } - // Else, if replace/insert starts within (not before or after) range, extend it. - - // If there replace/insert overlaps past the end of the original range, just extend existing range to the insert end position - if (removeEndPosition.isAfterOrEqual(range.end)) { - return new vscode.Range(range.start.line, range.start.character, insertEndPosition.line, insertEndPosition.character); - } - // Else, range has some left over at the end, which needs to be shifted after insertEndPosition. - - // If the trailing segment is on the last line replace, we just need to extend by the remaining number of characters - if (removeEndPosition.line === range.end.line) { - return new vscode.Range(range.start.line, range.start.character, insertEndPosition.line, insertEndPosition.character + (range.end.character - removeEndPosition.character)); - } - // Else, the trailing segment ends on another line, so the character position should remain the same. Just adjust based on added/removed lined. - const removedLines: number = removeEndPosition.line - removeInsertStartPosition.line; - const addedLines: number = insertEndPosition.line - removeInsertStartPosition.line; - const deltaLines: number = addedLines - removedLines; - return new vscode.Range(range.start.line, range.start.character, range.end.line + deltaLines, range.end.character); - } - - private fixRanges(originalRanges: vscode.Range[], changes: readonly vscode.TextDocumentContentChangeEvent[]): vscode.Range[] { - // outer loop needs to be the versioned edits, then changes within that edit, then ranges - let ranges: vscode.Range[] = originalRanges; - if (ranges && ranges.length > 0) { - changes.forEach((change) => { - const newRanges: vscode.Range[] = []; - const insertRange: vscode.Range = this.textToRange(change.text, change.range.start); - for (let i: number = 0; i < ranges.length; i++) { - const newRange: vscode.Range | undefined = this.fixRange(ranges[i], change.range.start, change.range.end, insertRange.end); - if (newRange) { - newRanges.push(newRange); - } - } - ranges = newRanges; - }); - } - return ranges; - } - - // Add edits to be applied when/if cached tokens need to be reapplied. - public addEdits(changes: readonly vscode.TextDocumentContentChangeEvent[], editVersion: number): void { - const edits: VersionedEdits = { - editVersion: editVersion, - changes: changes - }; - this.versionedEdits.push(edits); - } - - // Apply any pending edits to the currently cached tokens - private applyEdits(): void { - this.versionedEdits.forEach((edit) => { - if (edit.editVersion > this.currentSemanticVersion) { - for (let i: number = 0; i < TokenKind.Count; i++) { - this.semanticRanges[i] = this.fixRanges(this.semanticRanges[i], edit.changes); - } - this.inactiveRanges = this.fixRanges(this.inactiveRanges, edit.changes); - this.currentSemanticVersion = edit.editVersion; - } - }); - } - - // Remove any edits from the list if we will never receive tokens that old. - private purgeOldVersionedEdits(): void { - const minVersion: number = this.lastReceivedSemanticVersion; - const index: number = this.versionedEdits.findIndex((edit) => edit.editVersion > minVersion); - if (index === -1) { - this.versionedEdits = []; - } else if (index > 0) { - this.versionedEdits = this.versionedEdits.slice(index); - } - } - - private updateColorizationRanges(uri: string): void { - const f: () => void = async () => { - this.applyEdits(); - this.purgeOldVersionedEdits(); - - // The only way to un-apply decorators is to dispose them. - // If we dispose old decorators before applying new decorators, we see a flicker on Mac, - // likely due to a race with UI updates. Here we set aside the existing decorators to be - // disposed of after the new decorators have been applied, so there is not a gap - // in which decorators are not applied. - const oldInactiveDecoration: vscode.TextEditorDecorationType | undefined = this.inactiveDecoration; - const oldDecorations: (vscode.TextEditorDecorationType | undefined)[] = this.decorations; - this.inactiveDecoration = undefined; - this.decorations = new Array(TokenKind.Count); - - const isCpp: boolean = util.isEditorFileCpp(uri); - this.createColorizationDecorations(isCpp); - - // Apply the decorations to all *visible* text editors - const editors: vscode.TextEditor[] = vscode.window.visibleTextEditors.filter(e => e.document.uri.toString() === uri); - for (const e of editors) { - this.refreshInner(e); - } - - // Dispose of the old decorators only after the new ones have been applied. - if (oldInactiveDecoration) { - oldInactiveDecoration.dispose(); - } - if (oldDecorations) { - for (let i: number = 0; i < TokenKind.Count; i++) { - const oldDecoration: vscode.TextEditorDecorationType | undefined = oldDecorations[i]; - if (oldDecoration) { - oldDecoration.dispose(); - } - } - } - }; - this.colorizationSettings.syncWithLoadingSettings(f); - } - - public updateSemantic(uri: string, semanticRanges: vscode.Range[][], inactiveRanges: vscode.Range[], editVersion: number): void { - this.inactiveRanges = inactiveRanges; - for (let i: number = 0; i < TokenKind.Count; i++) { - this.semanticRanges[i] = semanticRanges[i]; - } - this.currentSemanticVersion = editVersion; - this.lastReceivedSemanticVersion = editVersion; - this.updateColorizationRanges(uri); - } -} diff --git a/Extension/src/LanguageServer/extension.ts b/Extension/src/LanguageServer/extension.ts index 993d8bc798..4b8f2ebc8c 100644 --- a/Extension/src/LanguageServer/extension.ts +++ b/Extension/src/LanguageServer/extension.ts @@ -454,7 +454,6 @@ function realActivation(): void { disposables.push(vscode.window.onDidChangeActiveTextEditor(onDidChangeActiveTextEditor)); disposables.push(vscode.window.onDidChangeTextEditorSelection(onDidChangeTextEditorSelection)); disposables.push(vscode.window.onDidChangeVisibleTextEditors(onDidChangeVisibleTextEditors)); - disposables.push(vscode.window.onDidChangeTextEditorVisibleRanges(onDidChangeTextEditorVisibleRanges)); updateLanguageConfigurations(); @@ -606,32 +605,6 @@ function onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]): void { processDelayedDidOpen(editor.document); } }); - - clients.forEach(client => { - const editorsForThisClient: vscode.TextEditor[] = []; - editors.forEach(editor => { - if (editor.document.languageId === "c" || editor.document.languageId === "cpp" - || editor.document.languageId === "json" && editor.document.uri.fsPath.endsWith("c_cpp_properties.json")) { - if (clients.checkOwnership(client, editor.document)) { - editorsForThisClient.push(editor); - } - } - }); - if (editorsForThisClient.length > 0) { - client.onDidChangeVisibleTextEditors(editorsForThisClient); - } - }); -} - -function onDidChangeTextEditorVisibleRanges(textEditorVisibleRangesChangeEvent: vscode.TextEditorVisibleRangesChangeEvent): void { - const languageId: String = textEditorVisibleRangesChangeEvent.textEditor.document.languageId; - if (languageId === "c" || languageId === "cpp") { - clients.forEach(client => { - if (clients.checkOwnership(client, textEditorVisibleRangesChangeEvent.textEditor.document)) { - client.onDidChangeTextEditorVisibleRanges(textEditorVisibleRangesChangeEvent); - } - }); - } } function onInterval(): void { diff --git a/Extension/src/LanguageServer/protocolFilter.ts b/Extension/src/LanguageServer/protocolFilter.ts index 7f3b98a693..3190872599 100644 --- a/Extension/src/LanguageServer/protocolFilter.ts +++ b/Extension/src/LanguageServer/protocolFilter.ts @@ -64,7 +64,7 @@ export function createProtocolFilter(clients: ClientCollection): Middleware { // NO-OP // If the file is not opened into an editor (such as in response for a control-hover), // we do not actually load a translation unit for it. When we receive a didOpen, the file - // may not yet be visible. So, we defer creation of the translation until we receive a + // may not yet be visible. So, we defer creation of the translation until we receive a // call to onDidChangeVisibleTextEditors(), in extension.ts. A file is only loaded when // it is actually opened in the editor (not in response to control-hover, which sends a // didOpen), and first becomes visible. diff --git a/Extension/yarn.lock b/Extension/yarn.lock index a97e4ef457..5e3be46d83 100644 --- a/Extension/yarn.lock +++ b/Extension/yarn.lock @@ -204,10 +204,10 @@ dependencies: source-map "^0.6.1" -"@types/vscode@1.43.0": - version "1.43.0" - resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.43.0.tgz#22276e60034c693b33117f1068ffaac0e89522db" - integrity sha512-kIaR9qzd80rJOxePKpCB/mdy00mz8Apt2QA5Y6rdrKFn13QNFNeP3Hzmsf37Bwh/3cS7QjtAeGSK7wSqAU0sYQ== +"@types/vscode@1.44.0": + version "1.44.0" + resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.44.0.tgz#62ecfe3d0e38942fce556574da54ee1013c775b7" + integrity sha512-WJZtZlinE3meRdH+I7wTsIhpz/GLhqEQwmPGeh4s1irWLwMzCeTV8WZ+pgPTwrDXoafVUWwo1LiZ9HJVHFlJSQ== "@types/webpack-sources@*": version "0.1.6" diff --git a/Themes/README.md b/Themes/README.md index 9f80ac2fcf..b34aee95f9 100644 --- a/Themes/README.md +++ b/Themes/README.md @@ -1,8 +1,10 @@ # C/C++ Extension UI Themes -[Semantic colorization was added to the C/C++ Extension in version 0.24.0](https://devblogs.microsoft.com/cppblog/visual-studio-code-c-c-extension-july-2019-update/). By default, colorization in VS Code is syntactic/lexical and leverages TextMate grammar to associate named 'scopes' with syntactic elements. Themes and settings can be used to apply the colors associated with those scopes. Our implementation of semantic colorization leverages the same system of associating colors with named scopes. But, some tokens that can be colored by semantic colorization in C/C++ do not have existing analogs in VS Code's TextMate grammar. So, new named scopes are required. Information about these new scopes can be found [here](https://code.visualstudio.com/docs/cpp/colorization-cpp). Because these scopes are new, existing themes do not include colors for them either. +[Semantic colorization was added to the C/C++ Extension in version 0.24.0](https://devblogs.microsoft.com/cppblog/visual-studio-code-c-c-extension-july-2019-update/). At the time, colorization in VS Code was purely syntactic/lexical and leveraged TextMate grammar to associate named 'scopes' with syntactic elements. Themes and settings can be used to associate colors with these scopes. Our original implementation of semantic colorization leveraged the same system of associating colors with named scopes. But, some tokens that can be colored by semantic colorization in C/C++ did not have existing analogs in VS Code's TextMate grammar. So, new named scopes are required. Because these scopes were new, existing themes did not include colors for them either. -We created C/C++ Extension UI Themes to closely match Visual Studio themes, and include colors for many of the new scopes. +We created C/C++ Extension UI Themes to closely match Visual Studio themes and include colors for many of the new scopes. + +VS Code has since provided an API for semantic colorization. The C/C++ Extension has transitioned from its own implementation to this new API. These themes now include colors for some of the new semantic token scopes. ## Example @@ -16,7 +18,7 @@ Dark Theme ## Contributing -This project welcomes contributions and suggestions. Most contributions require you to agree to a +This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. diff --git a/Themes/themes/cpptools_dark_vs.json b/Themes/themes/cpptools_dark_vs.json index a0394b6d67..d2d7daaa77 100644 --- a/Themes/themes/cpptools_dark_vs.json +++ b/Themes/themes/cpptools_dark_vs.json @@ -20,6 +20,7 @@ "statusBarItem.remoteForeground": "#FFF", "statusBarItem.remoteBackground": "#16825D" }, + "semanticHighlighting": true, "tokenColors": [ { "scope": [ @@ -466,5 +467,37 @@ "foreground": "#7F7F7F" } } + ], + "semanticTokenColors": [ + { + "operatorOverload": { + "foreground": "#B4B4B4" + } + }, + { + "operatorOverloadMember": { + "foreground": "#B4B4B4" + } + }, + { + "newOperator": { + "foreground": "#569CD6" + } + }, + { + "numberLiteral": { + "foreground": "#B5CEA8" + } + }, + { + "customLiteral": { + "foreground": "#DADADA" + } + }, + { + "stringLiteral": { + "foreground": "#D69D85" + } + } ] } \ No newline at end of file diff --git a/Themes/themes/cpptools_light_vs.json b/Themes/themes/cpptools_light_vs.json index 4901df8c77..b1f5e6fd18 100644 --- a/Themes/themes/cpptools_light_vs.json +++ b/Themes/themes/cpptools_light_vs.json @@ -19,6 +19,7 @@ "statusBarItem.remoteForeground": "#FFF", "statusBarItem.remoteBackground": "#16825D" }, + "semanticHighlighting": true, "tokenColors": [ { "scope": ["meta.embedded", "source.groovy.embedded"], @@ -455,5 +456,27 @@ "foreground": "#808080" } } + ], + "semanticTokenColors": [ + { + "operatorOverload": { + "foreground": "#008080" + } + }, + { + "operatorOverloadMember": { + "foreground": "#008080" + } + }, + { + "newOperator": { + "foreground": "#0000FF" + } + }, + { + "stringLiteral": { + "foreground": "#A31515" + } + } ] } \ No newline at end of file