Skip to content

Commit 14d0156

Browse files
authored
Aux window: support to restore aux windows and editors (fix #195887) (#201291)
1 parent 01b8428 commit 14d0156

File tree

21 files changed

+304
-92
lines changed

21 files changed

+304
-92
lines changed

src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { BrowserWindow, WebContents } from 'electron';
77
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
88
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
9+
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
910
import { ILogService } from 'vs/platform/log/common/log';
1011
import { IStateService } from 'vs/platform/state/node/state';
1112
import { IBaseWindow } from 'vs/platform/window/electron-main/window';
@@ -33,7 +34,8 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow {
3334
@IEnvironmentMainService environmentMainService: IEnvironmentMainService,
3435
@ILogService private readonly logService: ILogService,
3536
@IConfigurationService configurationService: IConfigurationService,
36-
@IStateService stateService: IStateService
37+
@IStateService stateService: IStateService,
38+
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService
3739
) {
3840
super(configurationService, stateService, environmentMainService);
3941

@@ -61,6 +63,9 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow {
6163

6264
// Disable Menu
6365
window.setMenu(null);
66+
67+
// Lifecycle
68+
this.lifecycleMainService.registerAuxWindow(this);
6469
}
6570
}
6671
}

src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { IStateService } from 'vs/platform/state/node/state';
1818
import { ICodeWindow, LoadReason, UnloadReason } from 'vs/platform/window/electron-main/window';
1919
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
2020
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
21+
import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow';
2122

2223
export const ILifecycleMainService = createDecorator<ILifecycleMainService>('lifecycleMainService');
2324

@@ -131,6 +132,11 @@ export interface ILifecycleMainService {
131132
*/
132133
registerWindow(window: ICodeWindow): void;
133134

135+
/**
136+
* Make a `IAuxiliaryWindow` known to the lifecycle main service.
137+
*/
138+
registerAuxWindow(auxWindow: IAuxiliaryWindow): void;
139+
134140
/**
135141
* Reload a window. All lifecycle event handlers are triggered.
136142
*/
@@ -472,6 +478,34 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe
472478
});
473479
}
474480

481+
registerAuxWindow(auxWindow: IAuxiliaryWindow): void {
482+
const win = assertIsDefined(auxWindow.win);
483+
484+
win.on('close', e => {
485+
this.trace(`Lifecycle#auxWindow.on('close') - window ID ${auxWindow.id}`);
486+
487+
if (this._quitRequested) {
488+
this.trace(`Lifecycle#auxWindow.on('close') - preventDefault() because quit requested`);
489+
490+
// When quit is requested, Electron will close all
491+
// auxiliary windows before closing the main windows.
492+
// This prevents us from storing the auxiliary window
493+
// state on shutdown and thus we prevent closing if
494+
// quit is requested.
495+
//
496+
// Interestingly, this will not prevent the application
497+
// from quitting because the auxiliary windows will still
498+
// close once the owning window closes.
499+
500+
e.preventDefault();
501+
}
502+
});
503+
504+
win.on('closed', () => {
505+
this.trace(`Lifecycle#auxWindow.on('closed') - window ID ${auxWindow.id}`);
506+
});
507+
}
508+
475509
async reload(window: ICodeWindow, cli?: NativeParsedArgs): Promise<void> {
476510

477511
// Only reload when the window has not vetoed this

src/vs/platform/test/electron-main/workbenchTestServices.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { Promises } from 'vs/base/common/async';
77
import { Event, Emitter } from 'vs/base/common/event';
8+
import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow';
89
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
910
import { ILifecycleMainService, IRelaunchHandler, LifecycleMainPhase, ShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
1011
import { IStateService } from 'vs/platform/state/node/state';
@@ -41,6 +42,7 @@ export class TestLifecycleMainService implements ILifecycleMainService {
4142
phase = LifecycleMainPhase.Ready;
4243

4344
registerWindow(window: ICodeWindow): void { }
45+
registerAuxWindow(auxWindow: IAuxiliaryWindow): void { }
4446
async reload(window: ICodeWindow, cli?: NativeParsedArgs): Promise<void> { }
4547
async unload(window: ICodeWindow, reason: UnloadReason): Promise<boolean> { return true; }
4648
setRelaunchHandler(handler: IRelaunchHandler): void { }

src/vs/workbench/api/browser/mainThreadEditorTabs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export class MainThreadEditorTabs implements MainThreadEditorTabsShape {
7070
this._dispoables.add(this._editorGroupsService.onDidRemoveGroup(() => this._createTabsModel()));
7171

7272
// Once everything is read go ahead and initialize the model
73-
this._editorGroupsService.mainPart.whenReady.then(() => this._createTabsModel());
73+
this._editorGroupsService.whenReady.then(() => this._createTabsModel());
7474
}
7575

7676
dispose(): void {

src/vs/workbench/browser/actions/developerActions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c
3939
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
4040
import product from 'vs/platform/product/common/product';
4141
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
42+
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
4243

4344
class InspectContextKeysAction extends Action2 {
4445

@@ -467,6 +468,7 @@ class RemoveLargeStorageEntriesAction extends Action2 {
467468
const quickInputService = accessor.get(IQuickInputService);
468469
const userDataProfileService = accessor.get(IUserDataProfileService);
469470
const dialogService = accessor.get(IDialogService);
471+
const environmentService = accessor.get(IEnvironmentService);
470472

471473
interface IStorageItem extends IQuickPickItem {
472474
readonly key: string;
@@ -485,7 +487,7 @@ class RemoveLargeStorageEntriesAction extends Action2 {
485487
for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) {
486488
for (const key of storageService.keys(scope, target)) {
487489
const value = storageService.get(key, scope);
488-
if (value && value.length > RemoveLargeStorageEntriesAction.SIZE_THRESHOLD) {
490+
if (value && (!environmentService.isBuilt /* show all keys in dev */ || value.length > RemoveLargeStorageEntriesAction.SIZE_THRESHOLD)) {
489491
items.push({
490492
key,
491493
scope,

src/vs/workbench/browser/contextkeys.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ export class WorkbenchContextKeysHandler extends Disposable {
227227
}
228228

229229
private registerListeners(): void {
230-
this.editorGroupService.mainPart.whenReady.then(() => {
230+
this.editorGroupService.whenReady.then(() => {
231231
this.updateEditorAreaContextKeys();
232232
this.updateEditorContextKeys();
233233
});

src/vs/workbench/browser/layout.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
333333

334334
// Wait to register these listeners after the editor group service
335335
// is ready to avoid conflicts on startup
336-
this.editorGroupService.mainPart.whenRestored.then(() => {
336+
this.editorGroupService.whenRestored.then(() => {
337337

338338
// Restore main editor part on any editor change in main part
339339
this._register(this.mainPartEditorService.onDidVisibleEditorsChange(showEditorIfHidden));
@@ -494,7 +494,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
494494
this.updateMenubarVisibility(!!skipLayout);
495495

496496
// Centered Layout
497-
this.editorGroupService.mainPart.whenRestored.then(() => {
497+
this.editorGroupService.whenRestored.then(() => {
498498
this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED), skipLayout);
499499
});
500500
}
@@ -781,7 +781,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
781781

782782
// Empty workbench configured to open untitled file if empty
783783
else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && this.configurationService.getValue('workbench.startupEditor') === 'newUntitledFile') {
784-
if (this.editorGroupService.mainPart.hasRestorableState) {
784+
if (this.editorGroupService.hasRestorableState) {
785785
return []; // do not open any empty untitled file if we restored groups/editors from previous session
786786
}
787787

@@ -854,7 +854,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
854854
mark('code/willRestoreEditors');
855855

856856
// first ensure the editor part is ready
857-
await this.editorGroupService.mainPart.whenReady;
857+
await this.editorGroupService.whenReady;
858858
mark('code/restoreEditors/editorGroupsReady');
859859

860860
// apply editor layout if any
@@ -910,7 +910,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
910910
layoutRestoredPromises.push(
911911
Promise.all([
912912
openEditorsPromise?.finally(() => mark('code/restoreEditors/editorsOpened')),
913-
this.editorGroupService.mainPart.whenRestored.finally(() => mark('code/restoreEditors/editorGroupsRestored'))
913+
this.editorGroupService.whenRestored.finally(() => mark('code/restoreEditors/editorGroupsRestored'))
914914
]).finally(() => {
915915
// the `code/didRestoreEditors` perf mark is specifically
916916
// for when visible editors have resolved, so we only mark

src/vs/workbench/browser/part.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { ISerializableView, IViewSize } from 'vs/base/browser/ui/grid/grid';
1212
import { Event, Emitter } from 'vs/base/common/event';
1313
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
1414
import { assertIsDefined } from 'vs/base/common/types';
15-
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
15+
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
1616

1717
export interface IPartOptions {
1818
readonly hasTitle?: boolean;
@@ -187,7 +187,7 @@ export interface IMultiWindowPart {
187187
readonly element: HTMLElement;
188188
}
189189

190-
export abstract class MultiWindowParts<T extends IMultiWindowPart> extends Disposable {
190+
export abstract class MultiWindowParts<T extends IMultiWindowPart> extends Component {
191191

192192
protected readonly _parts = new Set<T>();
193193
get parts() { return Array.from(this._parts); }

src/vs/workbench/browser/parts/editor/auxiliaryEditorPart.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
1515
import { IThemeService } from 'vs/platform/theme/common/themeService';
1616
import { getTitleBarStyle } from 'vs/platform/window/common/window';
1717
import { IEditorGroupView, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor';
18-
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
18+
import { EditorPart, IEditorPartUIState } from 'vs/workbench/browser/parts/editor/editorPart';
1919
import { IAuxiliaryTitlebarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart';
2020
import { WindowTitle } from 'vs/workbench/browser/parts/titlebar/windowTitle';
2121
import { IAuxiliaryWindowOpenOptions, IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService';
@@ -27,6 +27,16 @@ import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecy
2727
import { IStatusbarService } from 'vs/workbench/services/statusbar/browser/statusbar';
2828
import { ITitleService } from 'vs/workbench/services/title/browser/titleService';
2929

30+
export interface IAuxiliaryEditorPartOpenOptions extends IAuxiliaryWindowOpenOptions {
31+
readonly state?: IEditorPartUIState;
32+
}
33+
34+
export interface ICreateAuxiliaryEditorPartResult {
35+
readonly part: AuxiliaryEditorPartImpl;
36+
readonly instantiationService: IInstantiationService;
37+
readonly disposables: DisposableStore;
38+
}
39+
3040
export class AuxiliaryEditorPart {
3141

3242
private static STATUS_BAR_VISIBILITY = 'workbench.statusBar.visible';
@@ -44,7 +54,7 @@ export class AuxiliaryEditorPart {
4454
) {
4555
}
4656

47-
async create(label: string, options?: IAuxiliaryWindowOpenOptions): Promise<{ readonly part: AuxiliaryEditorPartImpl; readonly instantiationService: IInstantiationService; readonly disposables: DisposableStore }> {
57+
async create(label: string, options?: IAuxiliaryEditorPartOpenOptions): Promise<ICreateAuxiliaryEditorPartResult> {
4858

4959
function computeEditorPartHeightOffset(): number {
5060
let editorPartHeightOffset = 0;
@@ -90,9 +100,9 @@ export class AuxiliaryEditorPart {
90100
editorPartContainer.style.position = 'relative';
91101
auxiliaryWindow.container.appendChild(editorPartContainer);
92102

93-
const editorPart = disposables.add(this.instantiationService.createInstance(AuxiliaryEditorPartImpl, auxiliaryWindow.window.vscodeWindowId, this.editorPartsView, label));
103+
const editorPart = disposables.add(this.instantiationService.createInstance(AuxiliaryEditorPartImpl, auxiliaryWindow.window.vscodeWindowId, this.editorPartsView, options?.state, label));
94104
disposables.add(this.editorPartsView.registerPart(editorPart));
95-
editorPart.create(editorPartContainer, { restorePreviousState: false });
105+
editorPart.create(editorPartContainer);
96106

97107
// Titlebar
98108
let titlebarPart: IAuxiliaryTitlebarPart | undefined = undefined;
@@ -140,7 +150,7 @@ export class AuxiliaryEditorPart {
140150

141151
// Lifecycle
142152
const editorCloseListener = disposables.add(Event.once(editorPart.onWillClose)(() => auxiliaryWindow.window.close()));
143-
disposables.add(Event.once(auxiliaryWindow.onWillClose)(() => {
153+
disposables.add(Event.once(auxiliaryWindow.onUnload)(() => {
144154
if (disposables.isDisposed) {
145155
return; // the close happened as part of an earlier dispose call
146156
}
@@ -187,6 +197,7 @@ class AuxiliaryEditorPartImpl extends EditorPart implements IAuxiliaryEditorPart
187197
constructor(
188198
readonly windowId: number,
189199
editorPartsView: IEditorPartsView,
200+
private readonly state: IEditorPartUIState | undefined,
190201
groupsLabel: string,
191202
@IInstantiationService instantiationService: IInstantiationService,
192203
@IThemeService themeService: IThemeService,
@@ -231,8 +242,12 @@ class AuxiliaryEditorPartImpl extends EditorPart implements IAuxiliaryEditorPart
231242
this.doClose(false /* do not merge any groups to main part */);
232243
}
233244

245+
protected override loadState(): IEditorPartUIState | undefined {
246+
return this.state;
247+
}
248+
234249
protected override saveState(): void {
235-
return; // TODO support auxiliary editor state
250+
return; // disabled, auxiliary editor part state is tracked outside
236251
}
237252

238253
close(): void {

src/vs/workbench/browser/parts/editor/editorGroupView.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -507,13 +507,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
507507

508508
options.pinned = this.model.isPinned(activeEditor); // preserve pinned state
509509
options.sticky = this.model.isSticky(activeEditor); // preserve sticky state
510-
options.preserveFocus = true; // handle focus after editor is opened
510+
options.preserveFocus = true; // handle focus after editor is restored
511+
512+
const internalOptions: IInternalEditorOpenOptions = {
513+
preserveWindowOrder: true // handle window order after editor is restored
514+
};
511515

512516
const activeElement = getActiveElement();
513517

514518
// Show active editor (intentionally not using async to keep
515519
// `restoreEditors` from executing in same stack)
516-
return this.doShowEditor(activeEditor, { active: true, isNew: false /* restored */ }, options).then(() => {
520+
return this.doShowEditor(activeEditor, { active: true, isNew: false /* restored */ }, options, internalOptions).then(() => {
517521

518522
// Set focused now if this is the active group and focus has
519523
// not changed meanwhile. This prevents focus from being

0 commit comments

Comments
 (0)