Skip to content

Commit 9d4df98

Browse files
Tobias, Liedlhanniavalera
authored andcommitted
Added: cache UI also handles cmake variables set in the cmake presets.
1 parent 38ff27a commit 9d4df98

4 files changed

Lines changed: 247 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ Improvements:
7171
- Make it easier for a new developer of CMake Tools to run tests. [#4620](https://github.com/microsoft/vscode-cmake-tools/pull/4620) [@cwalther](https://github.com/cwalther)
7272

7373
Bug Fixes:
74+
- Fix cache edits from "Edit Cache (UI)" being overwritten in presets mode when the edited variable is defined by the active configure preset. The extension now writes an override into CMakeUserPresets.json and reconfigures with that user preset so the change persists.
7475
- Fix stale C/C++ custom-configuration entries persisting after reconfigure/preset switches, which could cause Go to Definition/IntelliSense to surface symbols from inactive sources in the same folder. [#4472](https://github.com/microsoft/vscode-cmake-tools/issues/4472)
7576
- Fix IntelliSense not updating when switching the active project in multi-project workspaces with multiple `cmake.sourceDirectory` entries. [#4390](https://github.com/microsoft/vscode-cmake-tools/issues/4390)
7677
- Fix `tasks.json` schema validation rejecting valid CMake task commands `package` and `workflow`. [#4167](https://github.com/microsoft/vscode-cmake-tools/issues/4167)

src/cmakeProject.ts

Lines changed: 191 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import rollbar from '@cmt/rollbar';
3939
import * as telemetry from '@cmt/telemetry';
4040
import { VariantManager } from '@cmt/kits/variant';
4141
import * as nls from 'vscode-nls';
42-
import { ConfigurationWebview } from '@cmt/ui/cacheView';
42+
import { ConfigurationWebview, IOption } from '@cmt/ui/cacheView';
4343
import { enableFullFeatureSet, extensionManager, updateFullFeatureSet, setContextAndStore } from '@cmt/extension';
4444
import { CMakeCommunicationMode, ConfigurationReader, OptionConfig, UseCMakePresets, checkConfigureOverridesPresent } from '@cmt/config';
4545
import * as preset from '@cmt/presets/preset';
@@ -2461,6 +2461,192 @@ export class CMakeProject {
24612461
.then(doc => void vscode.window.showTextDocument(doc));
24622462
}
24632463

2464+
private getPresetCacheVariableType(option: IOption, presetCacheVariable: preset.CacheVarType | undefined): string {
2465+
if (option.type === 'Bool') {
2466+
return 'BOOL';
2467+
}
2468+
2469+
if (presetCacheVariable && typeof presetCacheVariable === 'object' && 'type' in presetCacheVariable && util.isString((presetCacheVariable as any).type) && (presetCacheVariable as any).type.toUpperCase() !== 'BOOL') {
2470+
return (presetCacheVariable as any).type;
2471+
}
2472+
2473+
return 'STRING';
2474+
}
2475+
2476+
private getPresetCacheVariableValue(option: IOption): string {
2477+
return util.isBoolean(option.value) ? (option.value ? 'TRUE' : 'FALSE') : String(option.value);
2478+
}
2479+
2480+
private getPresetCacheVariableRawValue(cacheVariable: preset.CacheVarType | undefined): string | undefined {
2481+
if (util.isString(cacheVariable)) {
2482+
return cacheVariable;
2483+
}
2484+
2485+
if (cacheVariable === true) {
2486+
return 'TRUE';
2487+
}
2488+
2489+
if (cacheVariable && typeof cacheVariable === 'object' && 'value' in cacheVariable) {
2490+
const value = cacheVariable.value;
2491+
if (util.isBoolean(value)) {
2492+
return value ? 'TRUE' : 'FALSE';
2493+
}
2494+
if (util.isString(value)) {
2495+
return value;
2496+
}
2497+
}
2498+
2499+
return undefined;
2500+
}
2501+
2502+
private findPresetDefinitionByName(name: string): preset.ConfigurePreset | undefined {
2503+
const userPreset = preset.userConfigurePresets(this.folderPath, true).find(configurePreset => configurePreset.name === name);
2504+
if (userPreset) {
2505+
return userPreset;
2506+
}
2507+
return preset.configurePresets(this.folderPath, true).find(configurePreset => configurePreset.name === name);
2508+
}
2509+
2510+
private resolvePresetVariableOrigin(
2511+
presetName: string,
2512+
variableName: string,
2513+
visited: Set<string> = new Set()
2514+
): { presetName: string; isUserPreset: boolean; presetValue?: string } | undefined {
2515+
if (visited.has(presetName)) {
2516+
return undefined;
2517+
}
2518+
visited.add(presetName);
2519+
2520+
const configurePreset = this.findPresetDefinitionByName(presetName);
2521+
if (!configurePreset) {
2522+
return undefined;
2523+
}
2524+
2525+
const cacheVariable = configurePreset.cacheVariables?.[variableName];
2526+
if (cacheVariable !== undefined) {
2527+
const isUserPreset = configurePreset.isUserPreset ?? configurePreset.__file?.__path?.endsWith('CMakeUserPresets.json') ?? false;
2528+
return {
2529+
presetName: configurePreset.name,
2530+
isUserPreset,
2531+
presetValue: this.getPresetCacheVariableRawValue(cacheVariable)
2532+
};
2533+
}
2534+
2535+
if (configurePreset.inherits) {
2536+
const parents = util.isString(configurePreset.inherits) ? [configurePreset.inherits] : configurePreset.inherits;
2537+
if (parents) {
2538+
for (const parent of parents) {
2539+
const origin = this.resolvePresetVariableOrigin(parent, variableName, visited);
2540+
if (origin) {
2541+
return origin;
2542+
}
2543+
}
2544+
}
2545+
}
2546+
2547+
return undefined;
2548+
}
2549+
2550+
private async getCacheOptionPresetMetadata(options: IOption[]): Promise<Map<string, { presetSource: string; differsFromPresetValue?: boolean }>> {
2551+
const metadata = new Map<string, { presetSource: string; differsFromPresetValue?: boolean }>();
2552+
if (!this.useCMakePresets || !this.configurePreset) {
2553+
return metadata;
2554+
}
2555+
2556+
const activePresetName = this.configurePreset.name;
2557+
for (const option of options) {
2558+
const origin = this.resolvePresetVariableOrigin(activePresetName, option.key);
2559+
if (!origin) {
2560+
continue;
2561+
}
2562+
2563+
const sourceKind = origin.isUserPreset
2564+
? localize('preset.source.user', 'User preset')
2565+
: localize('preset.source.project', 'Project preset');
2566+
2567+
let differsFromPresetValue: boolean | undefined;
2568+
if (origin.presetValue !== undefined) {
2569+
if (option.type === 'Bool') {
2570+
differsFromPresetValue = util.isTruthy(String(option.value)) !== util.isTruthy(origin.presetValue);
2571+
} else {
2572+
differsFromPresetValue = String(option.value) !== origin.presetValue;
2573+
}
2574+
}
2575+
2576+
metadata.set(option.key, {
2577+
presetSource: `${sourceKind}: ${origin.presetName}`,
2578+
differsFromPresetValue
2579+
});
2580+
}
2581+
2582+
return metadata;
2583+
}
2584+
2585+
private async updatePresetBackedCacheVariableOverrides(dirtyOptions: IOption[]): Promise<void> {
2586+
if (!this.useCMakePresets || !this.configurePreset || dirtyOptions.length === 0) {
2587+
return;
2588+
}
2589+
2590+
const presetCacheVariables = this.configurePreset.cacheVariables;
2591+
if (!presetCacheVariables) {
2592+
return;
2593+
}
2594+
2595+
const presetBackedOptions = dirtyOptions.filter(option => Object.prototype.hasOwnProperty.call(presetCacheVariables, option.key));
2596+
if (presetBackedOptions.length === 0) {
2597+
return;
2598+
}
2599+
2600+
const userPresets = preset.getOriginalUserPresetsFile(this.folderPath)
2601+
?? { version: preset.getOriginalPresetsFile(this.folderPath)?.version ?? 8 };
2602+
userPresets.configurePresets = userPresets.configurePresets ?? [];
2603+
2604+
const currentPreset = this.configurePreset;
2605+
let targetPreset = currentPreset.isUserPreset
2606+
? userPresets.configurePresets.find(configurePreset => configurePreset.name === currentPreset.name)
2607+
: undefined;
2608+
2609+
if (!targetPreset) {
2610+
const defaultOverrideName = `${currentPreset.name}__cacheEditorOverride`;
2611+
let overrideName = defaultOverrideName;
2612+
let suffix = 1;
2613+
const allPresetNames = new Set(preset.allConfigurePresets(this.folderPath, true).map(configurePreset => configurePreset.name));
2614+
2615+
while (allPresetNames.has(overrideName) && !userPresets.configurePresets.find(configurePreset => configurePreset.name === overrideName)) {
2616+
overrideName = `${defaultOverrideName}_${suffix++}`;
2617+
}
2618+
2619+
targetPreset = userPresets.configurePresets.find(configurePreset => configurePreset.name === overrideName);
2620+
if (!targetPreset) {
2621+
targetPreset = {
2622+
name: overrideName,
2623+
hidden: true,
2624+
inherits: currentPreset.name,
2625+
cacheVariables: {}
2626+
};
2627+
userPresets.configurePresets.push(targetPreset);
2628+
}
2629+
}
2630+
2631+
targetPreset.cacheVariables = targetPreset.cacheVariables ?? {};
2632+
2633+
for (const option of presetBackedOptions) {
2634+
const presetCacheVariable = presetCacheVariables[option.key];
2635+
targetPreset.cacheVariables[option.key] = {
2636+
type: this.getPresetCacheVariableType(option, presetCacheVariable),
2637+
value: this.getPresetCacheVariableValue(option)
2638+
};
2639+
}
2640+
2641+
this.presetsController.suppressWatcherReapply = true;
2642+
await this.presetsController.updatePresetsFile(userPresets, true, false);
2643+
await this.presetsController.reapplyPresets();
2644+
2645+
if (targetPreset.name !== currentPreset.name) {
2646+
await this.presetsController.setConfigurePreset(targetPreset.name);
2647+
}
2648+
}
2649+
24642650
/**
24652651
* Implementation of `cmake.EditCacheUI`
24662652
*/
@@ -2472,9 +2658,10 @@ export class CMakeProject {
24722658
return 1;
24732659
}
24742660

2475-
this.cacheEditorWebview = new ConfigurationWebview(drv.cachePath, () => {
2476-
void this.configureInternal(ConfigureTrigger.commandEditCacheUI, [], ConfigureType.Cache);
2477-
});
2661+
this.cacheEditorWebview = new ConfigurationWebview(drv.cachePath, async dirtyOptions => {
2662+
await this.updatePresetBackedCacheVariableOverrides(dirtyOptions);
2663+
await this.configureInternal(ConfigureTrigger.commandEditCacheUI, [], ConfigureType.Cache);
2664+
}, async options => this.getCacheOptionPresetMetadata(options));
24782665
await this.cacheEditorWebview.initPanel();
24792666

24802667
this.cacheEditorWebview.panel.onDidDispose(() => {

src/presets/presetsController.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1723,7 +1723,7 @@ export class PresetsController implements vscode.Disposable {
17231723
return { insertSpaces, tabSize };
17241724
}
17251725

1726-
async updatePresetsFile(presetsFile: preset.PresetsFile, isUserPresets = false): Promise<vscode.TextEditor | undefined> {
1726+
async updatePresetsFile(presetsFile: preset.PresetsFile, isUserPresets = false, showInEditor = true): Promise<vscode.TextEditor | undefined> {
17271727
const presetsFilePath = isUserPresets ? this.userPresetsPath : this.presetsPath;
17281728
const indent = this.getIndentationSettings();
17291729
try {
@@ -1733,6 +1733,10 @@ export class PresetsController implements vscode.Disposable {
17331733
return;
17341734
}
17351735

1736+
if (!showInEditor) {
1737+
return undefined;
1738+
}
1739+
17361740
return vscode.window.showTextDocument(vscode.Uri.file(presetsFilePath));
17371741
}
17381742

src/ui/cacheView.ts

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFo
1212
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
1313

1414
export interface IOption {
15-
key: string; // same as CMake cache variable key names
16-
type: string; // "Bool" for boolean and "String" for anything else for now
15+
key: string; // same as CMake cache variable key names
16+
type: string; // "Bool" for boolean and "String" for anything else for now
1717
helpString: string;
1818
choices: string[];
19-
value: string; // value from the cache file or changed in the UI
19+
value: string | boolean; // value from the cache file or changed in the UI
2020
dirty: boolean; // if the variable was edited in the UI
21+
presetSource?: string; // where this variable is defined in presets
22+
differsFromPresetValue?: boolean; // true if cache value differs from preset value
2123
}
2224

2325
/**
@@ -50,7 +52,11 @@ export class ConfigurationWebview {
5052

5153
private options: IOption[] = [];
5254

53-
constructor(protected cachePath: string, protected save: () => void) {
55+
constructor(
56+
protected cachePath: string,
57+
protected save: (dirtyOptions: IOption[]) => Promise<void>,
58+
protected getPresetOptionMetadata?: (options: IOption[]) => Promise<Map<string, { presetSource: string; differsFromPresetValue?: boolean }>>
59+
) {
5460
this.panel = vscode.window.createWebviewPanel(
5561
'cmakeConfiguration', // Identifies the type of the webview. Used internally
5662
this.cmakeCacheEditorText, // Title of the panel displayed to the user
@@ -66,11 +72,17 @@ export class ConfigurationWebview {
6672
async persistCacheEntries() {
6773
if (this.isDirty) {
6874
telemetry.logEvent("editCMakeCache", { command: "saveCMakeCacheUI" });
69-
await this.saveCmakeCache(this.options);
75+
const dirtyOptions = this.options.filter(option => option.dirty);
76+
await this.saveCmakeCache(dirtyOptions);
7077
void vscode.window.showInformationMessage(localize('cmake.cache.saved', 'CMake options have been saved.'));
71-
// start configure
72-
this.save();
78+
// Start configure after persisting the edited values.
79+
await this.save(dirtyOptions);
7380
this.isDirty = false;
81+
// Force a post-configure refresh so the editor reflects cache changes immediately.
82+
this.options = await this.getConfigurationOptions();
83+
if (this.panel.visible) {
84+
await this.renderWebview(this.panel, false);
85+
}
7486
}
7587
}
7688

@@ -207,7 +219,11 @@ export class ConfigurationWebview {
207219

208220
async saveCmakeCache(options: IOption[]) {
209221
const cmakeCache = await CMakeCache.fromPath(this.cachePath);
210-
await cmakeCache.saveAll(options);
222+
const serializedOptions = options.map(option => ({
223+
key: option.key,
224+
value: util.isBoolean(option.value) ? (option.value ? 'TRUE' : 'FALSE') : option.value
225+
}));
226+
await cmakeCache.saveAll(serializedOptions);
211227
}
212228

213229
/**
@@ -225,6 +241,18 @@ export class ConfigurationWebview {
225241
options.push({ key: entry.key, helpString: entry.helpString, choices: entry.choices, type: (entry.type === CacheEntryType.Bool) ? "Bool" : "String", value: entry.value, dirty: false });
226242
}
227243
}
244+
245+
if (this.getPresetOptionMetadata) {
246+
const metadataByKey = await this.getPresetOptionMetadata(options);
247+
for (const option of options) {
248+
const metadata = metadataByKey.get(option.key);
249+
if (metadata) {
250+
option.presetSource = metadata.presetSource;
251+
option.differsFromPresetValue = metadata.differsFromPresetValue;
252+
}
253+
}
254+
}
255+
228256
return options;
229257
}
230258

@@ -254,6 +282,10 @@ export class ConfigurationWebview {
254282
const saveButtonText = localize("save", "Save");
255283
const keyColumnText = localize("key", "Key");
256284
const valueColumnText = localize("value", "Value");
285+
const sourceColumnText = localize("source", "Source");
286+
const notSetInPresetsText = localize("not.set.in.presets", "Cmake Cache");
287+
const differsFromPresetText = localize("differs.from.preset", "Changed in cache");
288+
const matchesPresetText = localize("matches.preset", "Matches preset");
257289

258290
let html = `
259291
<!DOCTYPE html>
@@ -484,6 +516,7 @@ export class ConfigurationWebview {
484516
<th style="width: 30px"></th>
485517
<th style="width: 1px; white-space: nowrap;">${keyColumnText}</th>
486518
<th>${valueColumnText}</th>
519+
<th style="width: 1px; white-space: nowrap;">${sourceColumnText}</th>
487520
</tr>
488521
${key}
489522
</table>
@@ -506,9 +539,10 @@ export class ConfigurationWebview {
506539

507540
const id = escapeAttribute(option.key);
508541
let editControls = '';
542+
const stringValue = util.isBoolean(option.value) ? (option.value ? 'TRUE' : 'FALSE') : String(option.value);
509543

510544
if (option.type === "Bool") {
511-
editControls = `<input class="cmake-input-bool" id="${id}" type="checkbox" ${util.isTruthy(option.value) ? 'checked' : ''}>
545+
editControls = `<input class="cmake-input-bool" id="${id}" type="checkbox" ${util.isTruthy(stringValue) ? 'checked' : ''}>
512546
<label id="LABEL_${id}" for="${id}"/>`;
513547
} else {
514548
const hasChoices = option.choices.length > 0;
@@ -517,14 +551,20 @@ export class ConfigurationWebview {
517551
${option.choices.map(ch => `<option value="${escapeAttribute(ch)}">`).join()}
518552
</datalist>`;
519553
}
520-
editControls += `<input class="cmake-input-text" id="${id}" value="${escapeAttribute(option.value)}" style="width: 90%;"
554+
editControls += `<input class="cmake-input-text" id="${id}" value="${escapeAttribute(stringValue)}" style="width: 90%;"
521555
type="text" ${hasChoices ? `list="CHOICES_${id}"` : ''}>`;
522556
}
523557

558+
let sourceText = option.presetSource || notSetInPresetsText;
559+
if (option.presetSource && option.differsFromPresetValue !== undefined) {
560+
sourceText = `${sourceText} | ${option.differsFromPresetValue ? differsFromPresetText : matchesPresetText}`;
561+
}
562+
524563
return `<tr class="content-tr">
525564
<td></td>
526565
<td title="${escapeAttribute(option.helpString)}">${escapeHtml(option.key)}</td>
527566
<td>${editControls}</td>
567+
<td>${escapeHtml(sourceText)}</td>
528568
</tr>`;
529569
});
530570

0 commit comments

Comments
 (0)