mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-02 08:15:56 +01:00
customizations editor: hook up dirty state for built-in customization editing (#305300)
* Enhance AI Customization Management Editor with confirmation handling and dirty state tracking * Improve dialog handling by waiting for keyboard events to propagate before opening confirmation * Refactor AI Customization Management Editor to replace confirmation handling with save handling * Add escape key handling to close dialog only if previously pressed * Address review: guard save() against auto-save, reset editor dirty baseline - Only run the pick-target save flow on explicit saves (not auto-save from focus/window changes) - Reset _editorContentChanged after successful save so the embedded editor stays clean until the next edit (updateEditorActionButton propagates this to input.setDirty via updateInputDirtyState) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -328,9 +328,14 @@ export class Dialog extends Disposable {
|
||||
|
||||
// Handle keyboard events globally: Tab, Arrow-Left/Right
|
||||
const window = getWindow(this.container);
|
||||
let sawEscapeKeyDown = false;
|
||||
this._register(addDisposableListener(window, 'keydown', e => {
|
||||
const evt = new StandardKeyboardEvent(e);
|
||||
|
||||
if (evt.equals(KeyCode.Escape)) {
|
||||
sawEscapeKeyDown = true;
|
||||
}
|
||||
|
||||
if (evt.equals(KeyMod.Alt)) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
@@ -470,7 +475,7 @@ export class Dialog extends Disposable {
|
||||
EventHelper.stop(e, true);
|
||||
const evt = new StandardKeyboardEvent(e);
|
||||
|
||||
if (!this.options.disableCloseAction && evt.equals(KeyCode.Escape)) {
|
||||
if (!this.options.disableCloseAction && evt.equals(KeyCode.Escape) && sawEscapeKeyDown) {
|
||||
close();
|
||||
}
|
||||
}, true));
|
||||
|
||||
@@ -1202,6 +1202,8 @@ export class AICustomizationManagementEditor extends EditorPane {
|
||||
this.inEditorContextKey.set(true);
|
||||
this.sectionContextKey.set(this.selectedSection);
|
||||
|
||||
input.setSaveHandler(() => this.handleBuiltinSave());
|
||||
|
||||
this.telemetryService.publicLog2<CustomizationEditorOpenedEvent, CustomizationEditorOpenedClassification>('chatCustomizationEditor.opened', {
|
||||
section: this.selectedSection,
|
||||
});
|
||||
@@ -1214,6 +1216,12 @@ export class AICustomizationManagementEditor extends EditorPane {
|
||||
}
|
||||
|
||||
override clearInput(): void {
|
||||
const input = this.input;
|
||||
if (input instanceof AICustomizationManagementEditorInput) {
|
||||
input.setSaveHandler(undefined);
|
||||
input.setDirty(false);
|
||||
}
|
||||
|
||||
this.inEditorContextKey.set(false);
|
||||
if (this.viewMode === 'editor') {
|
||||
this.goBackToList();
|
||||
@@ -1661,6 +1669,8 @@ export class AICustomizationManagementEditor extends EditorPane {
|
||||
}
|
||||
|
||||
private updateEditorActionButton(): void {
|
||||
this.updateInputDirtyState();
|
||||
|
||||
if (!this.editorActionButton || !this.editorActionButtonIcon) {
|
||||
return;
|
||||
}
|
||||
@@ -1682,6 +1692,49 @@ export class AICustomizationManagementEditor extends EditorPane {
|
||||
&& (this.currentEditingPromptType === PromptsType.prompt || this.currentEditingPromptType === PromptsType.skill);
|
||||
}
|
||||
|
||||
private updateInputDirtyState(): void {
|
||||
const input = this.input;
|
||||
if (input instanceof AICustomizationManagementEditorInput) {
|
||||
input.setDirty(this.shouldShowBuiltinSaveAction());
|
||||
}
|
||||
}
|
||||
|
||||
private async handleBuiltinSave(): Promise<boolean> {
|
||||
if (!this.shouldShowBuiltinSaveAction()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const target = await this.pickBuiltinPromptSaveTarget();
|
||||
if (!target || target.target === 'cancel') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const saveRequest = this.createBuiltinPromptSaveRequest(target);
|
||||
if (!saveRequest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.saveBuiltinPromptCopy(saveRequest);
|
||||
this.telemetryService.publicLog2<CustomizationEditorSaveItemEvent, CustomizationEditorSaveItemClassification>('chatCustomizationEditor.saveItem', {
|
||||
promptType: this.currentEditingPromptType ?? '',
|
||||
storage: String(this.currentEditingStorage ?? ''),
|
||||
saveTarget: target.target,
|
||||
});
|
||||
|
||||
this._editorContentChanged = false;
|
||||
this.updateEditorActionButton();
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to save built-in override:', error);
|
||||
this.notificationService.warn(target.target === 'workspace'
|
||||
? localize('saveBuiltinCopyFailedWorkspace', "Could not save the override to the workspace.")
|
||||
: localize('saveBuiltinCopyFailedUser', "Could not save the override to your user folder."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private resetEditorSaveIndicator(): void {
|
||||
this.editorSaveIndicator.className = 'editor-save-indicator';
|
||||
this.editorSaveIndicator.title = '';
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { Codicon } from '../../../../../base/common/codicons.js';
|
||||
import { ThemeIcon } from '../../../../../base/common/themables.js';
|
||||
import { localize } from '../../../../../nls.js';
|
||||
import { IUntypedEditorInput, EditorInputCapabilities } from '../../../../common/editor.js';
|
||||
import { IUntypedEditorInput, EditorInputCapabilities, GroupIdentifier, ISaveOptions, SaveReason } from '../../../../common/editor.js';
|
||||
import { EditorInput } from '../../../../common/editor/editorInput.js';
|
||||
import { AI_CUSTOMIZATION_MANAGEMENT_EDITOR_INPUT_ID } from './aiCustomizationManagement.js';
|
||||
|
||||
@@ -20,6 +20,9 @@ export class AICustomizationManagementEditorInput extends EditorInput {
|
||||
|
||||
readonly resource = undefined;
|
||||
|
||||
private _isDirty = false;
|
||||
private _saveHandler?: () => Promise<boolean>;
|
||||
|
||||
override get capabilities(): EditorInputCapabilities {
|
||||
return super.capabilities | EditorInputCapabilities.Singleton | EditorInputCapabilities.RequiresModal;
|
||||
}
|
||||
@@ -59,4 +62,34 @@ export class AICustomizationManagementEditorInput extends EditorInput {
|
||||
override async resolve(): Promise<null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
override isDirty(): boolean {
|
||||
return this._isDirty;
|
||||
}
|
||||
|
||||
override async save(group: GroupIdentifier, options?: ISaveOptions): Promise<EditorInput | undefined> {
|
||||
if (options?.reason !== undefined && options.reason !== SaveReason.EXPLICIT) {
|
||||
return undefined;
|
||||
}
|
||||
if (this._saveHandler) {
|
||||
const saved = await this._saveHandler();
|
||||
return saved ? this : undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
override async revert(): Promise<void> {
|
||||
this.setDirty(false);
|
||||
}
|
||||
|
||||
setDirty(dirty: boolean): void {
|
||||
if (this._isDirty !== dirty) {
|
||||
this._isDirty = dirty;
|
||||
this._onDidChangeDirty.fire();
|
||||
}
|
||||
}
|
||||
|
||||
setSaveHandler(handler: (() => Promise<boolean>) | undefined): void {
|
||||
this._saveHandler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user