editors - introduce aggregated editors change event and adopt (fix #123864)

This commit is contained in:
Benjamin Pasero 2021-08-23 12:49:56 +02:00
parent 967e3b1509
commit 4b254e6288
5 changed files with 102 additions and 32 deletions

View file

@ -3,12 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { ExtHostContext, IExtHostEditorTabsShape, IExtHostContext, MainContext, IEditorTabDto } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { EditorResourceAccessor, Verbosity } from 'vs/workbench/common/editor';
import { GroupChangeKind, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
export interface ITabInfo {
@ -19,10 +19,7 @@ export interface ITabInfo {
@extHostNamedCustomer(MainContext.MainThreadEditorTabs)
export class MainThreadEditorTabs {
private static _GroupEventFilter = new Set([GroupChangeKind.EDITOR_CLOSE, GroupChangeKind.EDITOR_OPEN]);
private readonly _dispoables = new DisposableStore();
private readonly _groups = new Map<IEditorGroup, IDisposable>();
private readonly _proxy: IExtHostEditorTabsShape;
constructor(
@ -33,35 +30,14 @@ export class MainThreadEditorTabs {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostEditorTabs);
this._editorGroupsService.whenReady.then(() => this._editorGroupsService.groups.forEach(this._subscribeToGroup, this));
this._dispoables.add(_editorGroupsService.onDidAddGroup(this._subscribeToGroup, this));
this._dispoables.add(_editorGroupsService.onDidRemoveGroup(e => {
const subscription = this._groups.get(e);
if (subscription) {
subscription.dispose();
this._groups.delete(e);
this._pushEditorTabs();
}
}));
this._dispoables.add(editorService.onDidActiveEditorChange(this._pushEditorTabs, this));
this._pushEditorTabs();
this._dispoables.add(editorService.onDidEditorsChange(this._pushEditorTabs, this));
this._editorGroupsService.whenReady.then(() => this._pushEditorTabs());
}
dispose(): void {
dispose(this._groups.values());
this._dispoables.dispose();
}
private _subscribeToGroup(group: IEditorGroup) {
this._groups.get(group)?.dispose();
const listener = group.onDidGroupChange(e => {
if (MainThreadEditorTabs._GroupEventFilter.has(e.kind)) {
this._pushEditorTabs();
}
});
this._groups.set(group, listener);
}
private _pushEditorTabs(): void {
const tabs: IEditorTabDto[] = [];
for (const group of this._editorGroupsService.groups) {

View file

@ -14,7 +14,7 @@ import { ResourceMap } from 'vs/base/common/map';
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { IFileService, FileOperationEvent, FileOperation, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files';
import { Schemas } from 'vs/base/common/network';
import { Event, Emitter } from 'vs/base/common/event';
import { Event, Emitter, DebounceEmitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { basename, joinPath } from 'vs/base/common/resources';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
@ -54,6 +54,9 @@ export class EditorService extends Disposable implements EditorServiceImpl {
private readonly _onDidVisibleEditorsChange = this._register(new Emitter<void>());
readonly onDidVisibleEditorsChange = this._onDidVisibleEditorsChange.event;
private readonly _onDidEditorsChange = this._register(new DebounceEmitter<void>({ delay: 0, merge: () => undefined }));
readonly onDidEditorsChange = this._onDidEditorsChange.event;
private readonly _onDidCloseEditor = this._register(new Emitter<IEditorCloseEvent>());
readonly onDidCloseEditor = this._onDidCloseEditor.event;
@ -97,6 +100,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
this.editorGroupService.whenReady.then(() => this.onEditorGroupsReady());
this.editorGroupService.onDidChangeActiveGroup(group => this.handleActiveEditorChange(group));
this.editorGroupService.onDidAddGroup(group => this.registerGroupListeners(group as IEditorGroupView));
this.editorGroupService.onDidMoveGroup(group => this.handleGroupMove(group));
this.editorsObserver.onDidMostRecentlyActiveEditorsChange(() => this._onDidMostRecentlyActiveEditorsChange.fire());
// Out of workspace file watchers
@ -148,6 +152,14 @@ export class EditorService extends Disposable implements EditorServiceImpl {
}
}
private handleGroupMove(group: IEditorGroup): void {
if (group.isEmpty) {
return; // empty groups do not change structure of editors
}
this._onDidEditorsChange.fire();
}
private handleActiveEditorChange(group: IEditorGroup): void {
if (group !== this.editorGroupService.activeGroup) {
return; // ignore if not the active group
@ -168,15 +180,23 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// Fire event to outside parties
this._onDidActiveEditorChange.fire();
this._onDidEditorsChange.fire();
}
private registerGroupListeners(group: IEditorGroupView): void {
const groupDisposables = new DisposableStore();
groupDisposables.add(group.onDidGroupChange(e => {
if (e.kind === GroupChangeKind.EDITOR_ACTIVE) {
this.handleActiveEditorChange(group);
this._onDidVisibleEditorsChange.fire();
switch (e.kind) {
case GroupChangeKind.EDITOR_ACTIVE:
this.handleActiveEditorChange(group);
this._onDidVisibleEditorsChange.fire();
break;
case GroupChangeKind.EDITOR_CLOSE:
case GroupChangeKind.EDITOR_OPEN:
case GroupChangeKind.EDITOR_MOVE:
this._onDidEditorsChange.fire();
break;
}
}));

View file

@ -97,6 +97,16 @@ export interface IEditorService {
*/
readonly onDidVisibleEditorsChange: Event<void>;
/**
* An aggregated event for a set of editor related events
* across all editor groups:
* - active editor changes
* - editors opening/closing
* - editors moving
* - groups moving (unless they are empty)
*/
readonly onDidEditorsChange: Event<void>;
/**
* Emitted when an editor is closed.
*/

View file

@ -1858,6 +1858,69 @@ suite('EditorService', () => {
visibleEditorChangeListener.dispose();
});
test('editors change event', async function () {
const [part, service] = await createEditorService();
const rootGroup = part.activeGroup;
let input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID);
let otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID);
let editorsChangeEventCounter = 0;
async function assertEditorsChangeEvent(expected: number) {
await Event.toPromise(service.onDidEditorsChange);
editorsChangeEventCounter++;
assert.strictEqual(editorsChangeEventCounter, expected);
}
// open
let p: Promise<unknown> = service.openEditor(input, { pinned: true });
await assertEditorsChangeEvent(1);
await p;
// open (other)
p = service.openEditor(otherInput, { pinned: true });
await assertEditorsChangeEvent(2);
await p;
// close (inactive)
p = rootGroup.closeEditor(input);
await assertEditorsChangeEvent(3);
await p;
// close (active)
p = rootGroup.closeEditor(otherInput);
await assertEditorsChangeEvent(4);
await p;
input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID);
otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID);
// open editors
p = service.openEditors([{ editor: input, options: { pinned: true } }, { editor: otherInput, options: { pinned: true } }]);
await assertEditorsChangeEvent(5);
await p;
// active editor change
p = service.openEditor(otherInput);
await assertEditorsChangeEvent(6);
await p;
// move editor (in group)
p = service.openEditor(input, { pinned: true, index: 1 });
await assertEditorsChangeEvent(7);
await p;
// move editor (across groups)
const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT);
rootGroup.moveEditor(input, rightGroup);
await assertEditorsChangeEvent(8);
// move group
part.moveGroup(rightGroup, rootGroup, GroupDirection.LEFT);
await assertEditorsChangeEvent(9);
});
test('two active editor change events when opening editor to the side', async function () {
const [, service] = await createEditorService();

View file

@ -798,6 +798,7 @@ export class TestEditorService implements EditorServiceImpl {
onDidActiveEditorChange: Event<void> = Event.None;
onDidVisibleEditorsChange: Event<void> = Event.None;
onDidEditorsChange: Event<void> = Event.None;
onDidCloseEditor: Event<IEditorCloseEvent> = Event.None;
onDidOpenEditorFail: Event<IEditorIdentifier> = Event.None;
onDidMostRecentlyActiveEditorsChange: Event<void> = Event.None;