Skip to content

Commit 889cf8a

Browse files
Copilotsnehara99
andauthored
Allow preset modification commands to target CMakeUserPresets.json (#4733)
* Initial plan * Allow preset modification commands to target CMakeUserPresets.json Add determineTargetPresetsFile() to presetsController that: 1. Uses the focused presets file editor if one is active 2. Prompts user to choose when both files exist 3. Uses whichever file exists when only one is present 4. Falls back to inheritance-based logic when neither exists Co-authored-by: snehara99 <[email protected]> * Address code review: use path.basename and simplify conditions Co-authored-by: snehara99 <[email protected]> * Add changelog entry for issue #4564 in improvements section Co-authored-by: snehara99 <[email protected]> * Remove dead inheritsFromUserPreset fallback in addPresetAddUpdate When determineTargetPresetsFile() returns null, neither presets file exists on disk. The inheritsFromUserPreset() check was dead code in this path since it checks against user presets that don't exist. Default to CMakePresets.json instead. Co-authored-by: snehara99 <[email protected]> * Restore inheritsFromUserPreset as first priority in addPresetAddUpdate Check if the new preset inherits from a user preset first — if so, automatically write to CMakeUserPresets.json without prompting. Only fall through to determineTargetPresetsFile() when there's no inheritance relationship dictating the target file. Co-authored-by: snehara99 <[email protected]> * Fix active editor detection by capturing activeTextEditor before QuickPick dialogs Snapshot vscode.window.activeTextEditor at the start of each addXxxPreset() method, before any QuickPick interactions that could shift focus. Pass the captured path through to determineTargetPresetsFile() so the active editor check works correctly. Co-authored-by: snehara99 <[email protected]> * Fix path comparison using platformNormalizePath for cross-platform compatibility The strict equality comparison between uri.fsPath and path.join() results could fail due to case differences on Windows or path separator differences. Use util.platformNormalizePath() on both paths before comparing to handle case normalization on Windows and Unicode normalization on macOS. Co-authored-by: snehara99 <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: snehara99 <[email protected]>
1 parent b80d038 commit 889cf8a

2 files changed

Lines changed: 82 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Features:
1111
Improvements:
1212
- Add MSVC linker error problem matching to the Problems pane. [#4675](https://github.com/microsoft/vscode-cmake-tools/pull/4675) [@bradphelan](https://github.com/bradphelan)
1313
- Use environment variables from `cmake.environment` and `cmake.configureEnvironment` when expanding `$penv{}` macros in CMake Presets `include` paths. [#3578](https://github.com/microsoft/vscode-cmake-tools/issues/3578)
14+
- Allow preset modification commands to target CMakeUserPresets.json. The target file is determined by the focused editor, or by prompting the user when both files exist. [#4564](https://github.com/microsoft/vscode-cmake-tools/issues/4564)
1415

1516
Bug Fixes:
1617
- Fix initial kit scan ignoring `cmake.enableAutomaticKitScan: false` on first workspace open, and prevent redundant concurrent scans in multi-project workspaces. [#4726](https://github.com/microsoft/vscode-cmake-tools/issues/4726)

src/presets/presetsController.ts

Lines changed: 81 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ export class PresetsController implements vscode.Disposable {
202202
}
203203

204204
async addConfigurePreset(quickStart?: boolean): Promise<boolean> {
205+
const activeDocumentPath = vscode.window.activeTextEditor?.document.uri.fsPath;
206+
205207
interface AddPresetQuickPickItem extends vscode.QuickPickItem {
206208
name: string;
207209
}
@@ -423,7 +425,7 @@ export class PresetsController implements vscode.Disposable {
423425
return false;
424426
}
425427
newPreset.name = name;
426-
await this.addPresetAddUpdate(newPreset, 'configurePresets');
428+
await this.addPresetAddUpdate(newPreset, 'configurePresets', activeDocumentPath);
427429

428430
// Ensure that we update our local copies of the PresetsFile so that adding the build preset happens as expected.
429431
await this.reapplyPresets();
@@ -435,7 +437,7 @@ export class PresetsController implements vscode.Disposable {
435437
configurePreset: newPreset.name,
436438
configuration: 'Debug'
437439
};
438-
await this.addPresetAddUpdate(buildPreset, 'buildPresets');
440+
await this.addPresetAddUpdate(buildPreset, 'buildPresets', activeDocumentPath);
439441
}
440442

441443
if (before.length === 0) {
@@ -463,6 +465,8 @@ export class PresetsController implements vscode.Disposable {
463465
}
464466

465467
async addBuildPreset(): Promise<boolean> {
468+
const activeDocumentPath = vscode.window.activeTextEditor?.document.uri.fsPath;
469+
466470
if (preset.allConfigurePresets(this.folderPath).length === 0) {
467471
return this.handleNoConfigurePresets();
468472
}
@@ -532,14 +536,16 @@ export class PresetsController implements vscode.Disposable {
532536
}
533537

534538
newPreset.name = name;
535-
await this.addPresetAddUpdate(newPreset, 'buildPresets');
539+
await this.addPresetAddUpdate(newPreset, 'buildPresets', activeDocumentPath);
536540
}
537541

538542
return true;
539543
}
540544
}
541545

542546
async addTestPreset(): Promise<boolean> {
547+
const activeDocumentPath = vscode.window.activeTextEditor?.document.uri.fsPath;
548+
543549
if (preset.allConfigurePresets(this.folderPath).length === 0) {
544550
return this.handleNoConfigurePresets();
545551
}
@@ -609,13 +615,15 @@ export class PresetsController implements vscode.Disposable {
609615
}
610616

611617
newPreset.name = name;
612-
await this.addPresetAddUpdate(newPreset, 'testPresets');
618+
await this.addPresetAddUpdate(newPreset, 'testPresets', activeDocumentPath);
613619
}
614620
return true;
615621
}
616622
}
617623

618624
async addPackagePreset(): Promise<boolean> {
625+
const activeDocumentPath = vscode.window.activeTextEditor?.document.uri.fsPath;
626+
619627
if (preset.allConfigurePresets(this.folderPath).length === 0) {
620628
return this.handleNoConfigurePresets();
621629
}
@@ -685,13 +693,15 @@ export class PresetsController implements vscode.Disposable {
685693
}
686694

687695
newPreset.name = name;
688-
await this.addPresetAddUpdate(newPreset, 'packagePresets');
696+
await this.addPresetAddUpdate(newPreset, 'packagePresets', activeDocumentPath);
689697
}
690698
return true;
691699
}
692700
}
693701

694702
async addWorkflowPreset(): Promise<boolean> {
703+
const activeDocumentPath = vscode.window.activeTextEditor?.document.uri.fsPath;
704+
695705
if (preset.allConfigurePresets(this.folderPath).length === 0) {
696706
return this.handleNoConfigurePresets();
697707
}
@@ -774,7 +784,7 @@ export class PresetsController implements vscode.Disposable {
774784
}
775785

776786
newPreset.name = name;
777-
await this.addPresetAddUpdate(newPreset, 'workflowPresets');
787+
await this.addPresetAddUpdate(newPreset, 'workflowPresets', activeDocumentPath);
778788
}
779789
return true;
780790
}
@@ -1562,19 +1572,80 @@ export class PresetsController implements vscode.Disposable {
15621572
}
15631573
}
15641574

1575+
/**
1576+
* Determines which presets file to target for adding a new preset.
1577+
* Returns 'user' for CMakeUserPresets.json, 'main' for CMakePresets.json,
1578+
* null to fall back to the default inheritance-based logic,
1579+
* or undefined if the user cancelled the selection.
1580+
*/
1581+
private async determineTargetPresetsFile(activeDocumentPath?: string): Promise<'user' | 'main' | null | undefined> {
1582+
// 1. Check if the active text editor is showing a presets file
1583+
if (activeDocumentPath) {
1584+
const normalizedActivePath = util.platformNormalizePath(activeDocumentPath);
1585+
if (normalizedActivePath === util.platformNormalizePath(this.presetsPath)) {
1586+
return 'main';
1587+
}
1588+
if (normalizedActivePath === util.platformNormalizePath(this.userPresetsPath)) {
1589+
return 'user';
1590+
}
1591+
}
1592+
1593+
// 2. Check which presets files exist
1594+
const presetsExists = await fs.exists(this.presetsPath);
1595+
const userPresetsExists = await fs.exists(this.userPresetsPath);
1596+
1597+
if (presetsExists && userPresetsExists) {
1598+
// Both files exist: prompt user to choose
1599+
const cmakePresetsLabel = path.basename(this.presetsPath);
1600+
const cmakeUserPresetsLabel = path.basename(this.userPresetsPath);
1601+
const selection = await vscode.window.showQuickPick(
1602+
[cmakePresetsLabel, cmakeUserPresetsLabel],
1603+
{ placeHolder: localize('select.preset.file', 'Select which presets file to add the new preset to') }
1604+
);
1605+
if (!selection) {
1606+
return undefined; // User cancelled
1607+
}
1608+
return selection === cmakeUserPresetsLabel ? 'user' : 'main';
1609+
}
1610+
1611+
if (userPresetsExists) {
1612+
return 'user';
1613+
}
1614+
1615+
if (presetsExists) {
1616+
return 'main';
1617+
}
1618+
1619+
// Neither file exists, fall back to default behavior
1620+
return null;
1621+
}
1622+
15651623
// Note: in case anyone want to change this, presetType must match the corresponding key in presets.json files
15661624
async addPresetAddUpdate(newPreset: preset.ConfigurePreset | preset.BuildPreset | preset.TestPreset | preset.PackagePreset | preset.WorkflowPreset,
1567-
presetType: 'configurePresets' | 'buildPresets' | 'testPresets' | 'packagePresets' | 'workflowPresets') {
1568-
// If the new preset inherits from a user preset, it should be added to the user presets file.
1625+
presetType: 'configurePresets' | 'buildPresets' | 'testPresets' | 'packagePresets' | 'workflowPresets', activeDocumentPath?: string) {
15691626
let presetsFile: preset.PresetsFile;
15701627
let isUserPreset = false;
15711628

1629+
// If the new preset inherits from a user preset, it should be added to the user presets file.
15721630
if (preset.inheritsFromUserPreset(newPreset, presetType, this.folderPath)) {
1573-
presetsFile = preset.getOriginalUserPresetsFile(this.folderPath) || { version: 8 };
15741631
isUserPreset = true;
1632+
} else {
1633+
// Otherwise, let the user choose the target file.
1634+
const targetFile = await this.determineTargetPresetsFile(activeDocumentPath);
1635+
if (targetFile === undefined) {
1636+
// User cancelled the selection
1637+
return;
1638+
}
1639+
if (targetFile !== null) {
1640+
isUserPreset = targetFile === 'user';
1641+
}
1642+
// When targetFile is null (neither file exists), default to CMakePresets.json (isUserPreset remains false)
1643+
}
1644+
1645+
if (isUserPreset) {
1646+
presetsFile = preset.getOriginalUserPresetsFile(this.folderPath) || { version: 8 };
15751647
} else {
15761648
presetsFile = preset.getOriginalPresetsFile(this.folderPath) || { version: 8 };
1577-
isUserPreset = false;
15781649
}
15791650

15801651
if (!presetsFile[presetType]) {

0 commit comments

Comments
 (0)