From e76806698f69d4e8353defcadcf67f3020512295 Mon Sep 17 00:00:00 2001 From: DukeNgn Date: Fri, 6 Nov 2020 15:53:01 -0500 Subject: [PATCH] preferences: Support 'Close On File Delete' preference + Add new preference `workbench.editor.closeOnFileDelete` to control whether the editor should be closed if another process deleted or renamed the file. + Change deletedSuffix to ` (deleted)`. Signed-off-by: Duc Nguyen --- examples/api-tests/src/saveable.spec.js | 21 +++++++++++++++++-- packages/core/src/browser/core-preferences.ts | 7 +++++++ .../filesystem-frontend-contribution.ts | 16 ++++++++------ 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/examples/api-tests/src/saveable.spec.js b/examples/api-tests/src/saveable.spec.js index 3214d4c8e635e..f8fa31d75fe7c 100644 --- a/examples/api-tests/src/saveable.spec.js +++ b/examples/api-tests/src/saveable.spec.js @@ -67,6 +67,7 @@ describe('Saveable', function () { beforeEach(async () => { await preferences.set('editor.autoSave', 'off', undefined, rootUri.toString()); + await preferences.set('editor.closeOnFileDelete', true); await editorManager.closeAll({ save: false }); await fileService.create(fileUri, 'foo', { fromUserGesture: false, overwrite: true }); widget = /** @type {EditorWidget & SaveableWidget} */ @@ -227,6 +228,22 @@ describe('Saveable', function () { assert.equal(state.value, 'foo', 'fs should NOT be updated after rejected close'); }); + it('delete file for saved with editor.CloseOnFileDelete off', async () => { + await preferences.set('editor.closeOnFileDelete', false); + assert.isFalse(Saveable.isDirty(widget), 'should NOT be dirty before delete'); + assert.isTrue(editor.document.valid, 'should be valid before delete'); + const waitForInvalid = new Deferred(); + const listener = editor.document.onDidChangeValid(() => waitForInvalid.resolve()); + try { + await fileService.delete(fileUri); + await waitForInvalid.promise; + assert.isFalse(editor.document.valid, 'should be INVALID after delete'); + assert.isFalse(widget.isDisposed, 'model should NOT be disposed after delete'); + } finally { + listener.dispose(); + } + }); + it('accept save on close and reject it', async () => { let outOfSync = false; toTearDown.push(setShouldOverwrite(async () => { @@ -284,7 +301,7 @@ describe('Saveable', function () { try { await fileService.delete(fileUri); await waitForDidChangeTitle.promise; - assert.isTrue(widget.title.label.endsWith('(deleted from disk)'), 'should be marked as deleted'); + assert.isTrue(widget.title.label.endsWith('(deleted)'), 'should be marked as deleted'); assert.isTrue(Saveable.isDirty(widget), 'should be dirty after delete'); assert.isFalse(widget.isDisposed, 'model should NOT be disposed after delete'); } finally { @@ -296,7 +313,7 @@ describe('Saveable', function () { try { await fileService.create(fileUri, 'foo'); await waitForDidChangeTitle.promise; - assert.isFalse(widget.title.label.endsWith('(deleted from disk)'), 'should NOT be marked as deleted'); + assert.isFalse(widget.title.label.endsWith('(deleted)'), 'should NOT be marked as deleted'); assert.isTrue(Saveable.isDirty(widget), 'should be dirty after added again'); assert.isFalse(widget.isDisposed, 'model should NOT be disposed after added again'); } finally { diff --git a/packages/core/src/browser/core-preferences.ts b/packages/core/src/browser/core-preferences.ts index 04868cd73eee0..5e481c21944ed 100644 --- a/packages/core/src/browser/core-preferences.ts +++ b/packages/core/src/browser/core-preferences.ts @@ -36,6 +36,12 @@ export const corePreferenceSchema: PreferenceSchema = { 'description': 'Controls whether a top border is drawn on modified (dirty) editor tabs or not.', 'default': false }, + 'workbench.editor.closeOnFileDelete': { + 'type': 'boolean', + // eslint-disable-next-line max-len + 'description': 'Controls whether editors showing a file that was opened during the session should close automatically when getting deleted or renamed by some other process. Disabling this will keep the editor open on such an event. Note that deleting from within the application will always close the editor and that dirty files will never close to preserve your data.', + 'default': true + }, 'application.confirmExit': { type: 'string', enum: [ @@ -100,6 +106,7 @@ export interface CoreConfiguration { 'workbench.list.openMode': 'singleClick' | 'doubleClick'; 'workbench.commandPalette.history': number; 'workbench.editor.highlightModifiedTabs': boolean; + 'workbench.editor.closeOnFileDelete': boolean; 'workbench.colorTheme': string; 'workbench.iconTheme': string | null; 'workbench.silentNotifications': boolean; diff --git a/packages/filesystem/src/browser/filesystem-frontend-contribution.ts b/packages/filesystem/src/browser/filesystem-frontend-contribution.ts index ddaba126bdae1..0e7b1c5e669be 100644 --- a/packages/filesystem/src/browser/filesystem-frontend-contribution.ts +++ b/packages/filesystem/src/browser/filesystem-frontend-contribution.ts @@ -22,7 +22,8 @@ import { Command, CommandContribution, CommandRegistry } from '@theia/core/lib/c import { FrontendApplicationContribution, ApplicationShell, NavigatableWidget, NavigatableWidgetOptions, - Saveable, WidgetManager, StatefulWidget, FrontendApplication, ExpandableTreeNode, waitForClosed + Saveable, WidgetManager, StatefulWidget, FrontendApplication, ExpandableTreeNode, waitForClosed, + CorePreferences } from '@theia/core/lib/browser'; import { MimeService } from '@theia/core/lib/browser/mime-service'; import { TreeWidgetSelection } from '@theia/core/lib/browser/tree/tree-widget-selection'; @@ -63,6 +64,9 @@ export class FileSystemFrontendContribution implements FrontendApplicationContri @inject(FileSystemPreferences) protected readonly preferences: FileSystemPreferences; + @inject(CorePreferences) + protected readonly corePreferences: CorePreferences; + @inject(SelectionService) protected readonly selectionService: SelectionService; @@ -258,7 +262,7 @@ export class FileSystemFrontendContribution implements FrontendApplicationContri return targetUri && widget.createMoveToUri(targetUri); } - protected readonly deletedSuffix = ' (deleted from disk)'; + protected readonly deletedSuffix = ' (deleted)'; protected async updateWidgets(event: FileChangesEvent): Promise { if (!event.gotDeleted() && !event.gotAdded()) { return; @@ -272,7 +276,7 @@ export class FileSystemFrontendContribution implements FrontendApplicationContri this.updateWidget(uri, widget, event, { dirty, toClose }); } for (const [uriString, widgets] of toClose.entries()) { - if (!dirty.has(uriString)) { + if (!dirty.has(uriString) && this.corePreferences['workbench.editor.closeOnFileDelete']) { for (const widget of widgets) { widget.close(); pending.push(waitForClosed(widget)); @@ -291,11 +295,11 @@ export class FileSystemFrontendContribution implements FrontendApplicationContri if (event.contains(uri, FileChangeType.DELETED)) { const uriString = uri.toString(); if (Saveable.isDirty(widget)) { - if (!deleted) { - widget.title.label += this.deletedSuffix; - } dirty.add(uriString); } + if (!deleted) { + widget.title.label += this.deletedSuffix; + } const widgets = toClose.get(uriString) || []; widgets.push(widget); toClose.set(uriString, widgets);