提交 c8680b8f 编写于 作者: B Benjamin Pasero

Introduce event on input to indicate dirty state change (fixes #6953)

上级 afbe654b
......@@ -21,7 +21,7 @@ import errors = require('vs/base/common/errors');
import {Scope as MementoScope} from 'vs/workbench/common/memento';
import {Scope} from 'vs/workbench/browser/actionBarRegistry';
import {Part} from 'vs/workbench/browser/part';
import {EventType as WorkbenchEventType, EditorInputEvent, EditorEvent} from 'vs/workbench/common/events';
import {EventType as WorkbenchEventType, EditorEvent} from 'vs/workbench/common/events';
import {IEditorRegistry, Extensions as EditorExtensions, BaseEditor, EditorDescriptor} from 'vs/workbench/browser/parts/editor/baseEditor';
import {EditorInput, EditorOptions, TextEditorOptions, ConfirmResult} from 'vs/workbench/common/editor';
import {BaseTextEditor} from 'vs/workbench/browser/parts/editor/textEditor';
......@@ -122,17 +122,19 @@ export class EditorPart extends Part implements IEditorPart {
}
private registerListeners(): void {
this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.EDITOR_INPUT_DIRTY_STATE_CHANGED, (event: EditorInputEvent) => this.onEditorInputDirtyStateChanged(event)));
this.toUnbind.push(this.stacks.onEditorDirty(identifier => this.onEditorDirty(identifier)));
this.toUnbind.push(this.stacks.onEditorDisposed(identifier => this.onEditorDisposed(identifier)));
}
private onEditorInputDirtyStateChanged(event: EditorInputEvent): void {
private onEditorDirty(identifier: IEditorIdentifier): void {
const position = this.stacks.positionOfGroup(identifier.group);
const group = identifier.group;
// we pin every editor that becomes dirty across all groups
this.stacks.groups.forEach(group => group.contains(event.editorInput) && this.pinEditor(this.stacks.positionOfGroup(group), event.editorInput));
// we pin every editor that becomes dirty
this.pinEditor(position, identifier.editor, false /* we update the UI right after */);
// Update UI
this.sideBySideControl.updateTitleArea(event.editorInput);
this.sideBySideControl.updateTitleArea({ position, preview: group.previewEditor, editorCount: group.count });
}
private onEditorDisposed(identifier: IEditorIdentifier): void {
......@@ -1026,7 +1028,7 @@ export class EditorPart extends Part implements IEditorPart {
}
}
public pinEditor(position: Position, input: EditorInput): void {
public pinEditor(position: Position, input: EditorInput, updateTitleArea = true): void {
const group = this.stacks.groupAt(position);
if (group) {
if (group.isPinned(input)) {
......@@ -1037,7 +1039,9 @@ export class EditorPart extends Part implements IEditorPart {
group.pin(input);
// Update UI
this.sideBySideControl.updateTitleArea({ position, preview: group.previewEditor, editorCount: group.count });
if (updateTitleArea) {
this.sideBySideControl.updateTitleArea({ position, preview: group.previewEditor, editorCount: group.count });
}
}
}
......
......@@ -20,7 +20,7 @@ import {Dimension, Builder, $} from 'vs/base/browser/builder';
import {Sash, ISashEvent, IVerticalSashLayoutProvider} from 'vs/base/browser/ui/sash/sash';
import {ProgressBar} from 'vs/base/browser/ui/progressbar/progressbar';
import {BaseEditor, IEditorInputActionContext} from 'vs/workbench/browser/parts/editor/baseEditor';
import {EditorInput, isInputRelated} from 'vs/workbench/common/editor';
import {EditorInput} from 'vs/workbench/common/editor';
import {EventType as BaseEventType} from 'vs/base/common/events';
import DOM = require('vs/base/browser/dom');
import {IActionItem, ActionsOrientation, Separator} from 'vs/base/browser/ui/actionbar/actionbar';
......@@ -86,7 +86,6 @@ export interface ISideBySideEditorControl {
recreateTitleArea(states: ITitleAreaState[]): void;
updateTitleArea(state: ITitleAreaState): void;
updateTitleArea(input: EditorInput): void;
clearTitleArea(position: Position): void;
setTitleLabel(position: Position, input: EditorInput, isPinned: boolean, isActive: boolean): void;
......@@ -1178,49 +1177,32 @@ export class SideBySideEditorControl implements ISideBySideEditorControl, IVerti
return actionItem;
}
public updateTitleArea(state: ITitleAreaState): void;
public updateTitleArea(input: EditorInput): void;
public updateTitleArea(arg1: any): void {
public updateTitleArea(state: ITitleAreaState): void {
let editor = this.visibleEditors[state.position];
let input = editor ? editor.input : null;
// Update all title areas that relate to given input if provided
if (arg1 instanceof EditorInput) {
const input: EditorInput = arg1;
if (input && editor) {
// Update the input title actions in each position according to the new status
POSITIONS.forEach((position) => {
if (this.visibleEditors[position] && isInputRelated(this.visibleEditors[position].input, input)) {
this.closeEditorActions[position].class = input.isDirty() ? 'close-editor-dirty-action' : 'close-editor-action';
}
});
}
// Otherwise update specific title position
else {
const state: ITitleAreaState = arg1;
let editor = this.visibleEditors[state.position];
let input = editor ? editor.input : null;
if (input && editor) {
// Dirty
this.closeEditorActions[state.position].class = input.isDirty() ? 'close-editor-dirty-action' : 'close-editor-action';
// Pinned
const isPinned = !input.matches(state.preview);
if (isPinned) {
this.titleContainer[state.position].addClass('pinned');
} else {
this.titleContainer[state.position].removeClass('pinned');
}
// Pinned
const isPinned = !input.matches(state.preview);
if (isPinned) {
this.titleContainer[state.position].addClass('pinned');
} else {
this.titleContainer[state.position].removeClass('pinned');
}
// Overflow
const isOverflowing = state.editorCount > 1;
const showEditorAction = this.showEditorsOfGroup[state.position];
if (!isOverflowing) {
showEditorAction.class = 'show-group-editors-overflowing-action-hidden';
showEditorAction.enabled = false;
} else {
showEditorAction.class = 'show-group-editors-action';
showEditorAction.enabled = true;
}
// Overflow
const isOverflowing = state.editorCount > 1;
const showEditorAction = this.showEditorsOfGroup[state.position];
if (!isOverflowing) {
showEditorAction.class = 'show-group-editors-overflowing-action-hidden';
showEditorAction.enabled = false;
} else {
showEditorAction.class = 'show-group-editors-action';
showEditorAction.enabled = true;
}
}
}
......
......@@ -6,6 +6,7 @@
import {TPromise} from 'vs/base/common/winjs.base';
import {EventEmitter} from 'vs/base/common/eventEmitter';
import Event, {Emitter} from 'vs/base/common/event';
import types = require('vs/base/common/types');
import URI from 'vs/base/common/uri';
import {IEditor, IEditorViewState, IRange} from 'vs/editor/common/editorCommon';
......@@ -24,14 +25,23 @@ export enum ConfirmResult {
* Each editor input is mapped to an editor that is capable of opening it through the Platform facade.
*/
export abstract class EditorInput extends EventEmitter implements IEditorInput {
protected _onDidChangeDirty: Emitter<void>;
private disposed: boolean;
constructor() {
super();
this._onDidChangeDirty = new Emitter<void>();
this.disposed = false;
}
/**
* Fired when the dirty state of this input changes.
*/
public get onDidChangeDirty(): Event<void> {
return this._onDidChangeDirty.event;
}
/**
* Returns the name of this input that can be shown to the user. Examples include showing the name of the input
* above the editor area when the input is shown.
......@@ -565,23 +575,4 @@ export function asFileEditorInput(obj: any, supportDiff?: boolean): IFileEditorI
let i = <IFileEditorInput>obj;
return i instanceof EditorInput && types.areFunctions(i.setResource, i.setMime, i.setEncoding, i.getEncoding, i.getResource, i.getMime) ? i : null;
}
export function isInputRelated(sourceInput: EditorInput, targetInput: EditorInput): boolean {
if (!sourceInput || !targetInput) {
return false;
}
if (sourceInput.matches(targetInput)) {
return true;
}
if (sourceInput instanceof BaseDiffEditorInput) {
let modifiedInput = (<BaseDiffEditorInput>sourceInput).getModifiedInput();
if (modifiedInput && modifiedInput.matches(targetInput)) {
return true;
}
}
return false;
}
\ No newline at end of file
......@@ -57,6 +57,9 @@ export class DiffEditorInput extends BaseDiffEditorInput {
this.dispose();
}
}));
// When the modified model gets dirty, re-emit this to the outside
this._toUnbind.push(this.modifiedInput.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
}
public get toUnbind() {
......
......@@ -134,6 +134,7 @@ export class EditorGroup implements IEditorGroup {
private _onEditorOpened: Emitter<EditorInput>;
private _onEditorClosed: Emitter<IGroupEvent>;
private _onEditorDisposed: Emitter<EditorInput>;
private _onEditorDirty: Emitter<EditorInput>;
private _onEditorMoved: Emitter<EditorInput>;
private _onEditorPinned: Emitter<EditorInput>;
private _onEditorUnpinned: Emitter<EditorInput>;
......@@ -154,12 +155,13 @@ export class EditorGroup implements IEditorGroup {
this._onEditorOpened = new Emitter<EditorInput>();
this._onEditorClosed = new Emitter<IGroupEvent>();
this._onEditorDisposed = new Emitter<EditorInput>();
this._onEditorDirty = new Emitter<EditorInput>();
this._onEditorMoved = new Emitter<EditorInput>();
this._onEditorPinned = new Emitter<EditorInput>();
this._onEditorUnpinned = new Emitter<EditorInput>();
this._onEditorChanged = new Emitter<EditorInput>();
this.toDispose.push(this._onEditorActivated, this._onEditorOpened, this._onEditorClosed, this._onEditorDisposed, this._onEditorMoved, this._onEditorPinned, this._onEditorUnpinned, this._onEditorChanged);
this.toDispose.push(this._onEditorActivated, this._onEditorOpened, this._onEditorClosed, this._onEditorDisposed, this._onEditorDirty, this._onEditorMoved, this._onEditorPinned, this._onEditorUnpinned, this._onEditorChanged);
if (typeof arg1 === 'object') {
this.deserialize(arg1);
......@@ -200,6 +202,10 @@ export class EditorGroup implements IEditorGroup {
return this._onEditorDisposed.event;
}
public get onEditorDirty(): Event<EditorInput> {
return this._onEditorDirty.event;
}
public get onEditorMoved(): Event<EditorInput> {
return this._onEditorMoved.event;
}
......@@ -325,21 +331,26 @@ export class EditorGroup implements IEditorGroup {
}
private hookEditorListeners(editor: EditorInput): void {
const unbind: IDisposable[] = [];
// Re-emit disposal of editor input as our own event
const l1 = editor.addOneTimeDisposableListener('dispose', () => {
unbind.push(editor.addOneTimeDisposableListener('dispose', () => {
if (this.indexOf(editor) >= 0) {
this._onEditorDisposed.fire(editor);
}
});
}));
// Re-Emit dirty state changes
unbind.push(editor.onDidChangeDirty(() => {
this._onEditorDirty.fire(editor);
}));
// Clean up dispose listeners once the editor gets closed
const l2 = this.onEditorClosed(event => {
unbind.push(this.onEditorClosed(event => {
if (event.editor.matches(editor)) {
l1.dispose();
l2.dispose();
dispose(unbind);
}
});
}));
}
public closeEditor(editor: EditorInput, openNext = true): void {
......@@ -666,6 +677,7 @@ export class EditorStacksModel implements IEditorStacksModel {
private _onGroupRenamed: Emitter<EditorGroup>;
private _onModelChanged: Emitter<EditorGroup>;
private _onEditorDisposed: Emitter<IEditorIdentifier>;
private _onEditorDirty: Emitter<IEditorIdentifier>;
constructor(
@IStorageService private storageService: IStorageService,
......@@ -687,8 +699,9 @@ export class EditorStacksModel implements IEditorStacksModel {
this._onGroupRenamed = new Emitter<EditorGroup>();
this._onModelChanged = new Emitter<EditorGroup>();
this._onEditorDisposed = new Emitter<IEditorIdentifier>();
this._onEditorDirty = new Emitter<IEditorIdentifier>();
this.toDispose.push(this._onGroupOpened, this._onGroupClosed, this._onGroupActivated, this._onGroupMoved, this._onGroupRenamed, this._onModelChanged, this._onEditorDisposed);
this.toDispose.push(this._onGroupOpened, this._onGroupClosed, this._onGroupActivated, this._onGroupMoved, this._onGroupRenamed, this._onModelChanged, this._onEditorDisposed, this._onEditorDirty);
this.registerListeners();
}
......@@ -725,6 +738,10 @@ export class EditorStacksModel implements IEditorStacksModel {
return this._onEditorDisposed.event;
}
public get onEditorDirty(): Event<IEditorIdentifier> {
return this._onEditorDirty.event;
}
public get groups(): EditorGroup[] {
this.ensureLoaded();
......@@ -1081,14 +1098,16 @@ export class EditorStacksModel implements IEditorStacksModel {
this.groupToIdentifier[group.id] = group;
// Funnel editor changes in the group through our event aggregator
const l1 = group.onEditorChanged(e => this._onModelChanged.fire(group));
const l2 = group.onEditorClosed(e => this.onEditorClosed(e));
const l3 = group.onEditorDisposed(editor => this._onEditorDisposed.fire({ editor, group }));
const l4 = this.onGroupClosed(g => {
const unbind: IDisposable[] = [];
unbind.push(group.onEditorChanged(e => this._onModelChanged.fire(group)));
unbind.push(group.onEditorClosed(e => this.onEditorClosed(e)));
unbind.push(group.onEditorDisposed(editor => this._onEditorDisposed.fire({ editor, group })));
unbind.push(group.onEditorDirty(editor => this._onEditorDirty.fire({ editor, group })));
unbind.push(this.onGroupClosed(g => {
if (g === group) {
dispose(l1, l2, l3, l4);
dispose(unbind);
}
});
}));
return group;
}
......
......@@ -15,6 +15,9 @@ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiat
import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {IModeService} from 'vs/editor/common/services/modeService';
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
import {IEventService} from 'vs/platform/event/common/event';
import {EventType as WorkbenchEventType, UntitledEditorEvent} from 'vs/workbench/common/events';
import {ITextFileService} from 'vs/workbench/parts/files/common/files'; // TODO@Ben layer breaker
......@@ -31,6 +34,8 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput {
private modeId: string;
private cachedModel: UntitledEditorModel;
private toUnbind: IDisposable[];
constructor(
resource: URI,
hasAssociatedFilePath: boolean,
......@@ -39,13 +44,28 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput {
@ILifecycleService private lifecycleService: ILifecycleService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IModeService private modeService: IModeService,
@ITextFileService private textFileService: ITextFileService
@ITextFileService private textFileService: ITextFileService,
@IEventService private eventService: IEventService
) {
super();
this.resource = resource;
this.hasAssociatedFilePath = hasAssociatedFilePath;
this.modeId = modeId;
this.toUnbind = [];
this.registerListeners();
}
private registerListeners(): void {
this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_DELETED, (e: UntitledEditorEvent) => this.onDirtyStateChange(e)));
this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_DIRTY, (e: UntitledEditorEvent) => this.onDirtyStateChange(e)));
}
private onDirtyStateChange(e: UntitledEditorEvent): void {
if (e.resource.toString() === this.resource.toString()) {
this._onDidChangeDirty.fire();
}
}
public getTypeId(): string {
......@@ -158,11 +178,16 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput {
}
public dispose(): void {
super.dispose();
// Listeners
dispose(this.toUnbind);
// Model
if (this.cachedModel) {
this.cachedModel.dispose();
this.cachedModel = null;
}
super.dispose();
}
}
\ No newline at end of file
......@@ -52,11 +52,6 @@ export class EventType {
*/
static EDITOR_INPUT_CHANGED = 'editorInputChanged';
/**
* Event type for when the editor input dirty state changed.
*/
static EDITOR_INPUT_DIRTY_STATE_CHANGED = 'editorInputDirtyStateChanged';
/**
* Event type for when the editor input failed to be set to the editor.
*/
......
......@@ -53,6 +53,11 @@ export class ElectronWindow {
// React to editor input changes (Mac only)
if (platform.platform === platform.Platform.Mac) {
this.eventService.addListener2(EventType.EDITOR_INPUT_CHANGED, (e: EditorEvent) => {
let activeEditor = this.editorService.getActiveEditor();
if (activeEditor !== e.editor) {
return; // only care about active editor
}
let fileInput = workbenchEditorCommon.asFileEditorInput(e.editorInput, true);
let representedFilename = '';
if (fileInput) {
......
......@@ -18,10 +18,12 @@ import {IEditorRegistry, Extensions, EditorDescriptor} from 'vs/workbench/browse
import {BinaryEditorModel} from 'vs/workbench/common/editor/binaryEditorModel';
import {IFileOperationResult, FileOperationResult} from 'vs/platform/files/common/files';
import {FileEditorDescriptor} from 'vs/workbench/parts/files/browser/files';
import {ITextFileService, BINARY_FILE_EDITOR_ID, FILE_EDITOR_INPUT_ID, FileEditorInput as CommonFileEditorInput, AutoSaveMode, ModelState} from 'vs/workbench/parts/files/common/files';
import {ITextFileService, BINARY_FILE_EDITOR_ID, FILE_EDITOR_INPUT_ID, FileEditorInput as CommonFileEditorInput, AutoSaveMode, ModelState, EventType as FileEventType, TextFileChangeEvent} from 'vs/workbench/parts/files/common/files';
import {CACHE, TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel';
import {IWorkspaceContextService} from 'vs/workbench/services/workspace/common/contextService';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
import {IEventService} from 'vs/platform/event/common/event';
/**
* A file editor input is the input type for the file editor of file system resources.
......@@ -42,6 +44,8 @@ export class FileEditorInput extends CommonFileEditorInput {
private description: string;
private verboseDescription: string;
private toUnbind: IDisposable[];
/**
* An editor input who's contents are retrieved from file services.
*/
......@@ -49,17 +53,35 @@ export class FileEditorInput extends CommonFileEditorInput {
resource: URI,
mime: string,
preferredEncoding: string,
@IEventService private eventService: IEventService,
@IInstantiationService private instantiationService: IInstantiationService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@ITextFileService private textFileService: ITextFileService
) {
super();
this.toUnbind = [];
if (resource) {
this.setResource(resource);
this.setMime(mime || guessMimeTypes(this.resource.fsPath).join(', '));
this.preferredEncoding = preferredEncoding;
}
this.registerListeners();
}
private registerListeners(): void {
this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_DIRTY, (e: TextFileChangeEvent) => this.onDirtyStateChange(e)));
this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_SAVE_ERROR, (e: TextFileChangeEvent) => this.onDirtyStateChange(e)));
this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_SAVED, (e: TextFileChangeEvent) => this.onDirtyStateChange(e)));
this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_REVERTED, (e: TextFileChangeEvent) => this.onDirtyStateChange(e)));
}
private onDirtyStateChange(e: TextFileChangeEvent): void {
if (e.resource.toString() === this.resource.toString()) {
this._onDidChangeDirty.fire();
}
}
public setResource(resource: URI): void {
......@@ -254,9 +276,10 @@ export class FileEditorInput extends CommonFileEditorInput {
}
private indexOfClient(): number {
if (!types.isUndefinedOrNull(FileEditorInput.FILE_EDITOR_MODEL_CLIENTS[this.resource.toString()])) {
for (let i = 0; i < FileEditorInput.FILE_EDITOR_MODEL_CLIENTS[this.resource.toString()].length; i++) {
let client = FileEditorInput.FILE_EDITOR_MODEL_CLIENTS[this.resource.toString()][i];
const inputs = FileEditorInput.FILE_EDITOR_MODEL_CLIENTS[this.resource.toString()];
if (inputs) {
for (let i = 0; i < inputs.length; i++) {
let client = inputs[i];
if (client === this) {
return i;
}
......@@ -291,6 +314,9 @@ export class FileEditorInput extends CommonFileEditorInput {
public dispose(force?: boolean): void {
// Listeners
dispose(this.toUnbind);
// TextFileEditorModel
let cachedModel = CACHE.get(this.resource);
if (cachedModel) {
......
......@@ -10,7 +10,6 @@ import nls = require('vs/nls');
import {MIME_UNKNOWN} from 'vs/base/common/mime';
import URI from 'vs/base/common/uri';
import paths = require('vs/base/common/paths');
import arrays = require('vs/base/common/arrays');
import {DiffEditorInput} from 'vs/workbench/common/editor/diffEditorInput';
import {EditorInput, EditorOptions} from 'vs/workbench/common/editor';
import {BaseEditor} from 'vs/workbench/browser/parts/editor/baseEditor';
......@@ -82,49 +81,32 @@ export class FileTracker implements IWorkbenchContribution {
}
private onTextFileDirty(e: TextFileChangeEvent): void {
this.emitInputDirtyStateChangeEvent(e.resource, true);
if (this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY) {
this.updateActivityBadge(); // no indication needed when auto save is enabled for short delay
}
}
private onTextFileSaveError(e: TextFileChangeEvent): void {
this.emitInputDirtyStateChangeEvent(e.resource, true);
this.updateActivityBadge();
}
private onTextFileSaved(e: TextFileChangeEvent): void {
this.emitInputDirtyStateChangeEvent(e.resource, false);
if (this.lastDirtyCount > 0) {
this.updateActivityBadge();
}
}
private onTextFileReverted(e: TextFileChangeEvent): void {
this.emitInputDirtyStateChangeEvent(e.resource, false);
if (this.lastDirtyCount > 0) {
this.updateActivityBadge();
}
}
private onUntitledEditorDirty(e: UntitledEditorEvent): void {
let input = this.untitledEditorService.get(e.resource);
if (input) {
this.eventService.emit(WorkbenchEventType.EDITOR_INPUT_DIRTY_STATE_CHANGED, new EditorInputEvent(input));
}
this.updateActivityBadge();
}
private onUntitledEditorDeleted(e: UntitledEditorEvent): void {
let input = this.untitledEditorService.get(e.resource);
if (input) {
this.eventService.emit(WorkbenchEventType.EDITOR_INPUT_DIRTY_STATE_CHANGED, new EditorInputEvent(input));
}
if (this.lastDirtyCount > 0) {
this.updateActivityBadge();
}
......@@ -140,29 +122,6 @@ export class FileTracker implements IWorkbenchContribution {
}
}
private emitInputDirtyStateChangeEvent(resource: URI, gotDirty: boolean): void {
// Find all file editor inputs that are open from the given file resource and emit a editor input state change event.
// We could do all of this within the file editor input but having all the file change listeners in
// one place is more elegant and keeps the logic together at once place.
const editors = arrays.flatten(this.stacks.groups.map(g => g.getEditors()));
editors.forEach(input => {
if (this.matchesResource(input, resource)) {
this.eventService.emit(WorkbenchEventType.EDITOR_INPUT_DIRTY_STATE_CHANGED, new EditorInputEvent(input));
}
});
}
private matchesResource(input: EditorInput, resource: URI): boolean {
// Diff Editor Input
if (input instanceof DiffEditorInput) {
input = (<DiffEditorInput>input).getModifiedInput();
}
return input instanceof FileEditorInput && input.getResource().toString() === resource.toString();
}
// Note: there is some duplication with the other file event handler below. Since we cannot always rely on the disk events
// carrying all necessary data in all environments, we also use the local file events to make sure operations are handled.
// In any case there is no guarantee if the local event is fired first or the disk one. Thus, code must handle the case
......
......@@ -115,8 +115,18 @@ suite('Files - FileEditorInput', () => {
});
test('Input.matches() - FileEditorInput', function () {
let fileEditorInput = new FileEditorInput(toResource('/foo/bar/updatefile.js'), 'text/javascript', void 0, void 0, void 0, void 0);
let contentEditorInput2 = new FileEditorInput(toResource('/foo/bar/updatefile.js'), 'text/javascript', void 0, void 0, void 0, void 0);
let eventService = new TestEventService();
let contextService = new TestContextService();
let services = new ServiceCollection();
let instantiationService = new InstantiationService(services);
services.set(IEventService, eventService);
services.set(IWorkspaceContextService, contextService);
services.set(ITextFileService, <ITextFileService> instantiationService.createInstance(<any> TextFileService));
let fileEditorInput = instantiationService.createInstance(FileEditorInput, toResource('/foo/bar/updatefile.js'), 'text/javascript', void 0);
let contentEditorInput2 = instantiationService.createInstance(FileEditorInput, toResource('/foo/bar/updatefile.js'), 'text/javascript', void 0);
assert.strictEqual(fileEditorInput.matches(null), false);
assert.strictEqual(fileEditorInput.matches(fileEditorInput), true);
......
......@@ -13,6 +13,13 @@ import {Registry} from 'vs/platform/platform';
import {SyncDescriptor} from 'vs/platform/instantiation/common/descriptors';
import {FileEditorInput} from 'vs/workbench/parts/files/browser/editors/fileEditorInput';
import {Extensions} from 'vs/workbench/browser/parts/editor/baseEditor';
import {TestEventService, TestContextService} from 'vs/workbench/test/common/servicesTestUtils';
import {IEventService} from 'vs/platform/event/common/event';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection';
import {InstantiationService} from 'vs/platform/instantiation/common/instantiationService';
import {ITextFileService} from 'vs/workbench/parts/files/common/files';
import {TextFileService} from 'vs/workbench/parts/files/browser/textFileServices';
const ExtensionId = Extensions.Editors;
......@@ -37,9 +44,19 @@ suite('Files - TextFileEditor', () => {
equal(Registry.as(ExtensionId).getEditors().length, oldEditorCnt + 2);
equal(Registry.as(ExtensionId).getEditorInputs().length, oldInputCnt + 2);
strictEqual(Registry.as(ExtensionId).getEditor(new FileEditorInput(URI.file(join('C:\\', '/foo/bar/foobar.html')), 'test-text/html', void 0, void 0, void 0, void 0)), d1);
strictEqual(Registry.as(ExtensionId).getEditor(new FileEditorInput(URI.file(join('C:\\', '/foo/bar/foobar.js')), 'test-text/javascript', void 0, void 0, void 0, void 0)), d1);
strictEqual(Registry.as(ExtensionId).getEditor(new FileEditorInput(URI.file(join('C:\\', '/foo/bar/foobar.css')), 'test-text/css', void 0, void 0, void 0, void 0)), d2);
let eventService = new TestEventService();
let contextService = new TestContextService();
let services = new ServiceCollection();
let instantiationService = new InstantiationService(services);
services.set(IEventService, eventService);
services.set(IWorkspaceContextService, contextService);
services.set(ITextFileService, <ITextFileService> instantiationService.createInstance(<any> TextFileService));
strictEqual(Registry.as(ExtensionId).getEditor(instantiationService.createInstance(FileEditorInput, URI.file(join('C:\\', '/foo/bar/foobar.html')), 'test-text/html', void 0)), d1);
strictEqual(Registry.as(ExtensionId).getEditor(instantiationService.createInstance(FileEditorInput, URI.file(join('C:\\', '/foo/bar/foobar.js')), 'test-text/javascript', void 0)), d1);
strictEqual(Registry.as(ExtensionId).getEditor(instantiationService.createInstance(FileEditorInput, URI.file(join('C:\\', '/foo/bar/foobar.css')), 'test-text/css', void 0)), d2);
Registry.as(ExtensionId).setEditors(oldEditors);
});
......
......@@ -11,7 +11,7 @@ import {EventType} from 'vs/base/common/events';
import {IEditor as IBaseEditor} from 'vs/platform/editor/common/editor';
import {TextEditorOptions, EditorInput} from 'vs/workbench/common/editor';
import {BaseTextEditor} from 'vs/workbench/browser/parts/editor/textEditor';
import {EditorEvent, TextEditorSelectionEvent, EventType as WorkbenchEventType, EditorInputEvent} from 'vs/workbench/common/events';
import {EditorEvent, TextEditorSelectionEvent, EventType as WorkbenchEventType} from 'vs/workbench/common/events';
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
import {IHistoryService} from 'vs/workbench/services/history/common/history';
import {Selection} from 'vs/editor/common/core/selection';
......@@ -28,7 +28,6 @@ export class EditorState {
private static EDITOR_SELECTION_THRESHOLD = 5; // number of lines to move in editor to justify for new state
constructor(private _editorInput: IEditorInput, private _selection: Selection) {
//
}
public get editorInput(): IEditorInput {
......@@ -68,6 +67,7 @@ interface IInputWithPath {
export abstract class BaseHistoryService {
protected toUnbind: IDisposable[];
private activeEditorUnbind: IDisposable;
constructor(
private eventService: IEventService,
......@@ -82,25 +82,10 @@ export abstract class BaseHistoryService {
// Editor Input Changes
this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.EDITOR_INPUT_CHANGED, (e: EditorEvent) => this.onEditorInputChanged(e)));
// Editor Input State Changes
this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.EDITOR_INPUT_DIRTY_STATE_CHANGED, (e: EditorInputEvent) => this.onEditorInputDirtyStateChanged(e.editorInput)));
// Text Editor Selection Changes
this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.TEXT_EDITOR_SELECTION_CHANGED, (event: TextEditorSelectionEvent) => this.onTextEditorSelectionChanged(event)));
}
private onEditorInputDirtyStateChanged(input: IEditorInput): void {
// If an active editor is set, but is different from the one from the event, prevent update because the editor is not active.
let activeEditor = this.editorService.getActiveEditor();
if (activeEditor && !input.matches(activeEditor.input)) {
return;
}
// Calculate New Window Title
this.updateWindowTitle(input);
}
private onTextEditorSelectionChanged(event: TextEditorSelectionEvent): void {
// If an active editor is set, but is different from the one from the event, prevent update because the editor is not active.
......@@ -115,7 +100,22 @@ export abstract class BaseHistoryService {
}
private onEditorInputChanged(event: EditorEvent): void {
// Propagate to history
this.onEditorEvent(event.editor);
// Stop old listener
if (this.activeEditorUnbind) {
this.activeEditorUnbind.dispose();
}
// Apply listener for dirty changes
let activeInput = this.editorService.getActiveEditorInput();
if (activeInput instanceof EditorInput) {
this.activeEditorUnbind = activeInput.onDidChangeDirty(() => {
this.updateWindowTitle(activeInput); // Calculate New Window Title when dirty state changes
});
}
}
private onEditorEvent(editor: IBaseEditor): void {
......
......@@ -16,7 +16,9 @@ import {QuickOpenController} from 'vs/workbench/browser/parts/quickopen/quickOpe
import {Mode} from 'vs/base/parts/quickopen/common/quickOpen';
import {StringEditorInput} from 'vs/workbench/common/editor/stringEditorInput';
import {EditorInput} from 'vs/workbench/common/editor';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {isEmptyObject} from 'vs/base/common/types';
import {IEventService} from 'vs/platform/event/common/event';
import {join} from 'vs/base/common/paths';
import {Extensions, IEditorRegistry} from 'vs/workbench/browser/parts/editor/baseEditor';
import URI from 'vs/base/common/uri';
......@@ -96,10 +98,16 @@ suite('Workbench QuickOpen', () => {
test('EditorHistoryModel', () => {
Registry.as('workbench.contributions.editors').setInstantiationService(new InstantiationService());
let services = new ServiceCollection();
let eventService = new TestEventService();
let editorService = new TestEditorService();
let contextService = new TestContextService();
let inst = new InstantiationService(new ServiceCollection([IWorkbenchEditorService, editorService]));
services.set(IEventService, eventService);
services.set(IWorkspaceContextService, contextService);
services.set(IWorkbenchEditorService, editorService);
let inst = new InstantiationService(services);
let model = new EditorHistoryModel(editorService, inst, contextService);
......@@ -202,13 +210,18 @@ suite('Workbench QuickOpen', () => {
});
test('QuickOpenController adds to history on editor input change and can handle dispose', () => {
let editorService = new TestEditorService();
let services = new ServiceCollection();
let eventService = new TestEventService();
let storageService = new TestStorageService();
let eventService = new TestEventService();
let editorService = new TestEditorService();
let contextService = new TestContextService();
let inst = new InstantiationService(new ServiceCollection([IWorkbenchEditorService, editorService]));
services.set(IEventService, eventService);
services.set(IWorkspaceContextService, contextService);
services.set(IWorkbenchEditorService, editorService);
let inst = new InstantiationService(services);
let controller = new QuickOpenController(
eventService,
......@@ -226,7 +239,7 @@ suite('Workbench QuickOpen', () => {
assert.equal(0, controller.getEditorHistoryModel().getEntries().length);
let cinput1 = <EditorInput>inst.createInstance(fileInputCtor, toResource('Hello World'), 'text/plain', void 0);
let cinput1 = <EditorInput>inst.createInstance(fileInputCtor, toResource('Hello World'), 'text/plain', null);
let event = new EditorEvent(null, '', cinput1, null, Position.LEFT);
eventService.emit(EventType.EDITOR_INPUT_CHANGING, event);
......
......@@ -101,6 +101,10 @@ class TestEditorInput extends EditorInput {
public matches(other: TestEditorInput): boolean {
return other && this.id === other.id && other instanceof TestEditorInput;
}
public setDirty(): void {
this._onDidChangeDirty.fire();
}
}
class NonSerializableTestEditorInput extends EditorInput {
......@@ -1608,4 +1612,42 @@ suite('Editor Stacks Model', () => {
assert.equal(input2.isDisposed(), true);
assert.equal(input1.isDisposed(), false);
});
test('Stack - Multiple Editors - Editor Emits Dirty', function () {
const model = create();
const group1 = model.openGroup('group1');
const group2 = model.openGroup('group2');
const input1 = input();
const input2 = input();
group1.openEditor(input1, { pinned: true, active: true});
group2.openEditor(input2, { pinned: true, active: true });
let dirtyCounter = 0;
model.onEditorDirty(() => {
dirtyCounter++;
});
(<TestEditorInput>input1).setDirty();
assert.equal(dirtyCounter, 1);
(<TestEditorInput>input2).setDirty();
assert.equal(dirtyCounter, 2);
group2.closeAllEditors();
(<TestEditorInput>input2).setDirty();
assert.equal(dirtyCounter, 2);
model.closeGroups();
(<TestEditorInput>input1).setDirty();
assert.equal(dirtyCounter, 2);
});
});
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册