diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index fceb9c852cc..3169c8326c3 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -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)); diff --git a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditor.ts b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditor.ts index f5de7aec226..df45fc2b9b5 100644 --- a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditor.ts @@ -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('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 { + 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('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 = ''; diff --git a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditorInput.ts b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditorInput.ts index f317182e7c6..444a0bb118e 100644 --- a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditorInput.ts @@ -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; + override get capabilities(): EditorInputCapabilities { return super.capabilities | EditorInputCapabilities.Singleton | EditorInputCapabilities.RequiresModal; } @@ -59,4 +62,34 @@ export class AICustomizationManagementEditorInput extends EditorInput { override async resolve(): Promise { return null; } + + override isDirty(): boolean { + return this._isDirty; + } + + override async save(group: GroupIdentifier, options?: ISaveOptions): Promise { + 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 { + this.setDirty(false); + } + + setDirty(dirty: boolean): void { + if (this._isDirty !== dirty) { + this._isDirty = dirty; + this._onDidChangeDirty.fire(); + } + } + + setSaveHandler(handler: (() => Promise) | undefined): void { + this._saveHandler = handler; + } }