diff --git a/CHANGELOG.md b/CHANGELOG.md index e29cf5482b..df5fc58d1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Features: - Add an option to specify the launch target for debugging CTest tests. [#4273](https://github.com/microsoft/vscode-cmake-tools/pull/4273) +- Add a setting to enable/disable our built-in language services. [#4290](https://github.com/microsoft/vscode-cmake-tools/issues/4290) Improvements: diff --git a/package.json b/package.json index cb57208189..a999d5ced6 100644 --- a/package.json +++ b/package.json @@ -3624,6 +3624,12 @@ "description": "%cmake-tools.configuration.cmake.enableAutomaticKitScan.description%", "scope": "resource" }, + "cmake.enableLanguageServices": { + "type": "boolean", + "default": true, + "description": "%cmake-tools.configuration.cmake.enableLanguageServices.description%", + "scope": "machine" + }, "cmake.preRunCoverageTarget": { "type": "string", "default": null, diff --git a/package.nls.json b/package.nls.json index b8f7cf2077..43dcfa84c4 100644 --- a/package.nls.json +++ b/package.nls.json @@ -294,6 +294,7 @@ ] }, "cmake-tools.configuration.cmake.enableAutomaticKitScan.description": "Enable automatic scanning for kits when a kit isn't selected. This will only take affect when CMake Presets aren't being used.", + "cmake-tools.configuration.cmake.enableLanguageServices.description": "Enable language services for CMake files. This will enable syntax highlighting, code completion, and other features.", "cmake-tools.configuration.cmake.preRunCoverageTarget.description": "Target to build before running tests with coverage using the test explorer", "cmake-tools.configuration.cmake.postRunCoverageTarget.description": "Target to build after running tests with coverage using the test explorer", "cmake-tools.configuration.cmake.coverageInfoFiles.description": "LCOV coverage info files to be processed after running tests with coverage using the test explorer.", diff --git a/src/config.ts b/src/config.ts index 224d4652f3..e92fbba9ba 100644 --- a/src/config.ts +++ b/src/config.ts @@ -217,6 +217,7 @@ export interface ExtensionConfigurationSettings { automaticReconfigure: boolean; pinnedCommands: string[]; enableAutomaticKitScan: boolean; + enableLanguageServices: boolean; preRunCoverageTarget: string | null; postRunCoverageTarget: string | null; coverageInfoFiles: string[]; @@ -571,6 +572,10 @@ export class ConfigurationReader implements vscode.Disposable { return this.configData.enableAutomaticKitScan; } + get enableLanguageServices(): boolean { + return this.configData.enableLanguageServices; + } + get preRunCoverageTarget(): string | null { return this.configData.preRunCoverageTarget; } @@ -648,6 +653,7 @@ export class ConfigurationReader implements vscode.Disposable { automaticReconfigure: new vscode.EventEmitter(), pinnedCommands: new vscode.EventEmitter(), enableAutomaticKitScan: new vscode.EventEmitter(), + enableLanguageServices: new vscode.EventEmitter(), preRunCoverageTarget: new vscode.EventEmitter(), postRunCoverageTarget: new vscode.EventEmitter(), coverageInfoFiles: new vscode.EventEmitter() diff --git a/src/extension.ts b/src/extension.ts index 196158195f..f2205e5549 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -106,6 +106,12 @@ export class ExtensionManager implements vscode.Disposable { private onDidChangeActiveTextEditorSub: vscode.Disposable = new DummyDisposable(); private readonly extensionActiveCommandsEmitter = new vscode.EventEmitter(); private readonly workspaceConfig: ConfigurationReader = ConfigurationReader.create(); + private readonly CMAKE_LANGUAGE = "cmake"; + private readonly CMAKE_SELECTOR: vscode.DocumentSelector = [ + { language: this.CMAKE_LANGUAGE, scheme: "file" }, + { language: this.CMAKE_LANGUAGE, scheme: "untitled" } + ]; + private languageServicesDisposables: vscode.Disposable[] = []; private updateTouchBarVisibility(config: TouchBarConfig) { const touchBarVisible = config.visibility === "default"; @@ -119,6 +125,17 @@ export class ExtensionManager implements vscode.Disposable { * Second-phase async init */ public async init() { + if (this.workspaceConfig.enableLanguageServices) { + await this.enableLanguageServices(); + this.workspaceConfig.onChange('enableLanguageServices', async (value) => { + if (value) { + await this.enableLanguageServices(); + } else { + this.disposeLanguageServices(); + } + }); + } + this.updateTouchBarVisibility(this.workspaceConfig.touchbar); this.workspaceConfig.onChange('touchbar', config => this.updateTouchBarVisibility(config)); @@ -357,6 +374,7 @@ export class ExtensionManager implements vscode.Disposable { private activeTestPresetSub: vscode.Disposable = new DummyDisposable(); private activePackagePresetSub: vscode.Disposable = new DummyDisposable(); private activeWorkflowPresetSub: vscode.Disposable = new DummyDisposable(); + private enableLanguageServicesSub: vscode.Disposable = new DummyDisposable(); // Watch the code model so that we may update the tree view // @@ -379,6 +397,83 @@ export class ExtensionManager implements vscode.Disposable { private cppToolsAPI?: cpt.CppToolsApi; private configProviderRegistered?: boolean = false; + private async enableLanguageServices() { + try { + const languageServices = await LanguageServiceData.create(); + this.languageServicesDisposables.push(vscode.languages.registerHoverProvider( + this.CMAKE_SELECTOR, + languageServices + )); + this.languageServicesDisposables.push(vscode.languages.registerCompletionItemProvider( + this.CMAKE_SELECTOR, + languageServices + )); + } catch { + log.error( + localize( + "language.service.failed", + "Failed to initialize language services" + ) + ); + } + + this.languageServicesDisposables.push(vscode.languages.setLanguageConfiguration( + this.CMAKE_LANGUAGE, + { + indentationRules: { + // ^(.*\*/)?\s*\}.*$ + decreaseIndentPattern: /^(.*\*\/)?\s*\}.*$/, + // ^.*\{[^}"']*$ + increaseIndentPattern: /^.*\{[^}"']*$/ + }, + wordPattern: + /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g, + comments: { + lineComment: "#" + }, + brackets: [ + ["{", "}"], + ["(", ")"] + ], + + __electricCharacterSupport: { + brackets: [ + { + tokenType: "delimiter.curly.ts", + open: "{", + close: "}", + isElectric: true + }, + { + tokenType: "delimiter.square.ts", + open: "[", + close: "]", + isElectric: true + }, + { + tokenType: "delimiter.paren.ts", + open: "(", + close: ")", + isElectric: true + } + ] + }, + + __characterPairSupport: { + autoClosingPairs: [ + { open: "{", close: "}" }, + { open: "(", close: ")" }, + { open: '"', close: '"', notIn: ["string"] } + ] + } + } + )); + } + + private disposeLanguageServices() { + this.languageServicesDisposables.forEach(sub => sub.dispose()); + } + private getProjectsForWorkspaceFolder(folder?: vscode.WorkspaceFolder): CMakeProject[] | undefined { folder = this.getWorkspaceFolder(folder); return this.projectController.getProjectsForWorkspaceFolder(folder); @@ -554,6 +649,7 @@ export class ExtensionManager implements vscode.Disposable { */ async asyncDispose() { this.disposeSubs(); + this.disposeLanguageServices(); this.codeModelUpdateSubs.forEach( subs => subs.forEach( sub => sub.dispose() @@ -736,7 +832,7 @@ export class ExtensionManager implements vscode.Disposable { private disposeSubs() { util.disposeAll(this.projectSubscriptions); - for (const sub of [this.statusMessageSub, this.targetNameSub, this.buildTypeSub, this.launchTargetSub, this.ctestEnabledSub, this.isBusySub, this.activeConfigurePresetSub, this.activeBuildPresetSub, this.activeTestPresetSub, this.activePackagePresetSub, this.activeWorkflowPresetSub]) { + for (const sub of [this.statusMessageSub, this.targetNameSub, this.buildTypeSub, this.launchTargetSub, this.ctestEnabledSub, this.isBusySub, this.activeConfigurePresetSub, this.activeBuildPresetSub, this.activeTestPresetSub, this.activePackagePresetSub, this.activeWorkflowPresetSub, this.enableLanguageServicesSub]) { sub.dispose(); } } @@ -834,6 +930,7 @@ export class ExtensionManager implements vscode.Disposable { this.activeTestPresetSub = new DummyDisposable(); this.activePackagePresetSub = new DummyDisposable(); this.activeWorkflowPresetSub = new DummyDisposable(); + this.enableLanguageServicesSub = new DummyDisposable(); this.statusBar.setActiveKitName(''); this.statusBar.setConfigurePresetName(''); this.statusBar.setBuildPresetName(''); @@ -2400,53 +2497,6 @@ export async function activate(context: vscode.ExtensionContext): Promise\/\?\s]+)/g, - comments: { - lineComment: '#' - }, - brackets: [ - ['{', '}'], - ['(', ')'] - ], - - __electricCharacterSupport: { - brackets: [ - { tokenType: 'delimiter.curly.ts', open: '{', close: '}', isElectric: true }, - { tokenType: 'delimiter.square.ts', open: '[', close: ']', isElectric: true }, - { tokenType: 'delimiter.paren.ts', open: '(', close: ')', isElectric: true } - ] - }, - - __characterPairSupport: { - autoClosingPairs: [ - { open: '{', close: '}' }, - { open: '(', close: ')' }, - { open: '"', close: '"', notIn: ['string'] } - ] - } - }); - if (vscode.workspace.getConfiguration('cmake').get('showOptionsMovedNotification')) { void vscode.window.showInformationMessage( localize('options.moved.notification.body', "Some status bar options in CMake Tools have now moved to the Project Status View in the CMake Tools sidebar. You can customize your view with the 'cmake.options' property in settings."), diff --git a/test/unit-tests/config.test.ts b/test/unit-tests/config.test.ts index 28d2911ff1..a67337b274 100644 --- a/test/unit-tests/config.test.ts +++ b/test/unit-tests/config.test.ts @@ -78,6 +78,7 @@ function createConfig(conf: Partial): Configurat ignoreCMakeListsMissing: false, automaticReconfigure: false, enableAutomaticKitScan: true, + enableLanguageServices: true, preRunCoverageTarget: null, postRunCoverageTarget: null, coverageInfoFiles: []