diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 3e79332f086b518ebcebeca065abe07888967d61..9d0778cfd0d1b89e55afd41199f6be15e46ebbf0 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -30,7 +30,8 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ISaveParticipant, SaveReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ISaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../common/extHost.protocol'; export interface ICodeActionsOnSaveOptions { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5df456ab6c7e1283d91a193315da3d95c2c8d4bb..60701e91b6f4bc1230afc5719135795e90b7d628 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -45,7 +45,7 @@ import { ITerminalDimensions, IShellLaunchConfig } from 'vs/workbench/contrib/te import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import * as search from 'vs/workbench/services/search/common/search'; -import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; export interface IEnvironment { diff --git a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts index f55a89b3f30ba039bdb69973867ff2f1aa7dc1c1..3b1b80c1de4f250f64afef34acbcb17a3d2b4efa 100644 --- a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts @@ -11,7 +11,7 @@ import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, IResou import { TextEdit } from 'vs/workbench/api/common/extHostTypes'; import { Range, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/common/extHostTypeConverters'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; -import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import * as vscode from 'vscode'; import { LinkedList } from 'vs/base/common/linkedList'; import { ILogService } from 'vs/platform/log/common/log'; diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index a0671a6e51ef627fb1f11b464d739c86f750d564..3361a6e5d666b9e817c5b1b43290de4bb7782cb8 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -13,7 +13,7 @@ import { EndOfLineSequence, TrackedRangeStickiness } from 'vs/editor/common/mode import * as vscode from 'vscode'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ProgressLocation as MainProgressLocation } from 'vs/platform/progress/common/progress'; -import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IPosition } from 'vs/editor/common/core/position'; import * as editorRange from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; @@ -31,7 +31,6 @@ import { LogLevel as _MainLogLevel } from 'vs/platform/log/common/log'; import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays'; import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; - export interface PositionLike { line: number; character: number; diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index cb3930fa43ad4f46ef526a981662d6b8f90c3da7..b9e4dcc4696dab72562e841988a8b0b10a8179cc 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -16,7 +16,8 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ITextFileService, SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { isDiffEditor, isCodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index e6a70ab470275028a0e273403a00efc16f7fa72e..aff98eee1b05c525f4555a698cd438162e1954d1 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -20,6 +20,7 @@ import { ActionRunner, IAction } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { IPathData } from 'vs/platform/windows/common/windows'; import { coalesce, firstOrDefault } from 'vs/base/common/arrays'; +import { ISaveOptions, IRevertOptions } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export const ActiveEditorContext = new RawContextKey('activeEditor', null); export const ActiveEditorIsSaveableContext = new RawContextKey('activeEditorIsSaveable', false); @@ -261,25 +262,12 @@ export const enum Verbosity { LONG } -export interface IRevertOptions { - - /** - * Forces to load the contents of the editor again even if the editor is not dirty. - */ - force?: boolean; - - /** - * A soft revert will clear dirty state of an editor but will not attempt to load it. - */ - soft?: boolean; -} - export interface IEditorInput extends IDisposable { /** * Triggered when this input is disposed. */ - onDispose: Event; + readonly onDispose: Event; /** * Returns the associated resource of this input. @@ -316,6 +304,11 @@ export interface IEditorInput extends IDisposable { */ isDirty(): boolean; + /** + * Saves the editor if it is dirty. + */ + save(options?: ISaveOptions): Promise; + /** * Reverts this input. */ @@ -418,7 +411,7 @@ export abstract class EditorInput extends Disposable implements IEditorInput { /** * Saves the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation. */ - save(): Promise { + save(options?: ISaveOptions): Promise { return Promise.resolve(true); } @@ -553,12 +546,12 @@ export class SideBySideEditorInput extends EditorInput { return this.master.isDirty(); } - save(): Promise { - return this.master.save(); + save(options?: ISaveOptions): Promise { + return this.master.save(options); } - revert(): Promise { - return this.master.revert(); + revert(options?: IRevertOptions): Promise { + return this.master.revert(options); } getTelemetryDescriptor(): { [key: string]: unknown } { diff --git a/src/vs/workbench/common/editor/untitledTextEditorInput.ts b/src/vs/workbench/common/editor/untitledTextEditorInput.ts index 3e2998c3e2948f5ab21c7f847094156881617510..14c409a73eb0ceb56b8086785c56ecf876ae1cde 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorInput.ts @@ -15,6 +15,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ILabelService } from 'vs/platform/label/common/label'; import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; +import { ISaveOptions, IRevertOptions } from 'vs/workbench/services/workingCopy/common/workingCopyService'; /** * An editor input to be used for untitled text buffers. @@ -146,11 +147,11 @@ export class UntitledTextEditorInput extends EditorInput implements IEncodingSup return false; } - save(): Promise { - return this.textFileService.save(this.resource); + save(options?: ISaveOptions): Promise { + return this.textFileService.save(this.resource, options); } - revert(): Promise { + revert(options?: IRevertOptions): Promise { if (this.cachedModel) { this.cachedModel.revert(); } diff --git a/src/vs/workbench/common/editor/untitledTextEditorModel.ts b/src/vs/workbench/common/editor/untitledTextEditorModel.ts index b0d22deb9b3ad39a3c3d67b3e88955edd91514fc..280ad2c1068bca85d74d5a5e6f54e316cc7e4dd3 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorModel.ts @@ -16,7 +16,8 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { ITextBufferFactory } from 'vs/editor/common/model'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; -import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyService, IWorkingCopy, ISaveOptions } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; export class UntitledTextEditorModel extends BaseTextEditorModel implements IEncodingSupport, IWorkingCopy { @@ -48,7 +49,8 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IEnc @IModelService modelService: IModelService, @IBackupFileService private readonly backupFileService: IBackupFileService, @ITextResourceConfigurationService private readonly configurationService: ITextResourceConfigurationService, - @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @ITextFileService private readonly textFileService: ITextFileService ) { super(modelService, modeService); @@ -115,11 +117,17 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IEnc this._onDidChangeDirty.fire(); } - revert(): void { + save(options?: ISaveOptions): Promise { + return this.textFileService.save(this.resource, options); + } + + async revert(): Promise { this.setDirty(false); // Handle content change event buffered this.contentChangeEventScheduler.schedule(); + + return true; } backup(): Promise { diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorModel.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorModel.ts index 4afe96cdc35dd149e8583bd63eddbc758f8b0640..255c8f09f1653b8439cc7320150783a99a85bfa7 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorModel.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorModel.ts @@ -6,7 +6,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { IWorkingCopy, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy, IWorkingCopyService, WorkingCopyCapabilities, ISaveOptions, IRevertOptions } from 'vs/workbench/services/workingCopy/common/workingCopyService'; type Edit = string; @@ -56,9 +56,19 @@ export class CustomEditorModel extends Disposable implements IWorkingCopy { this._onDidChangeDirty.fire(); } - public save() { + public async save(options?: ISaveOptions) { this._savePoint = this._edits.length; this.updateDirty(); + + return true; + } + + public async revert(options?: IRevertOptions) { + while (this._currentEditIndex > 0) { + this.undo(); + } + + return true; } public undo() { diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index e6192c5c094f9a3c6f49fbafe9bb202d58b7b3d5..7fb4fcbab7ce54eb186f31131d64439879cf462c 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -14,7 +14,8 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { ExplorerFocusCondition, TextFileContentProvider, VIEWLET_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ISaveOptions } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IListService } from 'vs/platform/list/browser/listService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 104bebe8fb3b345fc1c063f7840d7f5189f2c9eb..b6aa3d45ac2796b6e8fe8bd20f1f52f80141b85f 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -7,11 +7,12 @@ import { localize } from 'vs/nls'; import { createMemoizer } from 'vs/base/common/decorators'; import { dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { EncodingMode, EditorInput, IFileEditorInput, ITextEditorModel, Verbosity, IRevertOptions } from 'vs/workbench/common/editor'; +import { EncodingMode, EditorInput, IFileEditorInput, ITextEditorModel, Verbosity } from 'vs/workbench/common/editor'; +import { IRevertOptions } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; -import { ITextFileService, ModelState, TextFileModelChangeEvent, LoadReason, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ModelState, TextFileModelChangeEvent, LoadReason, TextFileOperationError, TextFileOperationResult, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IReference } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -243,8 +244,8 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { return model.isDirty(); } - save(): Promise { - return this.textFileService.save(this.resource); + save(options?: ITextFileSaveOptions): Promise { + return this.textFileService.save(this.resource, options); } revert(options?: IRevertOptions): Promise { diff --git a/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts b/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts index 1a716bb01805c345e989d9406f8925358ad52dc3..ab34523d3917d870eaf71ef736789b70c6f94c81 100644 --- a/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts +++ b/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IEditorInputFactory, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, EditorModel, IRevertOptions, EditorOptions } from 'vs/workbench/common/editor'; +import { IEditorInputFactory, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, EditorModel, EditorOptions } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { Dimension, addDisposableListener, EventType } from 'vs/base/browser/dom'; @@ -24,7 +24,7 @@ import { isEqual } from 'vs/base/common/resources'; import { generateUuid } from 'vs/base/common/uuid'; import { CancellationToken } from 'vs/base/common/cancellation'; import { editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IWorkingCopy, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy, IWorkingCopyService, IRevertOptions, ISaveOptions } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { env } from 'vs/base/common/process'; const CUSTOM_SCHEME = 'testCustomEditor'; @@ -160,16 +160,16 @@ class TestCustomEditorInput extends EditorInput implements IWorkingCopy { return this.dirty; } - save(): Promise { + async save(options?: ISaveOptions): Promise { this.setDirty(false); - return Promise.resolve(true); + return true; } - revert(options?: IRevertOptions): Promise { + async revert(options?: IRevertOptions): Promise { this.setDirty(false); - return Promise.resolve(true); + return true; } async resolve(): Promise { diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index 4d1a4cface320d0da3b93008f60f33984de1151d..3afa7519ab93e5905e4e132f3c64b0b391325774 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -32,7 +32,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { createCancelablePromise, CancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ICommandHandler } from 'vs/platform/commands/common/commands'; -import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { toResource } from 'vs/workbench/common/editor'; import { normalizeDriveLetter } from 'vs/base/common/labels'; @@ -56,7 +56,7 @@ export namespace SaveLocalFileCommand { const textFileService = accessor.get(ITextFileService); const editorService = accessor.get(IEditorService); let resource: URI | undefined = toResource(editorService.activeEditor); - const options: ISaveOptions = { force: true, availableFileSystems: [Schemas.file] }; + const options: ITextFileSaveOptions = { force: true, availableFileSystems: [Schemas.file] }; if (resource) { return textFileService.saveAs(resource, undefined, options); } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index fe4ddff014d88468b94701d8be9448e02903b029..4d75a95a8e40b5f669ffb221d931d061a59c2004 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -8,8 +8,8 @@ import { URI } from 'vs/base/common/uri'; import { Emitter, AsyncEmitter } from 'vs/base/common/event'; import * as platform from 'vs/base/common/platform'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent } from 'vs/workbench/services/textfile/common/textfiles'; -import { IRevertOptions } from 'vs/workbench/common/editor'; +import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason, IRevertOptions } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IFileService, FileOperationError, FileOperationResult, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions, FileOperation } from 'vs/platform/files/common/files'; @@ -477,7 +477,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#region save/revert - async save(resource: URI, options?: ISaveOptions): Promise { + async save(resource: URI, options?: ITextFileSaveOptions): Promise { // Run a forced save if we detect the file is not dirty so that save participants can still run if (options?.force && this.fileService.canHandleResource(resource) && !this.isDirty(resource)) { @@ -496,9 +496,9 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return result.results.length === 1 && !!result.results[0].success; } - saveAll(includeUntitled?: boolean, options?: ISaveOptions): Promise; - saveAll(resources: URI[], options?: ISaveOptions): Promise; - saveAll(arg1?: boolean | URI[], options?: ISaveOptions): Promise { + saveAll(includeUntitled?: boolean, options?: ITextFileSaveOptions): Promise; + saveAll(resources: URI[], options?: ITextFileSaveOptions): Promise; + saveAll(arg1?: boolean | URI[], options?: ITextFileSaveOptions): Promise { // get all dirty let toSave: URI[] = []; @@ -522,7 +522,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.doSaveAll(filesToSave, untitledToSave, options); } - private async doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ISaveOptions): Promise { + private async doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ITextFileSaveOptions): Promise { // Handle files first that can just be saved const result = await this.doSaveAllFiles(fileResources, options); @@ -627,7 +627,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return options; } - private async doSaveAllFiles(resources?: URI[], options: ISaveOptions = Object.create(null)): Promise { + private async doSaveAllFiles(resources?: URI[], options: ITextFileSaveOptions = Object.create(null)): Promise { const dirtyFileModels = this.getDirtyFileModels(Array.isArray(resources) ? resources : undefined /* Save All */) .filter(model => { if ((model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)) { @@ -675,7 +675,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.getFileModels(resources).filter(model => model.isDirty()); } - async saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): Promise { + async saveAs(resource: URI, targetResource?: URI, options?: ITextFileSaveOptions): Promise { // Get to target resource if (!targetResource) { @@ -702,7 +702,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.doSaveAs(resource, targetResource, options); } - private async doSaveAs(resource: URI, target: URI, options?: ISaveOptions): Promise { + private async doSaveAs(resource: URI, target: URI, options?: ITextFileSaveOptions): Promise { // Retrieve text model from provided resource if any let model: ITextFileEditorModel | UntitledTextEditorModel | undefined; @@ -736,7 +736,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return target; } - private async doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledTextEditorModel, resource: URI, target: URI, options?: ISaveOptions): Promise { + private async doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledTextEditorModel, resource: URI, target: URI, options?: ITextFileSaveOptions): Promise { // Prefer an existing model if it is already loaded for the given target resource let targetExists: boolean = false; @@ -866,7 +866,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex await Promise.all(fileModels.map(async model => { try { - await model.revert(options?.soft); + await model.revert(options); if (!model.isDirty()) { const result = mapResourceToResult.get(model.resource); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 7dba20135d72312ea15f85e8de65be8726aca48c..f18c70d2aee23c9b17011b007c47345df1da592d 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -11,7 +11,7 @@ import { URI } from 'vs/base/common/uri'; import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ITextFileService, ModelState, ITextFileEditorModel, ISaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, ITextFileStreamContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ModelState, ITextFileEditorModel, ISaveErrorHandler, ISaveParticipant, StateChange, ITextFileStreamContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { EncodingMode } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -29,7 +29,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { isEqual, isEqualOrParent, extname, basename, joinPath } from 'vs/base/common/resources'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Schemas } from 'vs/base/common/network'; -import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopyService, WorkingCopyCapabilities, SaveReason, IRevertOptions } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IFilesConfigurationService, IAutoSaveConfiguration } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export interface IBackupMetaData { @@ -252,9 +252,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.backupFileService.hasBackupSync(this.resource, this.versionId); } - async revert(soft?: boolean): Promise { + async revert(options?: IRevertOptions): Promise { if (!this.isResolved()) { - return; + return false; } // Cancel any running auto-save @@ -265,7 +265,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil const undo = this.setDirty(false); // Force read from disk unless reverting soft - if (!soft) { + const softUndo = options?.soft; + if (!softUndo) { try { await this.load({ forceReadFromDisk: true }); } catch (error) { @@ -284,6 +285,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (wasDirty) { this._onDidChangeDirty.fire(); } + + return true; } async load(options?: ILoadOptions): Promise { @@ -612,9 +615,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.autoSaveDisposable.value = toDisposable(() => clearTimeout(handle)); } - async save(options: ISaveOptions = Object.create(null)): Promise { + async save(options: ITextFileSaveOptions = Object.create(null)): Promise { if (!this.isResolved()) { - return; + return false; } this.logService.trace('save() - enter', this.resource); @@ -622,10 +625,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Cancel any currently running auto saves to make this the one that succeeds this.autoSaveDisposable.clear(); - return this.doSave(this.versionId, options); + await this.doSave(this.versionId, options); + + return true; } - private doSave(versionId: number, options: ISaveOptions): Promise { + private doSave(versionId: number, options: ITextFileSaveOptions): Promise { if (isUndefinedOrNull(options.reason)) { options.reason = SaveReason.EXPLICIT; } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index d07dc93f41ee7e46b07e90b31a02075457eec8d7..78c9bc6dcb887971f108eb2152bfa2d4134483ba 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { Event, IWaitUntil } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IEncodingSupport, IRevertOptions, IModeSupport } from 'vs/workbench/common/editor'; +import { IEncodingSupport, IModeSupport } from 'vs/workbench/common/editor'; import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult, FileOperation } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; @@ -14,7 +14,7 @@ import { ITextBufferFactory, ITextModel, ITextSnapshot } from 'vs/editor/common/ import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { isNative } from 'vs/base/common/platform'; -import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy, ISaveOptions, SaveReason, IRevertOptions } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export const ITextFileService = createDecorator('textFileService'); @@ -323,13 +323,6 @@ export interface IResult { success?: boolean; } -export const enum SaveReason { - EXPLICIT = 1, - AUTO = 2, - FOCUS_CHANGE = 3, - WINDOW_CHANGE = 4 -} - export const enum LoadReason { EDITOR = 1, REFERENCE = 2, @@ -421,12 +414,9 @@ export interface ITextFileEditorModelManager { disposeModel(model: ITextFileEditorModel): void; } -export interface ISaveOptions { - force?: boolean; - reason?: SaveReason; +export interface ITextFileSaveOptions extends ISaveOptions { overwriteReadonly?: boolean; overwriteEncoding?: boolean; - skipSaveParticipants?: boolean; writeElevated?: boolean; availableFileSystems?: readonly string[]; } @@ -458,11 +448,11 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport updatePreferredEncoding(encoding: string | undefined): void; - save(options?: ISaveOptions): Promise; + save(options?: ITextFileSaveOptions): Promise; load(options?: ILoadOptions): Promise; - revert(soft?: boolean): Promise; + revert(options?: IRevertOptions): Promise; backup(target?: URI): Promise; diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index 8ccf5a25c5db6a78b727d57f627b9131a71e0771..3d8c90ba250867e43d69cbdbe9b6b9d483475b9b 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -263,7 +263,7 @@ suite('Files - TextFileEditorModel', () => { assert.equal(accessor.workingCopyService.dirtyCount, 1); assert.equal(accessor.workingCopyService.isDirty(model.resource), true); - await model.revert(true /* soft revert */); + await model.revert({ soft: true }); assert.ok(!model.isDirty()); assert.equal(model.textEditorModel!.getValue(), 'foo'); assert.equal(eventCounter, 1); @@ -300,7 +300,7 @@ suite('Files - TextFileEditorModel', () => { model.textEditorModel!.setValue('foo'); assert.ok(model.isDirty()); - await model.revert(true /* soft revert */); + await model.revert({ soft: true }); assert.ok(!model.isDirty()); model.onDidStateChange(e => { diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index 0494cbf139bb12a8bcc5d3b6f32ea5288c0ce973..f4db513644fc3e0b1eb0fed4856476711e42ddea 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -286,7 +286,7 @@ suite('Files - TextFileEditorModelManager', () => { model.textEditorModel!.setValue('make dirty'); manager.disposeModel((model as TextFileEditorModel)); assert.ok(!model.isDisposed()); - model.revert(true); + model.revert({ soft: true }); manager.disposeModel((model as TextFileEditorModel)); assert.ok(model.isDisposed()); manager.dispose(); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts index fe2f33adfa3189d6aabdb57fde0af9cfb1f1f140..d01206271bb3ae18db65348f437ae34b333ede0d 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -19,6 +19,63 @@ export const enum WorkingCopyCapabilities { AutoSave = 1 << 1 } +export const enum SaveReason { + + /** + * Explicit user gesture. + */ + EXPLICIT = 1, + + /** + * Auto save after a timeout. + */ + AUTO = 2, + + /** + * Auto save after editor focus change. + */ + FOCUS_CHANGE = 3, + + /** + * Auto save after window change. + */ + WINDOW_CHANGE = 4 +} + +export interface ISaveOptions { + + /** + * An indicator how the save operation was triggered. + */ + reason?: SaveReason; + + /** + * Forces to load the contents of the working copy + * again even if the working copy is not dirty. + */ + force?: boolean; + + /** + * Instructs the save operation to skip any save participants. + */ + skipSaveParticipants?: boolean; +} + +export interface IRevertOptions { + + /** + * Forces to load the contents of the working copy + * again even if the working copy is not dirty. + */ + force?: boolean; + + /** + * A soft revert will clear dirty state of a working copy + * but will not attempt to load it from its persisted state. + */ + soft?: boolean; +} + export interface IWorkingCopy { //#region Dirty Tracking @@ -29,6 +86,16 @@ export interface IWorkingCopy { //#endregion + + //#region Save/Revert + + save(options?: ISaveOptions): Promise; + + revert(options?: IRevertOptions): Promise; + + //#endregion + + readonly resource: URI; readonly capabilities: WorkingCopyCapabilities; diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts index 7110cd285820ea5d729ed7107077b54a74ea89a5..f9fe16495551f9c9780367273b0327c6d82b194c 100644 --- a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IWorkingCopy, ISaveOptions, IRevertOptions } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { URI } from 'vs/base/common/uri'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -41,6 +41,9 @@ suite('WorkingCopyService', () => { return this.dirty; } + async save(options?: ISaveOptions): Promise { return true; } + async revert(options?: IRevertOptions): Promise { return true; } + dispose(): void { this._onDispose.fire(); diff --git a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts index d85eae55ebf186303f65b3be916f62a97e9b1c9d..0e4d2acb7bd23a2e04e52d056772ae2869140f0d 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts @@ -10,7 +10,7 @@ import { TextDocumentSaveReason, TextEdit, Position, EndOfLine } from 'vs/workbe import { MainThreadTextEditorsShape, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; -import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import * as vscode from 'vscode'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { NullLogService } from 'vs/platform/log/common/log'; diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts index 502d3fae8e9083b4b28ab17e7e7b1e28ad6e20a7..c6122bedf48cb280c04bbd771e89621e766db631 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts @@ -13,7 +13,8 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService, SaveReason, IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { SaveReason } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; class ServiceAccessor {