提交 582ca10c 编写于 作者: B Benjamin Pasero

get rid of FILES_SAVING event

上级 31f0a339
......@@ -16,7 +16,6 @@ import {Action} from 'vs/base/common/actions';
import {Scope} from 'vs/workbench/common/memento';
import {IEditorOptions} from 'vs/editor/common/editorCommon';
import {VIEWLET_ID, TEXT_FILE_EDITOR_ID} from 'vs/workbench/parts/files/common/files';
import {SaveErrorHandler} from 'vs/workbench/parts/files/browser/saveErrorHandler';
import {BaseTextEditor} from 'vs/workbench/browser/parts/editor/textEditor';
import {EditorInput, EditorOptions, TextEditorOptions, EditorModel} from 'vs/workbench/common/editor';
import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel';
......@@ -65,9 +64,6 @@ export class TextFileEditor extends BaseTextEditor {
) {
super(TextFileEditor.ID, telemetryService, instantiationService, contextService, storageService, messageService, configurationService, eventService, editorService, themeService);
// Since we are the one providing save-support for models, we hook up the error handler for saving
TextFileEditorModel.setSaveErrorHandler(instantiationService.createInstance(SaveErrorHandler));
// Clear view state for deleted files
this.toUnbind.push(this.eventService.addListener2(EventType.FILE_CHANGES, (e: FileChangesEvent) => this.onFilesChanged(e)));
}
......
......@@ -21,6 +21,7 @@ import {AutoSaveConfiguration, SUPPORTED_ENCODINGS} from 'vs/platform/files/comm
import {FILE_EDITOR_INPUT_ID, VIEWLET_ID} from 'vs/workbench/parts/files/common/files';
import {FileEditorTracker} from 'vs/workbench/parts/files/common/editors/fileEditorTracker';
import {SaveParticipant} from 'vs/workbench/parts/files/common/editors/saveParticipant';
import {SaveErrorHandler} from 'vs/workbench/parts/files/browser/saveErrorHandler';
import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEditorInput';
import {TextFileEditor} from 'vs/workbench/parts/files/browser/editors/textFileEditor';
import {BinaryFileEditor} from 'vs/workbench/parts/files/browser/editors/binaryFileEditor';
......@@ -48,7 +49,7 @@ export class OpenExplorerViewletAction extends ToggleViewletAction {
}
// Register Viewlet
(<ViewletRegistry>Registry.as(ViewletExtensions.Viewlets)).registerViewlet(new ViewletDescriptor(
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor(
'vs/workbench/parts/files/browser/explorerViewlet',
'ExplorerViewlet',
VIEWLET_ID,
......@@ -57,14 +58,14 @@ export class OpenExplorerViewletAction extends ToggleViewletAction {
0
));
(<ViewletRegistry>Registry.as(ViewletExtensions.Viewlets)).setDefaultViewletId(VIEWLET_ID);
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).setDefaultViewletId(VIEWLET_ID);
let openViewletKb: IKeybindings = {
const openViewletKb: IKeybindings = {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_E
};
// Register Action to Open Viewlet
const registry = <IWorkbenchActionRegistry>Registry.as(ActionExtensions.WorkbenchActions);
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(
new SyncActionDescriptor(OpenExplorerViewletAction, OpenExplorerViewletAction.ID, OpenExplorerViewletAction.LABEL, openViewletKb),
'View: Show Explorer',
......@@ -72,7 +73,7 @@ registry.registerWorkbenchAction(
);
// Register file editors
(<IEditorRegistry>Registry.as(EditorExtensions.Editors)).registerEditor(
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
new FileEditorDescriptor(
TextFileEditor.ID, // explicit dependency because we don't want these editors lazy loaded
nls.localize('textFileEditor', "Text File Editor"),
......@@ -92,7 +93,7 @@ registry.registerWorkbenchAction(
]
);
(<IEditorRegistry>Registry.as(EditorExtensions.Editors)).registerEditor(
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
new FileEditorDescriptor(
BinaryFileEditor.ID, // explicit dependency because we don't want these editors lazy loaded
nls.localize('binaryFileEditor', "Binary File Editor"),
......@@ -115,8 +116,8 @@ registry.registerWorkbenchAction(
// Note: because of service injection, the descriptor needs to have the exact count
// of arguments as the FileEditorInput constructor. Otherwise when creating an
// instance through the instantiation service he will inject the services wrong!
let descriptor = new AsyncDescriptor<IFileEditorInput>('vs/workbench/parts/files/common/editors/fileEditorInput', 'FileEditorInput', /* DO NOT REMOVE */ void 0, /* DO NOT REMOVE */ void 0, /* DO NOT REMOVE */ void 0);
(<IEditorRegistry>Registry.as(EditorExtensions.Editors)).registerDefaultFileInput(descriptor);
const descriptor = new AsyncDescriptor<IFileEditorInput>('vs/workbench/parts/files/common/editors/fileEditorInput', 'FileEditorInput', /* DO NOT REMOVE */ void 0, /* DO NOT REMOVE */ void 0, /* DO NOT REMOVE */ void 0);
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerDefaultFileInput(descriptor);
interface ISerializedFileInput {
resource: string;
......@@ -128,8 +129,8 @@ class FileEditorInputFactory implements IEditorInputFactory {
constructor() { }
public serialize(editorInput: EditorInput): string {
let fileEditorInput = <FileEditorInput>editorInput;
let fileInput: ISerializedFileInput = {
const fileEditorInput = <FileEditorInput>editorInput;
const fileInput: ISerializedFileInput = {
resource: fileEditorInput.getResource().toString()
};
......@@ -137,26 +138,31 @@ class FileEditorInputFactory implements IEditorInputFactory {
}
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
let fileInput: ISerializedFileInput = JSON.parse(serializedEditorInput);
const fileInput: ISerializedFileInput = JSON.parse(serializedEditorInput);
return instantiationService.createInstance(FileEditorInput, URI.parse(fileInput.resource), void 0, void 0);
}
}
(<IEditorRegistry>Registry.as(EditorExtensions.Editors)).registerEditorInputFactory(FILE_EDITOR_INPUT_ID, FileEditorInputFactory);
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditorInputFactory(FILE_EDITOR_INPUT_ID, FileEditorInputFactory);
// Register File Editor Tracker
(<IWorkbenchContributionsRegistry>Registry.as(WorkbenchExtensions.Workbench)).registerWorkbenchContribution(
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(
FileEditorTracker
);
// Register Save Participant
(<IWorkbenchContributionsRegistry>Registry.as(WorkbenchExtensions.Workbench)).registerWorkbenchContribution(
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(
SaveParticipant
);
// Register Save Error Handler
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(
SaveErrorHandler
);
// Configuration
let configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigurationExtensions.Configuration);
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'files',
......
......@@ -28,10 +28,14 @@ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiat
import {IMessageService, IMessageWithAction, Severity, CancelAction} from 'vs/platform/message/common/message';
import {IModeService} from 'vs/editor/common/services/modeService';
import {IModelService} from 'vs/editor/common/services/modelService';
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
import {IWorkbenchContribution} from 'vs/workbench/common/contributions';
import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel';
// A handler for save error happening with conflict resolution actions
export class SaveErrorHandler implements ISaveErrorHandler {
export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContribution {
private messages: { [resource: string]: () => void };
private toUnbind: IDisposable[];
constructor(
@IMessageService private messageService: IMessageService,
......@@ -40,13 +44,21 @@ export class SaveErrorHandler implements ISaveErrorHandler {
@IInstantiationService private instantiationService: IInstantiationService
) {
this.messages = Object.create(null);
this.toUnbind = [];
this.registerListeners();
// Hook into model
TextFileEditorModel.setSaveErrorHandler(this);
}
public getId(): string {
return 'vs.files.saveerrorhandler';
}
private registerListeners(): void {
this.textFileService.models.onModelSaved(e => this.onFileSavedOrReverted(e.resource));
this.textFileService.models.onModelReverted(e => this.onFileSavedOrReverted(e.resource));
this.toUnbind.push(this.textFileService.models.onModelSaved(e => this.onFileSavedOrReverted(e.resource)));
this.toUnbind.push(this.textFileService.models.onModelReverted(e => this.onFileSavedOrReverted(e.resource)));
}
private onFileSavedOrReverted(resource: URI): void {
......@@ -127,6 +139,10 @@ export class SaveErrorHandler implements ISaveErrorHandler {
// Show message and keep function to hide in case the file gets saved/reverted
this.messages[model.getResource().toString()] = this.messageService.show(Severity.Error, message);
}
public dispose(): void {
this.toUnbind = dispose(this.toUnbind);
}
}
// Save conflict resolution editor input
......
......@@ -6,18 +6,19 @@
'use strict';
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
import {IWorkbenchContribution} from 'vs/workbench/common/contributions';
import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService';
import {TextFileChangeEvent, EventType} from 'vs/workbench/parts/files/common/files';
import {IWorkbenchContribution} from 'vs/workbench/common/contributions';
import {ISaveParticipant, ITextFileEditorModel} from 'vs/workbench/parts/files/common/files';
import {IFilesConfiguration} from 'vs/platform/files/common/files';
import {IPosition, IModel} from 'vs/editor/common/editorCommon';
import {Selection} from 'vs/editor/common/core/selection';
import {trimTrailingWhitespace} from 'vs/editor/common/commands/trimTrailingWhitespaceCommand';
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
import {IEventService} from 'vs/platform/event/common/event';
import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel';
// The save participant can change a model before its saved to support various scenarios like trimming trailing whitespace
export class SaveParticipant implements IWorkbenchContribution {
export class SaveParticipant implements ISaveParticipant, IWorkbenchContribution {
private trimTrailingWhitespace: boolean;
private toUnbind: IDisposable[];
......@@ -31,10 +32,16 @@ export class SaveParticipant implements IWorkbenchContribution {
this.registerListeners();
this.onConfigurationChange(this.configurationService.getConfiguration<IFilesConfiguration>());
// Hook into model
TextFileEditorModel.setSaveParticipant(this);
}
public getId(): string {
return 'vs.files.saveparticipant';
}
private registerListeners(): void {
this.toUnbind.push(this.eventService.addListener2(EventType.FILE_SAVING, (e: TextFileChangeEvent) => this.onTextFileSaving(e)));
this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(e.config)));
}
......@@ -42,21 +49,12 @@ export class SaveParticipant implements IWorkbenchContribution {
this.trimTrailingWhitespace = configuration && configuration.files && configuration.files.trimTrailingWhitespace;
}
public getId(): string {
return 'vs.files.saveparticipant';
}
private onTextFileSaving(e: TextFileChangeEvent): void {
// Trim Trailing Whitespace if enabled
public participate(model: ITextFileEditorModel, env: { isAutoSaved: boolean }): void {
if (this.trimTrailingWhitespace) {
this.doTrimTrailingWhitespace(e.model, e.isAutoSaved);
this.doTrimTrailingWhitespace(model.textEditorModel, env.isAutoSaved);
}
}
/**
* Trim trailing whitespace on a model and ignore lines on which cursors are sitting if triggered via auto save.
*/
private doTrimTrailingWhitespace(model: IModel, isAutoSaved: boolean): void {
let prevSelection: Selection[] = [new Selection(1, 1, 1, 1)];
const cursors: IPosition[] = [];
......
......@@ -17,7 +17,7 @@ import types = require('vs/base/common/types');
import {IModelContentChangedEvent} from 'vs/editor/common/editorCommon';
import {IMode} from 'vs/editor/common/modes';
import {EventType as WorkbenchEventType, ResourceEvent} from 'vs/workbench/common/events';
import {EventType as FileEventType, TextFileChangeEvent, ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveErrorHandler, StateChange} from 'vs/workbench/parts/files/common/files';
import {ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveErrorHandler, ISaveParticipant, StateChange} from 'vs/workbench/parts/files/common/files';
import {EncodingMode, EditorModel} from 'vs/workbench/common/editor';
import {BaseTextEditorModel} from 'vs/workbench/common/editor/textEditorModel';
import {IFileService, IFileStat, IFileOperationResult, FileOperationResult} from 'vs/platform/files/common/files';
......@@ -36,6 +36,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
public static ID = 'workbench.editors.files.textFileEditorModel';
private static saveErrorHandler: ISaveErrorHandler;
private static saveParticipant: ISaveParticipant;
private resource: URI;
private contentEncoding: string; // encoding as reported from disk
......@@ -115,6 +116,13 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
TextFileEditorModel.saveErrorHandler = handler;
}
/**
* Set a save participant handler to react on models getting saved.
*/
public static setSaveParticipant(handler: ISaveParticipant): void {
TextFileEditorModel.saveParticipant = handler;
}
/**
* When set, will disable any saving (including auto save) until the model is loaded again. This allows to resolve save conflicts
* without running into subsequent save errors when editing the model.
......@@ -378,7 +386,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
return this.doSave(this.versionId, false, overwriteReadonly, overwriteEncoding);
}
private doSave(versionId: number, isAutoSave: boolean, overwriteReadonly?: boolean, overwriteEncoding?: boolean): TPromise<void> {
private doSave(versionId: number, isAutoSaved: boolean, overwriteReadonly?: boolean, overwriteEncoding?: boolean): TPromise<void> {
diag('doSave(' + versionId + ') - enter with versionId ' + versionId, this.resource, new Date());
// Lookup any running pending save for this versionId and return it if found
......@@ -413,18 +421,18 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
this.textEditorModel.pushStackElement();
}
// Emit file saving event: Listeners can still change the model now and since we are so close to saving
// A save participant can still change the model now and since we are so close to saving
// we do not want to trigger another auto save or similar, so we block this
// In addition we update our version right after in case it changed because of a model change
this.blockModelContentChange = true;
try {
const saveEvent = new TextFileChangeEvent(this.resource, this.textEditorModel);
saveEvent.setAutoSaved(isAutoSave);
this.emitEvent(FileEventType.FILE_SAVING, saveEvent);
} finally {
this.blockModelContentChange = false;
if (TextFileEditorModel.saveParticipant) {
this.blockModelContentChange = true;
try {
TextFileEditorModel.saveParticipant.participate(this, { isAutoSaved });
} finally {
this.blockModelContentChange = false;
}
versionId = this.versionId;
}
versionId = this.versionId;
// Clear error flag since we are trying to save again
this.inErrorMode = false;
......@@ -539,15 +547,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
TextFileEditorModel.saveErrorHandler.onSaveError(error, this);
}
private emitEvent(type: string, event: TextFileChangeEvent): void {
try {
this.eventService.emit(type, event);
} catch (e) {
e.friendlyMessage = nls.localize('unexpectedEventError', "An unexpected error was thrown from a file change listener of type: {0}", type);
onUnexpectedError(e);
}
}
private isBusySaving(): boolean {
return !types.isEmptyObject(this.mapPendingSaveToVersionId);
}
......
......@@ -8,7 +8,7 @@ import {TPromise} from 'vs/base/common/winjs.base';
import {Event as BaseEvent, PropertyChangeEvent} from 'vs/base/common/events';
import URI from 'vs/base/common/uri';
import Event from 'vs/base/common/event';
import {IModel, IEditorOptions, IRawText} from 'vs/editor/common/editorCommon';
import {IEditorOptions, IRawText} from 'vs/editor/common/editorCommon';
import {IDisposable} from 'vs/base/common/lifecycle';
import {IEncodingSupport, EncodingMode, EditorInput, IFileEditorInput, ConfirmResult, IWorkbenchEditorConfiguration, IEditorDescriptor} from 'vs/workbench/common/editor';
import {IFileStat, IFilesConfiguration, IBaseStat, IResolveContentOptions} from 'vs/platform/files/common/files';
......@@ -107,16 +107,13 @@ export interface ISaveErrorHandler {
onSaveError(error: any, model: ITextFileEditorModel): void;
}
/**
* List of event types from files.
*/
export const EventType = {
export interface ISaveParticipant {
/**
* Indicates that a file is being saved.
* Participate in a save of a model. Allows to change the model before it is being saved to disk.
*/
FILE_SAVING: 'files:fileSaving',
};
participate(model: ITextFileEditorModel, env: { isAutoSaved: boolean }): void;
}
/**
* States the text text file editor model can be in.
......@@ -183,38 +180,6 @@ export class LocalFileChangeEvent extends PropertyChangeEvent {
}
}
/**
* Text file change events are emitted when files are saved or reverted.
*/
export class TextFileChangeEvent extends BaseEvent {
private _resource: URI;
private _model: IModel;
private _isAutoSaved: boolean;
constructor(resource: URI, model: IModel) {
super();
this._resource = resource;
this._model = model;
}
public get resource(): URI {
return this._resource;
}
public get model(): IModel {
return this._model;
}
public setAutoSaved(autoSaved: boolean): void {
this._isAutoSaved = autoSaved;
}
public get isAutoSaved(): boolean {
return this._isAutoSaved;
}
}
export enum StateChange {
DIRTY,
SAVING,
......
......@@ -36,7 +36,7 @@ export abstract class TextFileService implements ITextFileService {
public _serviceBrand: any;
private listenerToUnbind: IDisposable[];
private toUnbind: IDisposable[];
private _models: TextFileEditorModelManager;
private _onAutoSaveConfigurationChange: Emitter<IAutoSaveConfiguration>;
......@@ -55,7 +55,7 @@ export abstract class TextFileService implements ITextFileService {
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
@IInstantiationService private instantiationService: IInstantiationService
) {
this.listenerToUnbind = [];
this.toUnbind = [];
this._onAutoSaveConfigurationChange = new Emitter<IAutoSaveConfiguration>();
this._models = this.instantiationService.createInstance(TextFileEditorModelManager);
......@@ -88,12 +88,12 @@ export abstract class TextFileService implements ITextFileService {
this.lifecycleService.onShutdown(this.dispose, this);
// Configuration changes
this.listenerToUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(e.config)));
this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(e.config)));
// Application & Editor focus change
window.addEventListener('blur', () => this.onWindowFocusLost());
window.addEventListener('blur', () => this.onEditorFocusChanged(), true);
this.listenerToUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorFocusChanged()));
this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorFocusChanged()));
}
private beforeShutdown(): boolean | TPromise<boolean> {
......@@ -523,7 +523,7 @@ export abstract class TextFileService implements ITextFileService {
}
public dispose(): void {
this.listenerToUnbind = dispose(this.listenerToUnbind);
this.toUnbind = dispose(this.toUnbind);
// Clear all caches
this._models.clear();
......
......@@ -14,7 +14,7 @@ import paths = require('vs/base/common/paths');
import {EncodingMode} from 'vs/workbench/common/editor';
import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel';
import {IEventService} from 'vs/platform/event/common/event';
import {EventType, ITextFileService, ModelState, StateChange} from 'vs/workbench/parts/files/common/files';
import {ITextFileService, ModelState, StateChange} from 'vs/workbench/parts/files/common/files';
import {workbenchInstantiationService, TestTextFileService} from 'vs/test/utils/servicesTestUtils';
import {TextFileEditorModelManager} from 'vs/workbench/parts/files/common/editors/textFileEditorModelManager';
import {FileOperationResult, IFileOperationResult} from 'vs/platform/files/common/files';
......@@ -40,6 +40,7 @@ suite('Files - TextFileEditorModel', () => {
teardown(() => {
(<TextFileEditorModelManager>accessor.textFileService.models).clear();
TextFileEditorModel.setSaveParticipant(null); // reset any set participant
});
test('Save', function (done) {
......@@ -287,11 +288,13 @@ suite('Files - TextFileEditorModel', () => {
}
});
accessor.eventService.addListener2(EventType.FILE_SAVING, (e) => {
assert.ok(model.isDirty());
model.textEditorModel.setValue('bar');
assert.ok(model.isDirty());
eventCounter++;
TextFileEditorModel.setSaveParticipant({
participate: (model) => {
assert.ok(model.isDirty());
model.textEditorModel.setValue('bar');
assert.ok(model.isDirty());
eventCounter++;
}
});
model.load().then(() => {
......
......@@ -57,7 +57,7 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ConfigVariables } from 'vs/workbench/parts/lib/node/configVariables';
import { ITextFileService, EventType } from 'vs/workbench/parts/files/common/files';
import { ITextFileService } from 'vs/workbench/parts/files/common/files';
import { IOutputService, IOutputChannelRegistry, Extensions as OutputExt, IOutputChannel } from 'vs/workbench/parts/output/common/output';
import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TaskConfiguration, TaskDescription, TaskSystemEvents } from 'vs/workbench/parts/tasks/common/taskSystem';
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册