Move close and add array support

This commit is contained in:
Logan Ramos
2022-03-17 10:49:19 -04:00
parent 1e0f8c9ba1
commit c56ff000d4
6 changed files with 193 additions and 120 deletions
+1 -1
View File
@@ -1287,7 +1287,7 @@ export class Repository implements Disposable {
t.additionalResourcesAndViewTypes.find(r => r.resource!.scheme === 'git')));
// Close editors
diffEditorTabsToClose.forEach(t => t.close(false));
window.tabGroups.close(diffEditorTabsToClose, true);
}
async branch(name: string, _checkout: boolean, _ref?: string): Promise<void> {
@@ -377,19 +377,28 @@ export class MainThreadEditorTabs implements MainThreadEditorTabsShape {
return;
}
async $closeTab(tabId: string, preserveFocus: boolean): Promise<void> {
const tabInfo = this._tabInfoLookup.get(tabId);
const tab = tabInfo?.tab;
const group = tabInfo?.group;
const editorTab = tabInfo?.editorInput;
if (!group || !tab || !tabInfo || !editorTab) {
return;
async $closeTab(tabIds: string[], preserveFocus?: boolean): Promise<void> {
const groups: Map<IEditorGroup, EditorInput[]> = new Map();
for (const tabId of tabIds) {
const tabInfo = this._tabInfoLookup.get(tabId);
const tab = tabInfo?.tab;
const group = tabInfo?.group;
const editorTab = tabInfo?.editorInput;
// If not found skip
if (!group || !tab || !tabInfo || !editorTab) {
continue;
}
const groupEditors = groups.get(group);
if (!groupEditors) {
groups.set(group, [editorTab]);
} else {
groupEditors.push(editorTab);
}
}
const editor = group.editors.find(editor => editor.matches(editorTab));
if (!editor) {
return;
// Loop over keys of the groups map and call closeEditors
for (const [group, editors] of groups) {
group.closeEditors(editors, { preserveFocus });
}
await group.closeEditor(editor, { preserveFocus });
}
//#endregion
}
@@ -612,7 +612,7 @@ export interface ExtHostEditorInsetsShape {
export interface MainThreadEditorTabsShape extends IDisposable {
// manage tabs: move, close, rearrange etc
$moveTab(tabId: string, index: number, viewColumn: EditorGroupColumn): void;
$closeTab(tabId: string, preserveFocus: boolean): Promise<void>;
$closeTab(tabIds: string[], preserveFocus?: boolean): Promise<void>;
}
export interface IEditorTabGroupDto {
@@ -19,76 +19,6 @@ export interface IExtHostEditorTabs extends IExtHostEditorTabsShape {
export const IExtHostEditorTabs = createDecorator<IExtHostEditorTabs>('IExtHostEditorTabs');
class ExtHostEditorTabGroup {
private _apiObject: vscode.TabGroup | undefined;
private _dto: IEditorTabGroupDto;
private _tabs: ExtHostEditorTab[] = [];
private _activeTabId: string = '';
private _activeGroupIdGetter: () => number | undefined;
constructor(dto: IEditorTabGroupDto, proxy: MainThreadEditorTabsShape, activeGroupIdGetter: () => number | undefined) {
this._dto = dto;
this._activeGroupIdGetter = activeGroupIdGetter;
// Construct all tabs from the given dto
for (const tabDto of dto.tabs) {
if (tabDto.isActive) {
this._activeTabId = tabDto.id;
}
this._tabs.push(new ExtHostEditorTab(tabDto, proxy, () => this.activeTabId()));
}
}
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() {
// We use a getter function here to always ensure at most 1 active group and prevent iteration for being required
return that._dto.groupId === that._activeGroupIdGetter();
},
get viewColumn() {
return typeConverters.ViewColumn.to(that._dto.viewColumn);
},
get activeTab() {
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;
}
acceptGroupDtoUpdate(dto: IEditorTabGroupDto) {
this._dto = dto;
}
acceptTabDtoUpdate(dto: IEditorTabDto) {
const tab = this._tabs.find(extHostTab => extHostTab.tabId === dto.id);
if (tab) {
if (dto.isActive) {
this._activeTabId = dto.id;
}
tab.acceptDtoUpdate(dto);
}
}
// Not a getter since it must be a function to be used as a callback for the tabs
activeTabId(): string {
return this._activeTabId;
}
findExtHostTabFromApi(apiTab: vscode.Tab): ExtHostEditorTab | undefined {
return this._tabs.find(extHostTab => extHostTab.apiObject === apiTab);
}
}
class ExtHostEditorTab {
private _apiObject: vscode.Tab | undefined;
@@ -138,10 +68,6 @@ class ExtHostEditorTab {
move: async (index: number, viewColumn: ViewColumn) => {
this._proxy.$moveTab(that._dto.id, index, typeConverters.ViewColumn.from(viewColumn));
return;
},
close: async (preserveFocus) => {
this._proxy.$closeTab(that._dto.id, preserveFocus);
return;
}
});
}
@@ -158,6 +84,77 @@ class ExtHostEditorTab {
}
class ExtHostEditorTabGroup {
private _apiObject: vscode.TabGroup | undefined;
private _dto: IEditorTabGroupDto;
private _tabs: ExtHostEditorTab[] = [];
private _activeTabId: string = '';
private _activeGroupIdGetter: () => number | undefined;
constructor(dto: IEditorTabGroupDto, proxy: MainThreadEditorTabsShape, activeGroupIdGetter: () => number | undefined) {
this._dto = dto;
this._activeGroupIdGetter = activeGroupIdGetter;
// Construct all tabs from the given dto
for (const tabDto of dto.tabs) {
if (tabDto.isActive) {
this._activeTabId = tabDto.id;
}
this._tabs.push(new ExtHostEditorTab(tabDto, proxy, () => this.activeTabId()));
}
}
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() {
// We use a getter function here to always ensure at most 1 active group and prevent iteration for being required
return that._dto.groupId === that._activeGroupIdGetter();
},
get viewColumn() {
return typeConverters.ViewColumn.to(that._dto.viewColumn);
},
get activeTab() {
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;
}
get tabs(): ExtHostEditorTab[] {
return this._tabs;
}
acceptGroupDtoUpdate(dto: IEditorTabGroupDto) {
this._dto = dto;
}
acceptTabDtoUpdate(dto: IEditorTabDto) {
const tab = this._tabs.find(extHostTab => extHostTab.tabId === dto.id);
if (tab) {
if (dto.isActive) {
this._activeTabId = dto.id;
}
tab.acceptDtoUpdate(dto);
}
}
// Not a getter since it must be a function to be used as a callback for the tabs
activeTabId(): string {
return this._activeTabId;
}
}
class ExtHostTabGroups {
private _apiObject: vscode.TabGroups | undefined;
@@ -167,7 +164,8 @@ class ExtHostTabGroups {
constructor(
private readonly _onDidChangeTagGroup: vscode.Event<void>,
private readonly _onDidChangeActiveTabGroup: vscode.Event<vscode.TabGroup | undefined>,
private readonly _getActiveTabGroupdId: () => number | undefined
private readonly _getActiveTabGroupdId: () => number | undefined,
private readonly _proxy: MainThreadEditorTabsShape
) { }
get apiObject() {
@@ -185,6 +183,19 @@ class ExtHostTabGroups {
return undefined;
}
return that._tabGroups.find(candidate => candidate.groupId === activeTabGroupId)?.apiObject;
},
close: async (tab: vscode.Tab | vscode.Tab[], preserveFocus?: boolean) => {
const tabs = Array.isArray(tab) ? tab : [tab];
const extHostTabIds: string[] = [];
for (const tab of tabs) {
const extHostTab = this.findExtHostTabFromApi(tab);
if (!extHostTab) {
throw new Error('Tab close: Invalid tab not found!');
}
extHostTabIds.push(extHostTab.tabId);
}
this._proxy.$closeTab(extHostTabIds, preserveFocus);
return;
}
});
}
@@ -194,6 +205,17 @@ class ExtHostTabGroups {
acceptTabGroups(groups: ExtHostEditorTabGroup[]) {
this._tabGroups = groups;
}
private findExtHostTabFromApi(apiTab: vscode.Tab): ExtHostEditorTab | undefined {
for (const group of this._tabGroups) {
for (const tab of group.tabs) {
if (tab.apiObject === apiTab) {
return tab;
}
}
}
return;
}
}
export class ExtHostEditorTabs implements IExtHostEditorTabs {
@@ -212,7 +234,7 @@ export class ExtHostEditorTabs implements IExtHostEditorTabs {
constructor(@IExtHostRpcService extHostRpc: IExtHostRpcService) {
this._proxy = extHostRpc.getProxy(MainContext.MainThreadEditorTabs);
this._tabGroups = new ExtHostTabGroups(this._onDidChangeTabGroup.event, this._onDidChangeActiveTabGroup.event, () => this.activeGroupIdGetter());
this._tabGroups = new ExtHostTabGroups(this._onDidChangeTabGroup.event, this._onDidChangeActiveTabGroup.event, () => this.activeGroupIdGetter(), this._proxy);
}
get tabGroups(): vscode.TabGroups {
@@ -343,4 +343,45 @@ suite('ExtHostEditorTabs', function () {
extHostEditorTabs.tabGroups.onDidChangeTabGroup = undefined;
});
});
test('Ensure close is called with all tab ids', function () {
let closedTabIds: string[][] = [];
const extHostEditorTabs = new ExtHostEditorTabs(
SingleProxyRPCProtocol(new class extends mock<MainThreadEditorTabsShape>() {
// override/implement $moveTab or $closeTab
override async $closeTab(tabIds: string[], preserveFocus?: boolean) {
closedTabIds.push(tabIds);
}
})
);
const tab: IEditorTabDto = {
id: 'uniquestring',
isActive: true,
isDirty: true,
isPinned: true,
label: 'label1',
resource: URI.parse('file://abc/def.txt'),
editorId: 'default',
viewColumn: 0,
additionalResourcesAndViewTypes: [],
kind: TabKind.Singular
};
extHostEditorTabs.$acceptEditorTabModel([{
isActive: true,
viewColumn: 0,
groupId: 12,
tabs: [tab]
}]);
assert.strictEqual(extHostEditorTabs.tabGroups.groups.length, 1);
const activeTab = extHostEditorTabs.tabGroups.activeTabGroup?.activeTab;
assert.ok(activeTab);
extHostEditorTabs.tabGroups.close(activeTab, false);
assert.strictEqual(closedTabIds.length, 1);
assert.deepStrictEqual(closedTabIds[0], ['uniquestring']);
// Close with array
extHostEditorTabs.tabGroups.close([activeTab], false);
assert.strictEqual(closedTabIds.length, 2);
assert.deepStrictEqual(closedTabIds[1], ['uniquestring']);
});
});
+32 -31
View File
@@ -81,14 +81,6 @@ declare module 'vscode' {
*/
// TODO@API move into TabGroups
move(index: number, viewColumn: ViewColumn): Thenable<void>;
/**
* Closes the tab. This makes the tab object invalid and the tab
* should no longer be used for further actions.
* @param preserveFocus When `true` focus will remain in its current position. If `false` it will jump to the next tab.
*/
// TODO@API move into TabGroups, support one or many tabs or tab groups
close(preserveFocus: boolean): Thenable<void>;
}
export namespace window {
@@ -98,29 +90,6 @@ declare module 'vscode' {
export const tabGroups: TabGroups;
}
export interface TabGroups {
/**
* All the groups within the group container
*/
readonly groups: readonly TabGroup[];
/**
* The currently active group
*/
readonly activeTabGroup: TabGroup | undefined;
/**
* An {@link Event} which fires when a group changes.
*/
readonly onDidChangeTabGroup: Event<void>;
/**
* An {@link Event} which fires when the active group changes.
* Whether it be which group is active or its properties.
*/
readonly onDidChangeActiveTabGroup: Event<TabGroup | undefined>;
}
export interface TabGroup {
/**
* Whether or not the group is currently active
@@ -142,4 +111,36 @@ declare module 'vscode' {
*/
readonly tabs: Tab[];
}
export interface TabGroups {
/**
* All the groups within the group container
*/
readonly groups: readonly TabGroup[];
/**
* The currently active group
*/
readonly activeTabGroup: TabGroup | undefined;
/**
* An {@link Event} which fires when a group changes.
*/
readonly onDidChangeTabGroup: Event<void>;
/**
* An {@link Event} which fires when the active group changes.
* Whether it be which group is active or its properties.
*/
readonly onDidChangeActiveTabGroup: Event<TabGroup | undefined>;
/**
* Closes the tab. This makes the tab object invalid and the tab
* should no longer be used for further actions.
* @param tab The tab to close, must be reference equal to a tab given by the API
* @param preserveFocus When `true` focus will remain in its current position. If `false` it will jump to the next tab.
*/
close(tab: Tab[], preserveFocus?: boolean): Thenable<void>;
close(tab: Tab, preserveFocus?: boolean): Thenable<void>;
}
}