提交 72c2f506 编写于 作者: B Benjamin Pasero

working copy - introduce first save/revert semantics (#84672)

上级 261ef3e7
......@@ -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 {
......
......@@ -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 {
......
......@@ -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';
......
......@@ -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;
......
......@@ -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';
......
......@@ -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<string | null>('activeEditor', null);
export const ActiveEditorIsSaveableContext = new RawContextKey<boolean>('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<void>;
readonly onDispose: Event<void>;
/**
* 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<boolean>;
/**
* 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<boolean> {
save(options?: ISaveOptions): Promise<boolean> {
return Promise.resolve(true);
}
......@@ -553,12 +546,12 @@ export class SideBySideEditorInput extends EditorInput {
return this.master.isDirty();
}
save(): Promise<boolean> {
return this.master.save();
save(options?: ISaveOptions): Promise<boolean> {
return this.master.save(options);
}
revert(): Promise<boolean> {
return this.master.revert();
revert(options?: IRevertOptions): Promise<boolean> {
return this.master.revert(options);
}
getTelemetryDescriptor(): { [key: string]: unknown } {
......
......@@ -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<boolean> {
return this.textFileService.save(this.resource);
save(options?: ISaveOptions): Promise<boolean> {
return this.textFileService.save(this.resource, options);
}
revert(): Promise<boolean> {
revert(options?: IRevertOptions): Promise<boolean> {
if (this.cachedModel) {
this.cachedModel.revert();
}
......
......@@ -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<boolean> {
return this.textFileService.save(this.resource, options);
}
async revert(): Promise<boolean> {
this.setDirty(false);
// Handle content change event buffered
this.contentChangeEventScheduler.schedule();
return true;
}
backup(): Promise<void> {
......
......@@ -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() {
......
......@@ -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';
......
......@@ -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<boolean> {
return this.textFileService.save(this.resource);
save(options?: ITextFileSaveOptions): Promise<boolean> {
return this.textFileService.save(this.resource, options);
}
revert(options?: IRevertOptions): Promise<boolean> {
......
......@@ -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<boolean> {
async save(options?: ISaveOptions): Promise<boolean> {
this.setDirty(false);
return Promise.resolve(true);
return true;
}
revert(options?: IRevertOptions): Promise<boolean> {
async revert(options?: IRevertOptions): Promise<boolean> {
this.setDirty(false);
return Promise.resolve(true);
return true;
}
async resolve(): Promise<IEditorModel | null> {
......
......@@ -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);
}
......
......@@ -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<boolean> {
async save(resource: URI, options?: ITextFileSaveOptions): Promise<boolean> {
// 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<ITextFileOperationResult>;
saveAll(resources: URI[], options?: ISaveOptions): Promise<ITextFileOperationResult>;
saveAll(arg1?: boolean | URI[], options?: ISaveOptions): Promise<ITextFileOperationResult> {
saveAll(includeUntitled?: boolean, options?: ITextFileSaveOptions): Promise<ITextFileOperationResult>;
saveAll(resources: URI[], options?: ITextFileSaveOptions): Promise<ITextFileOperationResult>;
saveAll(arg1?: boolean | URI[], options?: ITextFileSaveOptions): Promise<ITextFileOperationResult> {
// 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<ITextFileOperationResult> {
private async doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ITextFileSaveOptions): Promise<ITextFileOperationResult> {
// 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<ITextFileOperationResult> {
private async doSaveAllFiles(resources?: URI[], options: ITextFileSaveOptions = Object.create(null)): Promise<ITextFileOperationResult> {
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<URI | undefined> {
async saveAs(resource: URI, targetResource?: URI, options?: ITextFileSaveOptions): Promise<URI | undefined> {
// 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<URI> {
private async doSaveAs(resource: URI, target: URI, options?: ITextFileSaveOptions): Promise<URI> {
// 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<boolean> {
private async doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledTextEditorModel, resource: URI, target: URI, options?: ITextFileSaveOptions): Promise<boolean> {
// 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);
......
......@@ -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<void> {
async revert(options?: IRevertOptions): Promise<boolean> {
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<ITextFileEditorModel> {
......@@ -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<void> {
async save(options: ITextFileSaveOptions = Object.create(null)): Promise<boolean> {
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<void> {
private doSave(versionId: number, options: ITextFileSaveOptions): Promise<void> {
if (isUndefinedOrNull(options.reason)) {
options.reason = SaveReason.EXPLICIT;
}
......
......@@ -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<ITextFileService>('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<void>;
save(options?: ITextFileSaveOptions): Promise<boolean>;
load(options?: ILoadOptions): Promise<ITextFileEditorModel>;
revert(soft?: boolean): Promise<void>;
revert(options?: IRevertOptions): Promise<boolean>;
backup(target?: URI): Promise<void>;
......
......@@ -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 => {
......
......@@ -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();
......
......@@ -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<boolean>;
revert(options?: IRevertOptions): Promise<boolean>;
//#endregion
readonly resource: URI;
readonly capabilities: WorkingCopyCapabilities;
......
......@@ -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<boolean> { return true; }
async revert(options?: IRevertOptions): Promise<boolean> { return true; }
dispose(): void {
this._onDispose.fire();
......
......@@ -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';
......
......@@ -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 {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册