mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-26 03:29:00 +01:00
Abstract ExtHostEditorTabs
This commit is contained in:
@@ -5,160 +5,202 @@
|
||||
|
||||
import type * as vscode from 'vscode';
|
||||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { IEditorTabDto, IEditorTabGroupDto, IExtHostEditorTabsShape, MainContext, MainThreadEditorTabsShape, TabKind } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IEditorTabDto, IEditorTabGroupDto, IExtHostEditorTabsShape, MainContext, MainThreadEditorTabsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ViewColumn } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
export interface IEditorTab {
|
||||
label: string;
|
||||
viewColumn: ViewColumn;
|
||||
resource: vscode.Uri | undefined;
|
||||
viewType: string | undefined;
|
||||
isActive: boolean;
|
||||
isPinned: boolean;
|
||||
kind: TabKind;
|
||||
isDirty: boolean;
|
||||
additionalResourcesAndViewTypes: { resource: vscode.Uri | undefined; viewType: string | undefined }[];
|
||||
move(index: number, viewColumn: ViewColumn): Promise<void>;
|
||||
close(preserveFocus: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IEditorTabGroup {
|
||||
isActive: boolean;
|
||||
viewColumn: ViewColumn;
|
||||
activeTab: IEditorTab | undefined;
|
||||
tabs: IEditorTab[];
|
||||
}
|
||||
|
||||
export interface IEditorTabGroups {
|
||||
groups: IEditorTabGroup[];
|
||||
activeTabGroup: IEditorTabGroup | undefined;
|
||||
readonly onDidChangeTabGroup: Event<void>;
|
||||
readonly onDidChangeActiveTabGroup: Event<IEditorTabGroup | undefined>;
|
||||
}
|
||||
|
||||
export interface IExtHostEditorTabs extends IExtHostEditorTabsShape {
|
||||
readonly _serviceBrand: undefined;
|
||||
tabGroups: IEditorTabGroups;
|
||||
tabGroups: vscode.TabGroups;
|
||||
}
|
||||
|
||||
export const IExtHostEditorTabs = createDecorator<IExtHostEditorTabs>('IExtHostEditorTabs');
|
||||
|
||||
class ExtHostEditorTabGroup {
|
||||
|
||||
private _apiObject: vscode.TabGroup | undefined;
|
||||
private _dto: IEditorTabGroupDto;
|
||||
private _tabs: ExtHostEditorTab[] = [];
|
||||
// private _activeTabId: string = '';
|
||||
|
||||
constructor(dto: IEditorTabGroupDto, proxy: MainThreadEditorTabsShape) {
|
||||
this._dto = dto;
|
||||
// Construct all tabs from the given dto
|
||||
this._tabs = dto.tabs.map(tab => new ExtHostEditorTab(tab, proxy));
|
||||
}
|
||||
|
||||
get apiObject(): vscode.TabGroup {
|
||||
// Don't want to lose reference to parent `this` in the getters
|
||||
const that = this;
|
||||
if (!this._apiObject) {
|
||||
this._apiObject = Object.freeze({
|
||||
get isActive() {
|
||||
return that._dto.isActive;
|
||||
},
|
||||
get viewColumn() {
|
||||
return typeConverters.ViewColumn.to(that._dto.viewColumn);
|
||||
},
|
||||
get activeTab() {
|
||||
return that._tabs.find(tab => that._dto.activeTab?.id === tab.tabId)?.apiObject;
|
||||
// return that._tabs.find(tab => tab.tabId === that._activeTabId)?.apiObject;
|
||||
},
|
||||
get tabs() {
|
||||
return that._tabs.map(tab => tab.apiObject);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this._apiObject;
|
||||
}
|
||||
|
||||
get groupId(): number {
|
||||
return this._dto.groupId;
|
||||
}
|
||||
|
||||
findExtHostTabFromApi(apiTab: vscode.Tab): ExtHostEditorTab | undefined {
|
||||
return this._tabs.find(extHostTab => extHostTab.apiObject === apiTab);
|
||||
}
|
||||
}
|
||||
|
||||
class ExtHostEditorTab {
|
||||
private _apiObject: vscode.Tab | undefined;
|
||||
private _dto: IEditorTabDto;
|
||||
private _proxy: MainThreadEditorTabsShape;
|
||||
|
||||
constructor(dto: IEditorTabDto, proxy: MainThreadEditorTabsShape) {
|
||||
this._dto = dto;
|
||||
this._proxy = proxy;
|
||||
}
|
||||
|
||||
get apiObject(): vscode.Tab {
|
||||
// Don't want to lose reference to parent `this` in the getters
|
||||
const that = this;
|
||||
if (!this._apiObject) {
|
||||
this._apiObject = Object.freeze({
|
||||
get isActive() {
|
||||
return that._dto.isActive;
|
||||
},
|
||||
get label() {
|
||||
return that._dto.label;
|
||||
},
|
||||
get resource() {
|
||||
return URI.revive(that._dto.resource);
|
||||
},
|
||||
get viewType() {
|
||||
return that._dto.editorId;
|
||||
},
|
||||
get isDirty() {
|
||||
return that._dto.isDirty;
|
||||
},
|
||||
get isPinned() {
|
||||
return that._dto.isDirty;
|
||||
},
|
||||
get viewColumn() {
|
||||
return typeConverters.ViewColumn.to(that._dto.viewColumn);
|
||||
},
|
||||
get kind() {
|
||||
return that._dto.kind;
|
||||
},
|
||||
get additionalResourcesAndViewTypes() {
|
||||
return that._dto.additionalResourcesAndViewTypes.map(({ resource, viewId }) => ({ resource: URI.revive(resource), viewType: viewId }));
|
||||
},
|
||||
move: async (index: number, viewColumn: ViewColumn) => {
|
||||
this._proxy.$moveTab(that._dto, index, typeConverters.ViewColumn.from(viewColumn));
|
||||
return;
|
||||
},
|
||||
close: async (preserveFocus) => {
|
||||
this._proxy.$closeTab(that._dto, preserveFocus);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
return this._apiObject;
|
||||
}
|
||||
|
||||
get tabId(): string {
|
||||
return this._dto.id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class ExtHostEditorTabs implements IExtHostEditorTabs {
|
||||
readonly _serviceBrand: undefined;
|
||||
private readonly _proxy: MainThreadEditorTabsShape;
|
||||
|
||||
private readonly _onDidChangeTabGroup = new Emitter<void>();
|
||||
|
||||
private readonly _onDidChangeActiveTabGroup = new Emitter<IEditorTabGroup | undefined>();
|
||||
private readonly _onDidChangeActiveTabGroup = new Emitter<vscode.TabGroup | undefined>();
|
||||
|
||||
private _tabGroups: IEditorTabGroups = {
|
||||
private _activeGroupId: number | undefined;
|
||||
|
||||
private _tabGroups: vscode.TabGroups = {
|
||||
groups: [],
|
||||
activeTabGroup: undefined,
|
||||
onDidChangeTabGroup: this._onDidChangeTabGroup.event,
|
||||
onDidChangeActiveTabGroup: this._onDidChangeActiveTabGroup.event
|
||||
};
|
||||
|
||||
private _extHostTabGroups: ExtHostEditorTabGroup[] = [];
|
||||
|
||||
constructor(@IExtHostRpcService extHostRpc: IExtHostRpcService) {
|
||||
this._proxy = extHostRpc.getProxy(MainContext.MainThreadEditorTabs);
|
||||
}
|
||||
|
||||
get tabGroups(): IEditorTabGroups {
|
||||
get tabGroups(): vscode.TabGroups {
|
||||
return this._tabGroups;
|
||||
}
|
||||
|
||||
$acceptEditorTabModel(tabGroups: IEditorTabGroupDto[]): void {
|
||||
// Clears the tab groups array
|
||||
this._tabGroups.groups.length = 0;
|
||||
let activeGroupFound = false;
|
||||
for (const group of tabGroups) {
|
||||
let activeTab: IEditorTab | undefined;
|
||||
const tabs = group.tabs.map(tab => {
|
||||
const extHostTab = this.createExtHostTabObject(tab);
|
||||
if (tab.isActive) {
|
||||
activeTab = extHostTab;
|
||||
}
|
||||
return extHostTab;
|
||||
});
|
||||
this._tabGroups.groups.push(Object.freeze({
|
||||
isActive: group.isActive,
|
||||
viewColumn: typeConverters.ViewColumn.to(group.viewColumn),
|
||||
activeTab,
|
||||
tabs
|
||||
}));
|
||||
// If the currrent group is active, set the active group to be that group.
|
||||
// We must use the same object so we pull from the array to allow for reference equality
|
||||
if (group.isActive) {
|
||||
activeGroupFound = true;
|
||||
const oldActiveTabGroup = this._tabGroups.activeTabGroup;
|
||||
this._tabGroups.activeTabGroup = this._tabGroups.groups[this._tabGroups.groups.length - 1];
|
||||
// Diff the old and current active group to decide if we should fire a change event
|
||||
if (this.groupDiff(oldActiveTabGroup, this._tabGroups.activeTabGroup)) {
|
||||
this._onDidChangeActiveTabGroup.fire(this._tabGroups.activeTabGroup);
|
||||
}
|
||||
}
|
||||
this._extHostTabGroups = tabGroups.map(tabGroup => {
|
||||
const group = new ExtHostEditorTabGroup(tabGroup, this._proxy);
|
||||
return group;
|
||||
});
|
||||
for (const group of this._extHostTabGroups) {
|
||||
this._tabGroups.groups.push(group.apiObject);
|
||||
}
|
||||
// No active group was found in the model (most common case is model was empty) fire undefined event
|
||||
if (!activeGroupFound) {
|
||||
this._tabGroups.activeTabGroup = undefined;
|
||||
this._onDidChangeActiveTabGroup.fire(undefined);
|
||||
// Set the active tab group id
|
||||
const activeTabGroup = this._extHostTabGroups.find(group => group.apiObject.isActive === true);
|
||||
const activeTabGroupId = activeTabGroup?.groupId;
|
||||
if (activeTabGroupId !== this._activeGroupId) {
|
||||
this._activeGroupId = activeTabGroupId;
|
||||
this._onDidChangeActiveTabGroup.fire(activeTabGroup?.apiObject);
|
||||
// TODO @lramos15 how do we set this without messing up readonly
|
||||
this._tabGroups.activeTabGroup = activeTabGroup?.apiObject;
|
||||
}
|
||||
this._onDidChangeTabGroup.fire();
|
||||
}
|
||||
|
||||
private createExtHostTabObject(tabDto: IEditorTabDto): IEditorTab {
|
||||
return Object.freeze({
|
||||
label: tabDto.label,
|
||||
viewColumn: typeConverters.ViewColumn.to(tabDto.viewColumn),
|
||||
resource: URI.revive(tabDto.resource),
|
||||
additionalResourcesAndViewTypes: tabDto.additionalResourcesAndViewTypes.map(({ resource, viewId }) => ({ resource: URI.revive(resource), viewType: viewId })),
|
||||
viewType: tabDto.editorId,
|
||||
isActive: tabDto.isActive,
|
||||
kind: tabDto.kind,
|
||||
isDirty: tabDto.isDirty,
|
||||
isPinned: tabDto.isPinned,
|
||||
move: async (index: number, viewColumn: ViewColumn) => {
|
||||
this._proxy.$moveTab(tabDto, index, typeConverters.ViewColumn.from(viewColumn));
|
||||
// TODO: Need an on did change tab event at the group level
|
||||
return;
|
||||
},
|
||||
close: async (preserveFocus) => {
|
||||
await this._proxy.$closeTab(tabDto, preserveFocus);
|
||||
// TODO: Need an on did change tab event at the group level
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two groups determining if they're the same or different
|
||||
* @param group1 The first group to compare
|
||||
* @param group2 The second group to compare
|
||||
* @returns True if different, false otherwise
|
||||
*/
|
||||
private groupDiff(group1: IEditorTabGroup | undefined, group2: IEditorTabGroup | undefined): boolean {
|
||||
if (group1 === group2) {
|
||||
return false;
|
||||
}
|
||||
// They would be reference equal if both undefined so one is undefined and one isn't hence different
|
||||
if (!group1 || !group2) {
|
||||
return true;
|
||||
}
|
||||
if (group1.isActive !== group2.isActive
|
||||
|| group1.viewColumn !== group2.viewColumn
|
||||
|| group1.tabs.length !== group2.tabs.length
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
for (let i = 0; i < group1.tabs.length; i++) {
|
||||
if (this.tabDiff(group1.tabs[i], group2.tabs[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// private groupDiff(group1: IEditorTabGroup | undefined, group2: IEditorTabGroup | undefined): boolean {
|
||||
// if (group1 === group2) {
|
||||
// return false;
|
||||
// }
|
||||
// // They would be reference equal if both undefined so one is undefined and one isn't hence different
|
||||
// if (!group1 || !group2) {
|
||||
// return true;
|
||||
// }
|
||||
// if (group1.isActive !== group2.isActive
|
||||
// || group1.viewColumn !== group2.viewColumn
|
||||
// || group1.tabs.length !== group2.tabs.length
|
||||
// ) {
|
||||
// return true;
|
||||
// }
|
||||
// for (let i = 0; i < group1.tabs.length; i++) {
|
||||
// if (this.tabDiff(group1.tabs[i], group2.tabs[i])) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Compares two tabs determining if they're the same or different
|
||||
@@ -166,34 +208,34 @@ export class ExtHostEditorTabs implements IExtHostEditorTabs {
|
||||
* @param tab2 The second tab to compare
|
||||
* @returns True if different, false otherwise
|
||||
*/
|
||||
private tabDiff(tab1: IEditorTab | undefined, tab2: IEditorTab | undefined): boolean {
|
||||
if (tab1 === tab2) {
|
||||
return false;
|
||||
}
|
||||
// They would be reference equal if both undefined so one is undefined and one isn't therefore they're different
|
||||
if (!tab1 || !tab2) {
|
||||
return true;
|
||||
}
|
||||
if (tab1.label !== tab2.label
|
||||
|| tab1.viewColumn !== tab2.viewColumn
|
||||
|| tab1.resource?.toString() !== tab2.resource?.toString()
|
||||
|| tab1.viewType !== tab2.viewType
|
||||
|| tab1.isActive !== tab2.isActive
|
||||
|| tab1.isPinned !== tab2.isPinned
|
||||
|| tab1.isDirty !== tab2.isDirty
|
||||
|| tab1.additionalResourcesAndViewTypes.length !== tab2.additionalResourcesAndViewTypes.length
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
for (let i = 0; i < tab1.additionalResourcesAndViewTypes.length; i++) {
|
||||
const tab1Resource = tab1.additionalResourcesAndViewTypes[i].resource;
|
||||
const tab2Resource = tab2.additionalResourcesAndViewTypes[i].resource;
|
||||
const tab1viewType = tab1.additionalResourcesAndViewTypes[i].viewType;
|
||||
const tab2viewType = tab2.additionalResourcesAndViewTypes[i].viewType;
|
||||
if (tab1Resource?.toString() !== tab2Resource?.toString() || tab1viewType !== tab2viewType) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// private tabDiff(tab1: IEditorTab | undefined, tab2: IEditorTab | undefined): boolean {
|
||||
// if (tab1 === tab2) {
|
||||
// return false;
|
||||
// }
|
||||
// // They would be reference equal if both undefined so one is undefined and one isn't therefore they're different
|
||||
// if (!tab1 || !tab2) {
|
||||
// return true;
|
||||
// }
|
||||
// if (tab1.label !== tab2.label
|
||||
// || tab1.viewColumn !== tab2.viewColumn
|
||||
// || tab1.resource?.toString() !== tab2.resource?.toString()
|
||||
// || tab1.viewType !== tab2.viewType
|
||||
// || tab1.isActive !== tab2.isActive
|
||||
// || tab1.isPinned !== tab2.isPinned
|
||||
// || tab1.isDirty !== tab2.isDirty
|
||||
// || tab1.additionalResourcesAndViewTypes.length !== tab2.additionalResourcesAndViewTypes.length
|
||||
// ) {
|
||||
// return true;
|
||||
// }
|
||||
// for (let i = 0; i < tab1.additionalResourcesAndViewTypes.length; i++) {
|
||||
// const tab1Resource = tab1.additionalResourcesAndViewTypes[i].resource;
|
||||
// const tab2Resource = tab2.additionalResourcesAndViewTypes[i].resource;
|
||||
// const tab1viewType = tab1.additionalResourcesAndViewTypes[i].viewType;
|
||||
// const tab2viewType = tab2.additionalResourcesAndViewTypes[i].viewType;
|
||||
// if (tab1Resource?.toString() !== tab2Resource?.toString() || tab1viewType !== tab2viewType) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user