Skip to content

Commit de61948

Browse files
authored
Add cmake.languageServerOnlyMode setting for language services without project integration (#4873)
* add languageserveronly mode * remove unnecessary localization and updated test * code cleanup * added test
1 parent 8a73b00 commit de61948

10 files changed

Lines changed: 111 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
Features:
66
- Add support for CMake Presets version 11 (added in CMake 4.3). In test presets, the `execution.jobs` field can now be an empty string, equivalent to passing `--parallel` with no value.
77
- Automatically add new source files to `CMakeLists.txt` and remove deleted source files from `CMakeLists.txt`. Two new commands (`cmake.addFileToCMakeLists` and `cmake.removeFileFromCMakeLists`) and nine new `cmake.modifyLists.*` settings provide full control over target selection, variable handling, and confirmation behavior. [#2132](https://github.com/microsoft/vscode-cmake-tools/issues/2132) [#4454](https://github.com/microsoft/vscode-cmake-tools/pull/4454) [@malsyned](https://github.com/malsyned)
8+
- Add `cmake.languageServerOnlyMode` to keep CMake language services active while disabling CMake project integration when no local CMake executable is available. [#4516](https://github.com/microsoft/vscode-cmake-tools/issues/4516)
89
- Allow specifying a custom debug adapter type in `cmake.debugConfig` via the `type` property. When set, automatic debugger detection is skipped and any debug adapter (e.g., `codelldb`, `lldb`) can be used with arbitrary configuration properties. [#4818](https://github.com/microsoft/vscode-cmake-tools/pull/4818)
910
- Add `${cmake.testEnvironment}` placeholder for launch.json that resolves to the CTest `ENVIRONMENT` test property, and automatically include CTest environment variables when debugging tests without a launch configuration. [#4572](https://github.com/microsoft/vscode-cmake-tools/issues/4572) [#4821](https://github.com/microsoft/vscode-cmake-tools/pull/4821)
1011
- Add "Delete Build Directory and Reconfigure" command that removes the entire build directory before reconfiguring, ensuring a completely clean state. [#4826](https://github.com/microsoft/vscode-cmake-tools/pull/4826)

docs/cmake-settings.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Options that support substitution, in the table below, allow variable references
5353
| `cmake.enabledOutputParsers` | List of enabled output parsers. | `["cmake", "gcc", "gnuld", "msvc", "ghs", "diab", "iwyu"]` | no |
5454
| `cmake.additionalBuildProblemMatchers` | Array of user-defined problem matchers for build output. Each entry has `name`, `regexp`, and optional capture group indices (`file`, `line`, `column`, `severity`, `message`, `code`). See [Additional Build Problem Matchers](#additional-build-problem-matchers) below. | `[]` | no |
5555
| `cmake.enableLanguageServices` | If `true`, enable CMake language services. | `true` | no |
56+
| `cmake.languageServerOnlyMode` | If `true`, keep CMake language services enabled while disabling CMake project, build, test, and kit integration. | `false` | no |
5657
| `cmake.enableTraceLogging` | If `true`, enable trace logging. | `false` | no |
5758
| `cmake.environment` | An object containing `key:value` pairs of environment variables, which will be available when configuring, building, or testing with CTest. | `{}` (no environment variables) | yes |
5859
| `cmake.exclude` | CMake Tools will ignore the folders defined in this setting. | `[]` | no |

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4140,6 +4140,12 @@
41404140
"description": "%cmake-tools.configuration.cmake.enableLanguageServices.description%",
41414141
"scope": "machine"
41424142
},
4143+
"cmake.languageServerOnlyMode": {
4144+
"type": "boolean",
4145+
"default": false,
4146+
"description": "%cmake-tools.configuration.cmake.languageServerOnlyMode.description%",
4147+
"scope": "machine"
4148+
},
41434149
"cmake.preRunCoverageTarget": {
41444150
"type": "string",
41454151
"default": null,

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@
383383
"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.",
384384
"cmake-tools.configuration.cmake.removeStaleKitsOnScan.description": "Remove compiler-based kits from the user kits file during a full kit scan when they are no longer rediscovered. Set \"keep\": true in a kit entry to preserve it.",
385385
"cmake-tools.configuration.cmake.enableLanguageServices.description": "Enable language services for CMake files. This will enable syntax highlighting, code completion, and other features.",
386+
"cmake-tools.configuration.cmake.languageServerOnlyMode.description": "Enable language-server-only mode for CMake files. This keeps CMake language services active while disabling CMake project, build, test, and kit integration.",
386387
"cmake-tools.configuration.cmake.preRunCoverageTarget.description": "Target to build before running tests with coverage using the test explorer",
387388
"cmake-tools.configuration.cmake.postRunCoverageTarget.description": "Target to build after running tests with coverage using the test explorer",
388389
"cmake-tools.configuration.cmake.coverageInfoFiles.description": "LCOV coverage info files to be processed after running tests with coverage using the test explorer.",

src/cmakeProject.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -843,13 +843,26 @@ export class CMakeProject {
843843
// Force re-reading of cmake exe, this will ensure that the debugger capabilities are updated.
844844
const cmakeInfo = await this.getCMakeExecutable();
845845
if (!cmakeInfo.isPresent) {
846-
// Do not show a popup here to avoid duplicate "Bad CMake executable" messages.
847-
// The canonical user-facing error is shown when a command actually needs a driver
848-
// and getCMakeDriverInstance() validates the executable.
849-
telemetry.logEvent('CMakeExecutableNotFound');
846+
if (!this.workspaceContext.config.languageServerOnlyMode) {
847+
telemetry.logEvent('CMakeExecutableNotFound');
848+
} else {
849+
log.debug(localize('bad.executable.suppressed.language.server.only', 'Skipping bad CMake executable notification because language-server-only mode is enabled.'));
850+
}
850851
}
851852

852-
await this.reloadCMakeDriver();
853+
if (!this.workspaceContext.config.languageServerOnlyMode) {
854+
await this.reloadCMakeDriver();
855+
}
856+
});
857+
858+
private readonly languageServerOnlyModeSub = this.workspaceContext.config.onChange('languageServerOnlyMode', async enabled => {
859+
if (enabled) {
860+
log.info(localize('language.server.only.shutdown.driver', 'Shutting down the CMake driver because language-server-only mode was enabled.'));
861+
await this.shutDownCMakeDriver();
862+
} else {
863+
log.info(localize('language.server.only.reload.driver', 'Reloading the CMake driver because language-server-only mode was disabled.'));
864+
await this.reloadCMakeDriver();
865+
}
853866
});
854867

855868
private readonly shellSub = this.workspaceContext.config.onChange('shell', async () => {
@@ -901,6 +914,7 @@ export class CMakeProject {
901914
this.preferredGeneratorsSub,
902915
this.communicationModeSub,
903916
this.cmakePathSub,
917+
this.languageServerOnlyModeSub,
904918
this.shellSub
905919
]) {
906920
sub.dispose();
@@ -1491,6 +1505,10 @@ export class CMakeProject {
14911505
*/
14921506
async getCMakeDriverInstance(): Promise<CMakeDriver | null> {
14931507
return this.driverStrand.execute(async () => {
1508+
if (this.workspaceContext.config.languageServerOnlyMode) {
1509+
log.debug(localize('not.starting.language.server.only', 'Not starting CMake driver because language-server-only mode is enabled.'));
1510+
return null;
1511+
}
14941512
if (!this.useCMakePresets && !this.activeKit) {
14951513
log.debug(localize('not.starting.no.kits', 'Not starting CMake driver: no kit selected'));
14961514
return null;
@@ -2154,6 +2172,9 @@ export class CMakeProject {
21542172

21552173
// Reconfigure if the saved file is a cmake file.
21562174
async doCMakeFileChangeReconfigure(uri: vscode.Uri) {
2175+
if (this.workspaceContext.config.languageServerOnlyMode) {
2176+
return;
2177+
}
21572178
const filePath = util.platformNormalizePath(uri.fsPath);
21582179
const driver: CMakeDriver | null = await this.getCMakeDriverInstance();
21592180

src/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ export interface ExtensionConfigurationSettings {
251251
enableAutomaticKitScan: boolean;
252252
removeStaleKitsOnScan: boolean;
253253
enableLanguageServices: boolean;
254+
languageServerOnlyMode: boolean;
254255
preRunCoverageTarget: string | null;
255256
postRunCoverageTarget: string | null;
256257
coverageInfoFiles: string[];
@@ -656,6 +657,10 @@ export class ConfigurationReader implements vscode.Disposable {
656657
return this.configData.enableLanguageServices;
657658
}
658659

660+
get languageServerOnlyMode(): boolean {
661+
return this.configData.languageServerOnlyMode;
662+
}
663+
659664
get preRunCoverageTarget(): string | null {
660665
return this.configData.preRunCoverageTarget;
661666
}
@@ -764,6 +769,7 @@ export class ConfigurationReader implements vscode.Disposable {
764769
additionalBuildProblemMatchers: new vscode.EventEmitter<BuildProblemMatcherConfig[]>(),
765770
shell: new vscode.EventEmitter<string | null>(),
766771
setBuildTargetSameAsLaunchTarget: new vscode.EventEmitter<boolean>(),
772+
languageServerOnlyMode: new vscode.EventEmitter<boolean>(),
767773
modifyLists: new vscode.EventEmitter<ModifyListsSettings>(),
768774
outlineViewType: new vscode.EventEmitter<string>()
769775
};

src/extension.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ export class ExtensionManager implements vscode.Disposable {
141141
}
142142
});
143143
}
144+
this.workspaceConfig.onChange('languageServerOnlyMode', async () => {
145+
await updateFullFeatureSet();
146+
});
144147

145148
this.updateTouchBarVisibility(this.workspaceConfig.touchbar);
146149
this.workspaceConfig.onChange('touchbar', config => this.updateTouchBarVisibility(config));
@@ -220,7 +223,7 @@ export class ExtensionManager implements vscode.Disposable {
220223
await setContextAndStore(multiProjectModeKey, this.projectController.hasMultipleProjects);
221224
// Update the full/partial view of the workspace by verifying if after the folder removal
222225
// it still has at least one CMake project.
223-
await enableFullFeatureSet(this.workspaceHasAtLeastOneProject());
226+
await updateFullFeatureSet();
224227
}
225228

226229
this.projectOutline.removeFolder(folder);
@@ -294,7 +297,7 @@ export class ExtensionManager implements vscode.Disposable {
294297
}
295298
await this.initActiveProject();
296299
}
297-
const isFullyActivated: boolean = this.workspaceHasAtLeastOneProject();
300+
const isFullyActivated: boolean = this.workspaceHasAtLeastOneProject() && !this.workspaceConfig.languageServerOnlyMode;
298301
await enableFullFeatureSet(isFullyActivated);
299302

300303
const telemetryProperties: telemetry.Properties = {
@@ -711,6 +714,10 @@ export class ExtensionManager implements vscode.Disposable {
711714
if (!project) {
712715
return;
713716
}
717+
if (project.workspaceContext.config.languageServerOnlyMode) {
718+
log.debug('Skipping CMake project integration during workspace open because language-server-only mode is enabled.');
719+
return;
720+
}
714721
const rootFolder: vscode.WorkspaceFolder = project.workspaceFolder;
715722
// Scan for kits even under presets mode, so we can create presets from compilers.
716723
// Silent re-scan when detecting a breaking change in the kits definition.
@@ -2865,8 +2872,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<api.CM
28652872
// and show or hide the buttons in the status bar, according to the boolean.
28662873
// The scope of this is the whole workspace.
28672874
export async function enableFullFeatureSet(fullFeatureSet: boolean) {
2868-
await setContextAndStore("cmake:enableFullFeatureSet", fullFeatureSet);
2869-
extensionManager?.showStatusBar(fullFeatureSet);
2875+
const enableFeatureSet = fullFeatureSet && !extensionManager?.getWorkspaceConfig().languageServerOnlyMode;
2876+
await setContextAndStore("cmake:enableFullFeatureSet", enableFeatureSet);
2877+
extensionManager?.showStatusBar(enableFeatureSet);
28702878
}
28712879

28722880
export function getActiveProject(): CMakeProject | undefined {

src/projectController.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,9 @@ export class ProjectController implements vscode.Disposable {
635635
private async doCMakeFileChangeReconfigure(uri: vscode.Uri): Promise<void> {
636636
const activeProject: CMakeProject | undefined = this.getActiveCMakeProject();
637637
if (activeProject) {
638+
if (activeProject.workspaceContext.config.languageServerOnlyMode) {
639+
return;
640+
}
638641
const isFileInsideActiveProject: boolean = util.isFileInsideFolder(uri, activeProject.isMultiProjectFolder ? activeProject.folderPath : activeProject.workspaceFolder.uri.fsPath);
639642
// A save of settings.json triggers the doSave event (doSaveTextDocument or onDidRenameFile)
640643
// before the settings update event (onDidChangeConfiguration).

test/smoke/goodProject/goodProject.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { CMakeProject } from '@cmt/cmakeProject';
22
import { expect } from 'chai';
3+
import * as vscode from 'vscode';
34

45
import { smokeSuite, smokeTestDefaultKit } from '@test/smoke/smoke';
56

@@ -22,4 +23,57 @@ suite('Smoke test: good project', () => {
2223
});
2324
});
2425
});
26+
27+
test('languageServerOnlyMode: getCMakeDriverInstance returns null', async () => {
28+
smokeSuite('Smoke test: languageServerOnlyMode driver', suite => {
29+
let cmakeProject: CMakeProject;
30+
suite.setup('create cmake-tools', async test => {
31+
cmakeProject = await test.createCMakeProject({
32+
kit: await smokeTestDefaultKit()
33+
});
34+
});
35+
suite.teardown('dispose cmake-tools', async () => {
36+
if (cmakeProject) {
37+
await cmakeProject.asyncDispose();
38+
}
39+
});
40+
suite.smokeTest('getCMakeDriverInstance returns null when languageServerOnlyMode is enabled', async () => {
41+
// Enable languageServerOnlyMode
42+
cmakeProject.workspaceContext.config.updatePartial({ languageServerOnlyMode: true });
43+
44+
// Verify getCMakeDriverInstance returns null
45+
const driver = await cmakeProject.getCMakeDriverInstance();
46+
expect(driver).to.be.null;
47+
});
48+
});
49+
});
50+
51+
test('languageServerOnlyMode: doCMakeFileChangeReconfigure skips reconfiguration', async () => {
52+
smokeSuite('Smoke test: languageServerOnlyMode reconfigure', suite => {
53+
let cmakeProject: CMakeProject;
54+
suite.setup('create cmake-tools', async test => {
55+
cmakeProject = await test.createCMakeProject({
56+
kit: await smokeTestDefaultKit()
57+
});
58+
});
59+
suite.teardown('dispose cmake-tools', async () => {
60+
if (cmakeProject) {
61+
await cmakeProject.asyncDispose();
62+
}
63+
});
64+
suite.smokeTest('doCMakeFileChangeReconfigure returns early when languageServerOnlyMode is enabled', async () => {
65+
// Enable languageServerOnlyMode
66+
cmakeProject.workspaceContext.config.updatePartial({ languageServerOnlyMode: true });
67+
68+
// Call doCMakeFileChangeReconfigure - should return early without error
69+
// Use a dummy URI for testing
70+
const dummyUri = vscode.Uri.file(cmakeProject.sourceDir + '/CMakeLists.txt');
71+
await cmakeProject.doCMakeFileChangeReconfigure(dummyUri);
72+
73+
// If we get here without error and no driver was created, the test passes
74+
const driver = await cmakeProject.getCMakeDriverInstance();
75+
expect(driver).to.be.null;
76+
});
77+
});
78+
});
2579
});

test/unit-tests/config.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ function createConfig(conf: Partial<ExtensionConfigurationSettings>): Configurat
8484
enableAutomaticKitScan: true,
8585
removeStaleKitsOnScan: false,
8686
enableLanguageServices: true,
87+
languageServerOnlyMode: false,
8788
preRunCoverageTarget: null,
8889
postRunCoverageTarget: null,
8990
coverageInfoFiles: [],

0 commit comments

Comments
 (0)