mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 09:08:48 +01:00
Move close and add array support
This commit is contained in:
@@ -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
@@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user