diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts
index aaf950b4c1fd1..13ff65cfb4eb6 100644
--- a/src/vs/base/browser/dom.ts
+++ b/src/vs/base/browser/dom.ts
@@ -12,10 +12,11 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import * as event from 'vs/base/common/event';
import * as dompurify from 'vs/base/browser/dompurify/dompurify';
import { KeyCode } from 'vs/base/common/keyCodes';
-import { Disposable, DisposableStore, IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
+import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { FileAccess, RemoteAuthorities, Schemas } from 'vs/base/common/network';
import * as platform from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
+import { hash } from 'vs/base/common/hash';
export const { registerWindow, getWindows, getWindowsCount, onDidRegisterWindow, onWillUnregisterWindow, onDidUnregisterWindow } = (function () {
const windows = new Set([window]);
@@ -749,9 +750,14 @@ export function isActiveDocument(element: Element): boolean {
/**
* Returns the active document across all child windows.
- * Use this instead of `document` when reacting to dom events to handle multiple windows.
+ * Use this instead of `document` when reacting to dom
+ * events to handle multiple windows.
*/
export function getActiveDocument(): Document {
+ if (getWindowsCount() <= 1) {
+ return document;
+ }
+
const documents = Array.from(getWindows()).map(window => window.document);
return documents.find(doc => doc.hasFocus()) ?? document;
}
@@ -785,44 +791,47 @@ export function focusWindow(element: Node): void {
}
}
-const globalStylesheets = new Map>();
-
-export function createStyleSheet(container: HTMLElement = document.head, beforeAppend?: (style: HTMLStyleElement) => void): HTMLStyleElement {
+export function createStyleSheet(container: HTMLElement = document.head, beforeAppend?: (style: HTMLStyleElement) => void, disposableStore?: DisposableStore): HTMLStyleElement {
const style = document.createElement('style');
style.type = 'text/css';
style.media = 'screen';
beforeAppend?.(style);
container.appendChild(style);
+ if (disposableStore) {
+ disposableStore.add(toDisposable(() => container.removeChild(style)));
+ }
+
// With
as container, the stylesheet becomes global and is tracked
// to support auxiliary windows to clone the stylesheet.
if (container === document.head) {
- const clonedGlobalStylesheets = new Set();
- globalStylesheets.set(style, clonedGlobalStylesheets);
-
for (const targetWindow of getWindows()) {
if (targetWindow === window) {
continue; // main window is already tracked
}
- const disposable = cloneGlobalStyleSheet(style, targetWindow);
+ const cloneDisposable = cloneGlobalStyleSheet(style, targetWindow);
+ disposableStore?.add(cloneDisposable);
- event.Event.once(onDidUnregisterWindow)(unregisteredWindow => {
+ disposableStore?.add(event.Event.once(onDidUnregisterWindow)(unregisteredWindow => {
if (unregisteredWindow === targetWindow) {
- disposable.dispose();
+ cloneDisposable.dispose();
}
- });
+ }));
}
+
}
return style;
}
+const globalStylesheets = new Map>();
+
export function isGlobalStylesheet(node: Node): boolean {
return globalStylesheets.has(node as HTMLStyleElement);
}
-export function cloneGlobalStylesheets(targetWindow: Window & typeof globalThis): IDisposable {
+export function cloneGlobalStylesheets(targetWindow: Window): IDisposable {
const disposables = new DisposableStore();
for (const [globalStylesheet] of globalStylesheets) {
@@ -832,29 +841,85 @@ export function cloneGlobalStylesheets(targetWindow: Window & typeof globalThis)
return disposables;
}
-function cloneGlobalStyleSheet(globalStylesheet: HTMLStyleElement, targetWindow: Window & typeof globalThis): IDisposable {
+function cloneGlobalStyleSheet(globalStylesheet: HTMLStyleElement, targetWindow: Window): IDisposable {
+ const disposables = new DisposableStore();
+
const clone = globalStylesheet.cloneNode(true) as HTMLStyleElement;
targetWindow.document.head.appendChild(clone);
+ disposables.add(toDisposable(() => targetWindow.document.head.removeChild(clone)));
for (const rule of getDynamicStyleSheetRules(globalStylesheet)) {
clone.sheet?.insertRule(rule.cssText, clone.sheet?.cssRules.length);
}
- const observer = new MutationObserver(() => {
+ disposables.add(sharedMutationObserver.observe(globalStylesheet, disposables, { childList: true })(() => {
clone.textContent = globalStylesheet.textContent;
- });
- observer.observe(globalStylesheet, { childList: true });
+ }));
- globalStylesheets.get(globalStylesheet)?.add(clone);
+ let clonedGlobalStylesheets = globalStylesheets.get(globalStylesheet);
+ if (!clonedGlobalStylesheets) {
+ clonedGlobalStylesheets = new Set();
+ globalStylesheets.set(globalStylesheet, clonedGlobalStylesheets);
+ }
+ clonedGlobalStylesheets.add(clone);
+ disposables.add(toDisposable(() => clonedGlobalStylesheets?.delete(clone)));
- return toDisposable(() => {
- observer.disconnect();
- targetWindow.document.head.removeChild(clone);
+ return disposables;
+}
- globalStylesheets.get(globalStylesheet)?.delete(clone);
- });
+interface IMutationObserver {
+ users: number;
+ readonly observer: MutationObserver;
+ readonly onDidMutate: event.Event;
}
+export const sharedMutationObserver = new class {
+
+ readonly mutationObservers = new Map>();
+
+ observe(target: Node, disposables: DisposableStore, options?: MutationObserverInit): event.Event {
+ let mutationObserversPerTarget = this.mutationObservers.get(target);
+ if (!mutationObserversPerTarget) {
+ mutationObserversPerTarget = new Map();
+ this.mutationObservers.set(target, mutationObserversPerTarget);
+ }
+
+ const optionsHash = hash(options);
+ let mutationObserverPerOptions = mutationObserversPerTarget.get(optionsHash);
+ if (!mutationObserverPerOptions) {
+ const onDidMutate = new event.Emitter();
+ const observer = new MutationObserver(mutations => onDidMutate.fire(mutations));
+ observer.observe(target, options);
+
+ const resolvedMutationObserverPerOptions = mutationObserverPerOptions = {
+ users: 1,
+ observer,
+ onDidMutate: onDidMutate.event
+ };
+
+ disposables.add(toDisposable(() => {
+ resolvedMutationObserverPerOptions.users -= 1;
+
+ if (resolvedMutationObserverPerOptions.users === 0) {
+ onDidMutate.dispose();
+ observer.disconnect();
+
+ mutationObserversPerTarget?.delete(optionsHash);
+ if (mutationObserversPerTarget?.size === 0) {
+ this.mutationObservers.delete(target);
+ }
+ }
+ }));
+
+ mutationObserversPerTarget.set(optionsHash, mutationObserverPerOptions);
+ } else {
+ mutationObserverPerOptions.users += 1;
+ }
+
+ return mutationObserverPerOptions.onDidMutate;
+ }
+};
+
export function createMetaElement(container: HTMLElement = document.head): HTMLMetaElement {
const meta = document.createElement('meta');
container.appendChild(meta);
@@ -2092,35 +2157,6 @@ function camelCaseToHyphenCase(str: string) {
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}
-interface IObserver extends IDisposable {
- readonly onDidChangeAttribute: event.Event;
-}
-
-function observeAttributes(element: Element, filter?: string[]): IObserver {
- const onDidChangeAttribute = new event.Emitter();
-
- const observer = new MutationObserver(mutations => {
- for (const mutation of mutations) {
- if (mutation.type === 'attributes' && mutation.attributeName) {
- onDidChangeAttribute.fire(mutation.attributeName);
- }
- }
- });
-
- observer.observe(element, {
- attributes: true,
- attributeFilter: filter
- });
-
- return {
- onDidChangeAttribute: onDidChangeAttribute.event,
- dispose: () => {
- observer.disconnect();
- onDidChangeAttribute.dispose();
- }
- };
-}
-
export function copyAttributes(from: Element, to: Element): void {
for (const { name, value } of from.attributes) {
to.setAttribute(name, value);
@@ -2139,10 +2175,15 @@ function copyAttribute(from: Element, to: Element, name: string): void {
export function trackAttributes(from: Element, to: Element, filter?: string[]): IDisposable {
copyAttributes(from, to);
- const observer = observeAttributes(from, filter);
+ const disposables = new DisposableStore();
- return combinedDisposable(
- observer,
- observer.onDidChangeAttribute(name => copyAttribute(from, to, name))
- );
+ disposables.add(sharedMutationObserver.observe(from, disposables, { attributes: true, attributeFilter: filter })(mutations => {
+ for (const mutation of mutations) {
+ if (mutation.type === 'attributes' && mutation.attributeName) {
+ copyAttribute(from, to, mutation.attributeName);
+ }
+ }
+ }));
+
+ return disposables;
}
diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts
index 5eef587493f58..6cb6877f8b394 100644
--- a/src/vs/code/electron-main/app.ts
+++ b/src/vs/code/electron-main/app.ts
@@ -449,8 +449,6 @@ export class CodeApplication extends Disposable {
//#region Bootstrap IPC Handlers
- validatedIpcMain.handle('vscode:getWindowId', event => Promise.resolve(event.sender.id));
-
validatedIpcMain.handle('vscode:fetchShellEnv', event => {
// Prefer to use the args and env from the target window
diff --git a/src/vs/code/node/sharedProcess/contrib/storageDataCleaner.ts b/src/vs/code/node/sharedProcess/contrib/storageDataCleaner.ts
index da67be66109e8..23af4d227b94f 100644
--- a/src/vs/code/node/sharedProcess/contrib/storageDataCleaner.ts
+++ b/src/vs/code/node/sharedProcess/contrib/storageDataCleaner.ts
@@ -52,7 +52,7 @@ export class UnusedWorkspaceStorageDataCleaner extends Disposable {
return; // keep workspace storage for empty extension development workspaces
}
- const windows = await this.nativeHostService.getWindows();
+ const windows = await this.nativeHostService.getWindows({ includeAuxiliaryWindows: false });
if (windows.some(window => window.workspace?.id === workspaceStorageFolder)) {
return; // keep workspace storage for empty workspaces opened as window
}
diff --git a/src/vs/editor/browser/editorDom.ts b/src/vs/editor/browser/editorDom.ts
index 6037946742616..e533d4d13ca0e 100644
--- a/src/vs/editor/browser/editorDom.ts
+++ b/src/vs/editor/browser/editorDom.ts
@@ -7,7 +7,7 @@ import * as dom from 'vs/base/browser/dom';
import { GlobalPointerMoveMonitor } from 'vs/base/browser/globalPointerMoveMonitor';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { RunOnceScheduler } from 'vs/base/common/async';
-import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
+import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { asCssVariable } from 'vs/platform/theme/common/colorRegistry';
import { ThemeColor } from 'vs/base/common/themables';
@@ -358,7 +358,8 @@ export interface CssProperties {
class RefCountedCssRule {
private _referenceCount: number = 0;
- private _styleElement: HTMLStyleElement;
+ private _styleElement: HTMLStyleElement | undefined;
+ private _styleElementDisposables: DisposableStore;
constructor(
public readonly key: string,
@@ -366,10 +367,8 @@ class RefCountedCssRule {
_containerElement: HTMLElement | undefined,
public readonly properties: CssProperties,
) {
- this._styleElement = dom.createStyleSheet(
- _containerElement
- );
-
+ this._styleElementDisposables = new DisposableStore();
+ this._styleElement = dom.createStyleSheet(_containerElement, undefined, this._styleElementDisposables);
this._styleElement.textContent = this.getCssText(this.className, this.properties);
}
@@ -392,7 +391,8 @@ class RefCountedCssRule {
}
public dispose(): void {
- this._styleElement.remove();
+ this._styleElementDisposables.dispose();
+ this._styleElement = undefined;
}
public increaseRefCount(): void {
diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts
index 1b1271068a4e5..a73b50a66830d 100644
--- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts
+++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts
@@ -15,15 +15,25 @@ export interface IAuxiliaryWindow {
readonly id: number;
readonly win: BrowserWindow | null;
+ readonly parentId: number;
+
readonly lastFocusTime: number;
focus(options?: { force: boolean }): void;
+
+ setRepresentedFilename(name: string): void;
+ getRepresentedFilename(): string | undefined;
+
+ setDocumentEdited(edited: boolean): void;
+ isDocumentEdited(): boolean;
}
export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow {
readonly id = this.contents.id;
+ parentId = -1;
+
private readonly _onDidClose = this._register(new Emitter());
readonly onDidClose = this._onDidClose.event;
diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts
index 983494e1cf6d7..aae6d0f62b4f9 100644
--- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts
+++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts
@@ -6,6 +6,7 @@
import { BrowserWindow, BrowserWindowConstructorOptions, WebContents, app } from 'electron';
import { Event } from 'vs/base/common/event';
import { FileAccess } from 'vs/base/common/network';
+import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain';
import { AuxiliaryWindow, IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow';
import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -26,7 +27,7 @@ export class AuxiliaryWindowsMainService implements IAuxiliaryWindowsMainService
private registerListeners(): void {
// We have to ensure that an auxiliary window gets to know its
- // parent `BrowserWindow` so that it can apply listeners to it
+ // containing `BrowserWindow` so that it can apply listeners to it
// Unfortunately we cannot rely on static `BrowserWindow` methods
// because we might call the methods too early before the window
// is created.
@@ -37,6 +38,15 @@ export class AuxiliaryWindowsMainService implements IAuxiliaryWindowsMainService
auxiliaryWindow.tryClaimWindow();
}
});
+
+ validatedIpcMain.handle('vscode:registerAuxiliaryWindow', async (event, mainWindowId: number) => {
+ const auxiliaryWindow = this.getWindowById(event.sender.id);
+ if (auxiliaryWindow) {
+ auxiliaryWindow.parentId = mainWindowId;
+ }
+
+ return event.sender.id;
+ });
}
createWindow(): BrowserWindowConstructorOptions {
diff --git a/src/vs/platform/layout/browser/layoutService.ts b/src/vs/platform/layout/browser/layoutService.ts
index 74d4ef4b801ac..8ba84c5d9e532 100644
--- a/src/vs/platform/layout/browser/layoutService.ts
+++ b/src/vs/platform/layout/browser/layoutService.ts
@@ -92,7 +92,7 @@ export interface ILayoutService {
readonly activeContainerOffset: ILayoutOffsetInfo;
/**
- * Focus the primary component of the container.
+ * Focus the primary component of the active container.
*/
focus(): void;
}
diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts
index 0ef1220538968..ed5141b1d0a3e 100644
--- a/src/vs/platform/menubar/electron-main/menubar.ts
+++ b/src/vs/platform/menubar/electron-main/menubar.ts
@@ -49,7 +49,7 @@ export class Menubar {
private willShutdown: boolean | undefined;
private appMenuInstalled: boolean | undefined;
private closedLastWindow: boolean;
- private noActiveWindow: boolean;
+ private noActiveMainWindow: boolean;
private menuUpdater: RunOnceScheduler;
private menuGC: RunOnceScheduler;
@@ -92,7 +92,7 @@ export class Menubar {
this.addFallbackHandlers();
this.closedLastWindow = false;
- this.noActiveWindow = false;
+ this.noActiveMainWindow = false;
this.oldMenus = [];
@@ -117,7 +117,11 @@ export class Menubar {
private addFallbackHandlers(): void {
// File Menu Items
- this.fallbackMenuHandlers['workbench.action.files.newUntitledFile'] = (menuItem, win, event) => this.windowsMainService.openEmptyWindow({ context: OpenContext.MENU, contextWindowId: win?.id });
+ this.fallbackMenuHandlers['workbench.action.files.newUntitledFile'] = (menuItem, win, event) => {
+ if (!this.runActionInRenderer({ type: 'commandId', commandId: 'workbench.action.files.newUntitledFile' })) { // this is one of the few supported actions when aux window has focus
+ this.windowsMainService.openEmptyWindow({ context: OpenContext.MENU, contextWindowId: win?.id });
+ }
+ };
this.fallbackMenuHandlers['workbench.action.newWindow'] = (menuItem, win, event) => this.windowsMainService.openEmptyWindow({ context: OpenContext.MENU, contextWindowId: win?.id });
this.fallbackMenuHandlers['workbench.action.files.openFileFolder'] = (menuItem, win, event) => this.nativeHostMainService.pickFileFolderAndOpen(undefined, { forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } });
this.fallbackMenuHandlers['workbench.action.files.openFolder'] = (menuItem, win, event) => this.nativeHostMainService.pickFolderAndOpen(undefined, { forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } });
@@ -242,7 +246,8 @@ export class Menubar {
return;
}
- this.noActiveWindow = !BrowserWindow.getFocusedWindow();
+ const focusedWindow = BrowserWindow.getFocusedWindow();
+ this.noActiveMainWindow = !focusedWindow || !!this.auxiliaryWindowsMainService.getWindowById(focusedWindow.id);
this.scheduleUpdateMenu();
}
@@ -377,8 +382,10 @@ export class Menubar {
Menu.setApplicationMenu(menu);
- for (const window of this.auxiliaryWindowsMainService.getWindows()) {
- window.win?.setMenu(null);
+ if (menu) {
+ for (const window of this.auxiliaryWindowsMainService.getWindows()) {
+ window.win?.setMenu(null);
+ }
}
}
@@ -470,12 +477,12 @@ export class Menubar {
case 'File':
case 'Help':
if (isMacintosh) {
- return (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) || (this.windowsMainService.getWindowCount() > 0 && this.noActiveWindow) || (!!this.menubarMenus && !!this.menubarMenus[menuId]);
+ return (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) || (this.windowsMainService.getWindowCount() > 0 && this.noActiveMainWindow) || (!!this.menubarMenus && !!this.menubarMenus[menuId]);
}
case 'Window':
if (isMacintosh) {
- return (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) || (this.windowsMainService.getWindowCount() > 0 && this.noActiveWindow) || !!this.menubarMenus;
+ return (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) || (this.windowsMainService.getWindowCount() > 0 && this.noActiveMainWindow) || !!this.menubarMenus;
}
default:
@@ -502,7 +509,7 @@ export class Menubar {
if (isMacintosh) {
if ((this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) ||
- (this.windowsMainService.getWindowCount() > 0 && this.noActiveWindow)) {
+ (this.windowsMainService.getWindowCount() > 0 && this.noActiveMainWindow)) {
// In the fallback scenario, we are either disabled or using a fallback handler
if (this.fallbackMenuHandlers[item.id]) {
menu.append(new MenuItem(this.likeAction(item.id, { label: this.mnemonicLabel(item.label), click: this.fallbackMenuHandlers[item.id] })));
@@ -747,13 +754,24 @@ export class Menubar {
};
}
- private runActionInRenderer(invocation: IMenuItemInvocation): void {
+ private runActionInRenderer(invocation: IMenuItemInvocation): boolean {
+
+ // We want to support auxililary windows that may have focus by
+ // returning their parent windows as target to support running
+ // actions via the main window.
+ let activeBrowserWindow = BrowserWindow.getFocusedWindow();
+ if (activeBrowserWindow) {
+ const auxiliaryWindowCandidate = this.auxiliaryWindowsMainService.getWindowById(activeBrowserWindow.id);
+ if (auxiliaryWindowCandidate) {
+ activeBrowserWindow = this.windowsMainService.getWindowById(auxiliaryWindowCandidate.parentId)?.win ?? null;
+ }
+ }
+
// We make sure to not run actions when the window has no focus, this helps
// for https://github.com/microsoft/vscode/issues/25907 and specifically for
// https://github.com/microsoft/vscode/issues/11928
// Still allow to run when the last active window is minimized though for
// https://github.com/microsoft/vscode/issues/63000
- let activeBrowserWindow = BrowserWindow.getFocusedWindow();
if (!activeBrowserWindow) {
const lastActiveWindow = this.windowsMainService.getLastActiveWindow();
if (lastActiveWindow?.isMinimized()) {
@@ -770,7 +788,7 @@ export class Menubar {
// prevent this action from running twice on macOS (https://github.com/microsoft/vscode/issues/62719)
// we already register a keybinding in bootstrap-window.js for opening developer tools in case something
// goes wrong and that keybinding is only removed when the application has loaded (= window ready).
- return;
+ return false;
}
}
@@ -781,8 +799,12 @@ export class Menubar {
const runKeybindingPayload: INativeRunKeybindingInWindowRequest = { userSettingsLabel: invocation.userSettingsLabel };
activeWindow.sendWhenReady('vscode:runKeybinding', CancellationToken.None, runKeybindingPayload);
}
+
+ return true;
} else {
this.logService.trace('menubar#runActionInRenderer: no active window found', invocation);
+
+ return false;
}
}
diff --git a/src/vs/platform/native/common/native.ts b/src/vs/platform/native/common/native.ts
index 6b2d0f538b494..59218bc0fe846 100644
--- a/src/vs/platform/native/common/native.ts
+++ b/src/vs/platform/native/common/native.ts
@@ -12,7 +12,7 @@ import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IV8Profile } from 'vs/platform/profiling/common/profiling';
import { IPartsSplash } from 'vs/platform/theme/common/themeService';
-import { IColorScheme, IOpenedWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IRectangle, IWindowOpenable } from 'vs/platform/window/common/window';
+import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IRectangle, IWindowOpenable } from 'vs/platform/window/common/window';
export interface ICPUProperties {
model: string;
@@ -60,7 +60,8 @@ export interface ICommonNativeHostService {
readonly onDidTriggerSystemContextMenu: Event<{ windowId: number; x: number; y: number }>;
// Window
- getWindows(): Promise;
+ getWindows(options: { includeAuxiliaryWindows: true }): Promise>;
+ getWindows(options: { includeAuxiliaryWindows: false }): Promise>;
getWindowCount(): Promise;
getActiveWindowId(): Promise;
@@ -111,8 +112,8 @@ export interface ICommonNativeHostService {
// OS
showItemInFolder(path: string): Promise;
- setRepresentedFilename(path: string): Promise;
- setDocumentEdited(edited: boolean): Promise;
+ setRepresentedFilename(path: string, options?: { targetWindowId?: number }): Promise;
+ setDocumentEdited(edited: boolean, options?: { targetWindowId?: number }): Promise;
openExternal(url: string): Promise;
moveItemToTrash(fullPath: string): Promise;
diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts
index 92855b5ae2441..43b3fd9591778 100644
--- a/src/vs/platform/native/electron-main/nativeHostMainService.ts
+++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts
@@ -33,7 +33,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { IPartsSplash } from 'vs/platform/theme/common/themeService';
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
import { ICodeWindow } from 'vs/platform/window/electron-main/window';
-import { IColorScheme, IOpenedWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IRectangle, IWindowOpenable } from 'vs/platform/window/common/window';
+import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IRectangle, IWindowOpenable } from 'vs/platform/window/common/window';
import { getFocusedOrLastActiveWindow, IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows';
import { isWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService';
@@ -112,16 +112,28 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
//#region Window
- async getWindows(): Promise {
- const windows = this.windowsMainService.getWindows();
-
- return windows.map(window => ({
+ getWindows(windowId: number | undefined, options: { includeAuxiliaryWindows: true }): Promise>;
+ getWindows(windowId: number | undefined, options: { includeAuxiliaryWindows: false }): Promise>;
+ async getWindows(windowId: number | undefined, options: { includeAuxiliaryWindows: boolean }): Promise> {
+ const mainWindows = this.windowsMainService.getWindows().map(window => ({
id: window.id,
workspace: window.openedWorkspace ?? toWorkspaceIdentifier(window.backupPath, window.isExtensionDevelopmentHost),
title: window.win?.getTitle() ?? '',
filename: window.getRepresentedFilename(),
dirty: window.isDocumentEdited()
}));
+
+ const auxiliaryWindows = [];
+ if (options.includeAuxiliaryWindows) {
+ auxiliaryWindows.push(...this.auxiliaryWindowsMainService.getWindows().map(window => ({
+ id: window.id,
+ parentId: window.parentId,
+ title: window.win?.getTitle() ?? '',
+ filename: window.getRepresentedFilename()
+ })));
+ }
+
+ return [...mainWindows, ...auxiliaryWindows];
}
async getWindowCount(windowId: number | undefined): Promise {
@@ -218,7 +230,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
}
async moveWindowTop(windowId: number | undefined, options?: { targetWindowId?: number }): Promise {
- const window = this.windowById(options?.targetWindowId) ?? this.codeWindowById(windowId);
+ const window = this.windowById(options?.targetWindowId, windowId);
if (window?.win) {
window.win.moveTop();
}
@@ -245,7 +257,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
}
async focusWindow(windowId: number | undefined, options?: { targetWindowId?: number; force?: boolean }): Promise {
- const window = this.windowById(options?.targetWindowId) ?? this.codeWindowById(windowId);
+ const window = this.windowById(options?.targetWindowId, windowId);
window?.focus({ force: options?.force ?? false });
}
@@ -439,13 +451,13 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
shell.showItemInFolder(path);
}
- async setRepresentedFilename(windowId: number | undefined, path: string): Promise {
- const window = this.codeWindowById(windowId);
+ async setRepresentedFilename(windowId: number | undefined, path: string, options?: { targetWindowId?: number }): Promise {
+ const window = this.windowById(options?.targetWindowId, windowId);
window?.setRepresentedFilename(path);
}
- async setDocumentEdited(windowId: number | undefined, edited: boolean): Promise {
- const window = this.codeWindowById(windowId);
+ async setDocumentEdited(windowId: number | undefined, edited: boolean, options?: { targetWindowId?: number }): Promise {
+ const window = this.windowById(options?.targetWindowId, windowId);
window?.setDocumentEdited(edited);
}
@@ -797,8 +809,8 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
//#endregion
- private windowById(windowId: number | undefined): ICodeWindow | IAuxiliaryWindow | undefined {
- return this.codeWindowById(windowId) ?? this.auxiliaryWindowById(windowId);
+ private windowById(windowId: number | undefined, fallbackCodeWindowId?: number): ICodeWindow | IAuxiliaryWindow | undefined {
+ return this.codeWindowById(windowId) ?? this.auxiliaryWindowById(windowId) ?? this.codeWindowById(fallbackCodeWindowId);
}
private codeWindowById(windowId: number | undefined): ICodeWindow | undefined {
diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts
index 4e7a1872b0eac..442c421ef5ef3 100644
--- a/src/vs/platform/window/common/window.ts
+++ b/src/vs/platform/window/common/window.ts
@@ -70,14 +70,25 @@ export interface IAddFoldersRequest {
readonly foldersToAdd: UriComponents[];
}
-export interface IOpenedWindow {
+interface IOpenedWindow {
readonly id: number;
- readonly workspace?: IAnyWorkspaceIdentifier;
readonly title: string;
readonly filename?: string;
+}
+
+export interface IOpenedMainWindow extends IOpenedWindow {
+ readonly workspace?: IAnyWorkspaceIdentifier;
readonly dirty: boolean;
}
+export interface IOpenedAuxiliaryWindow extends IOpenedWindow {
+ readonly parentId: number;
+}
+
+export function isOpenedAuxiliaryWindow(candidate: IOpenedMainWindow | IOpenedAuxiliaryWindow): candidate is IOpenedAuxiliaryWindow {
+ return typeof (candidate as IOpenedAuxiliaryWindow).parentId === 'number';
+}
+
export interface IOpenEmptyWindowOptions extends IBaseOpenWindowsOptions { }
export type IWindowOpenable = IWorkspaceToOpen | IFolderToOpen | IFileToOpen;
diff --git a/src/vs/platform/window/electron-main/window.ts b/src/vs/platform/window/electron-main/window.ts
index 379bdd161f63e..ac0eda4c73e6d 100644
--- a/src/vs/platform/window/electron-main/window.ts
+++ b/src/vs/platform/window/electron-main/window.ts
@@ -15,6 +15,12 @@ import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platf
export interface IBaseWindow extends IDisposable {
focus(options?: { force: boolean }): void;
+
+ setRepresentedFilename(name: string): void;
+ getRepresentedFilename(): string | undefined;
+
+ setDocumentEdited(edited: boolean): void;
+ isDocumentEdited(): boolean;
}
export interface ICodeWindow extends IBaseWindow {
@@ -65,12 +71,6 @@ export interface ICodeWindow extends IBaseWindow {
isMinimized(): boolean;
- setRepresentedFilename(name: string): void;
- getRepresentedFilename(): string | undefined;
-
- setDocumentEdited(edited: boolean): void;
- isDocumentEdited(): boolean;
-
handleTitleDoubleClick(): void;
updateTouchBar(items: ISerializableCommandAction[][]): void;
diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts
index 3b87cdf60856d..2bdd624c092dc 100644
--- a/src/vs/platform/windows/electron-main/windowImpl.ts
+++ b/src/vs/platform/windows/electron-main/windowImpl.ts
@@ -86,6 +86,41 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow {
protected abstract getWin(): BrowserWindow | null;
+ private representedFilename: string | undefined;
+ private documentEdited: boolean | undefined;
+
+ setRepresentedFilename(filename: string): void {
+ if (isMacintosh) {
+ this.getWin()?.setRepresentedFilename(filename);
+ } else {
+ this.representedFilename = filename;
+ }
+ }
+
+ getRepresentedFilename(): string | undefined {
+ if (isMacintosh) {
+ return this.getWin()?.getRepresentedFilename();
+ }
+
+ return this.representedFilename;
+ }
+
+ setDocumentEdited(edited: boolean): void {
+ if (isMacintosh) {
+ this.getWin()?.setDocumentEdited(edited);
+ }
+
+ this.documentEdited = edited;
+ }
+
+ isDocumentEdited(): boolean {
+ if (isMacintosh) {
+ return Boolean(this.getWin()?.isDocumentEdited());
+ }
+
+ return !!this.documentEdited;
+ }
+
focus(options?: { force: boolean }): void {
if (isMacintosh && options?.force) {
app.focus({ steal: true });
@@ -182,9 +217,6 @@ export class CodeWindow extends BaseWindow implements ICodeWindow {
private transientIsNativeFullScreen: boolean | undefined = undefined;
private joinNativeFullScreenTransition: DeferredPromise | undefined = undefined;
- private representedFilename: string | undefined;
- private documentEdited: boolean | undefined;
-
private readonly hasWindowControlOverlay: boolean = false;
private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[] = [];
@@ -402,38 +434,6 @@ export class CodeWindow extends BaseWindow implements ICodeWindow {
this.registerListeners();
}
- setRepresentedFilename(filename: string): void {
- if (isMacintosh) {
- this._win.setRepresentedFilename(filename);
- } else {
- this.representedFilename = filename;
- }
- }
-
- getRepresentedFilename(): string | undefined {
- if (isMacintosh) {
- return this._win.getRepresentedFilename();
- }
-
- return this.representedFilename;
- }
-
- setDocumentEdited(edited: boolean): void {
- if (isMacintosh) {
- this._win.setDocumentEdited(edited);
- }
-
- this.documentEdited = edited;
- }
-
- isDocumentEdited(): boolean {
- if (isMacintosh) {
- return this._win.isDocumentEdited();
- }
-
- return !!this.documentEdited;
- }
-
private readyState = ReadyState.NONE;
setReady(): void {
diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts
index 89b2e26c6459c..a2f3527ee5045 100644
--- a/src/vs/platform/windows/electron-main/windowsMainService.ts
+++ b/src/vs/platform/windows/electron-main/windowsMainService.ts
@@ -54,6 +54,8 @@ import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataPro
import { IPolicyService } from 'vs/platform/policy/common/policy';
import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile';
import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService';
+import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows';
+import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow';
//#region Helper Interfaces
@@ -216,7 +218,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
@IDialogMainService private readonly dialogMainService: IDialogMainService,
@IFileService private readonly fileService: IFileService,
@IProtocolMainService private readonly protocolMainService: IProtocolMainService,
- @IThemeMainService private readonly themeMainService: IThemeMainService
+ @IThemeMainService private readonly themeMainService: IThemeMainService,
+ @IAuxiliaryWindowsMainService private readonly auxiliaryWindowsMainService: IAuxiliaryWindowsMainService
) {
super();
@@ -636,7 +639,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
private doOpenFilesInExistingWindow(configuration: IOpenConfiguration, window: ICodeWindow, filesToOpen?: IFilesToOpen): ICodeWindow {
this.logService.trace('windowsManager#doOpenFilesInExistingWindow', { filesToOpen });
- window.focus(); // make sure window has focus
+ this.focusMainOrChildWindow(window); // make sure window or any of the children has focus
const params: INativeOpenFileRequest = {
filesToOpenOrCreate: filesToOpen?.filesToOpenOrCreate,
@@ -650,6 +653,20 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return window;
}
+ private focusMainOrChildWindow(mainWindow: ICodeWindow): void {
+ let windowToFocus: ICodeWindow | IAuxiliaryWindow = mainWindow;
+
+ const focusedWindow = BrowserWindow.getFocusedWindow();
+ if (focusedWindow && focusedWindow.id !== mainWindow.id) {
+ const auxiliaryWindowCandidate = this.auxiliaryWindowsMainService.getWindowById(focusedWindow.id);
+ if (auxiliaryWindowCandidate && auxiliaryWindowCandidate.parentId === mainWindow.id) {
+ windowToFocus = auxiliaryWindowCandidate;
+ }
+ }
+
+ windowToFocus.focus();
+ }
+
private doAddFoldersToExistingWindow(window: ICodeWindow, foldersToAdd: URI[]): ICodeWindow {
this.logService.trace('windowsManager#doAddFoldersToExistingWindow', { foldersToAdd });
diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts
index 49144b6102d7e..18d160704a641 100644
--- a/src/vs/workbench/browser/actions/developerActions.ts
+++ b/src/vs/workbench/browser/actions/developerActions.ts
@@ -56,10 +56,7 @@ class InspectContextKeysAction extends Action2 {
const disposables = new DisposableStore();
- const stylesheet = createStyleSheet();
- disposables.add(toDisposable(() => {
- stylesheet.parentNode?.removeChild(stylesheet);
- }));
+ const stylesheet = createStyleSheet(undefined, undefined, disposables);
createCSSRule('*', 'cursor: crosshair !important;', stylesheet);
const hoverFeedback = document.createElement('div');
diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts
index 5a9796b18db1f..3a7ba7f708d4f 100644
--- a/src/vs/workbench/browser/dnd.ts
+++ b/src/vs/workbench/browser/dnd.ts
@@ -96,14 +96,14 @@ export class ResourcesDropHandler {
) {
}
- async handleDrop(event: DragEvent, resolveTargetGroup?: () => IEditorGroup | undefined, afterDrop?: (targetGroup: IEditorGroup | undefined) => void, options?: IEditorOptions): Promise {
+ async handleDrop(event: DragEvent, targetWindow: Window, resolveTargetGroup?: () => IEditorGroup | undefined, afterDrop?: (targetGroup: IEditorGroup | undefined) => void, options?: IEditorOptions): Promise {
const editors = await this.instantiationService.invokeFunction(accessor => extractEditorsAndFilesDropData(accessor, event));
if (!editors.length) {
return;
}
// Make the window active to handle the drop properly within
- await this.hostService.focus();
+ await this.hostService.focus(targetWindow);
// Check for workspace file / folder being dropped if we are allowed to do so
if (this.options.allowWorkspaceOpen) {
@@ -168,9 +168,6 @@ export class ResourcesDropHandler {
return false;
}
- // Pass focus to window
- this.hostService.focus();
-
// Open in separate windows if we drop workspaces or just one folder
if (toOpen.length > folderURIs.length || folderURIs.length === 1) {
await this.hostService.openWindow(toOpen);
diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts
index 1554dfade7bdf..19553fffaa94e 100644
--- a/src/vs/workbench/browser/layout.ts
+++ b/src/vs/workbench/browser/layout.ts
@@ -5,7 +5,7 @@
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
-import { EventType, addDisposableListener, getClientArea, Dimension, position, size, IDimension, isAncestorUsingFlowTo, computeScreenAwareSize, getActiveDocument, getWindows, getActiveWindow, focusWindow, isActiveDocument } from 'vs/base/browser/dom';
+import { EventType, addDisposableListener, getClientArea, Dimension, position, size, IDimension, isAncestorUsingFlowTo, computeScreenAwareSize, getActiveDocument, getWindows, getActiveWindow, focusWindow, isActiveDocument, getWindow } from 'vs/base/browser/dom';
import { onDidChangeFullscreen, isFullscreen, isWCOEnabled } from 'vs/base/browser/browser';
import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup';
import { isWindows, isLinux, isMacintosh, isWeb, isNative, isIOS } from 'vs/base/common/platform';
@@ -47,11 +47,12 @@ import { IBannerService } from 'vs/workbench/services/banner/browser/bannerServi
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { AuxiliaryBarPart } from 'vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
-import { IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService';
+import { IAuxiliaryWindowService, isAuxiliaryWindow } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService';
//#region Layout Implementation
interface ILayoutRuntimeState {
+ activeContainer: 'main' | number /* window ID */;
fullscreen: boolean;
maximized: boolean;
hasFocus: boolean;
@@ -451,10 +452,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
private onWindowFocusChanged(hasFocus: boolean): void {
if (hasFocus) {
- // This is a bit simplified: we assume that the active container
- // has changed when receiving focus, but we might end up with
- // the same active container as before...
- this._onDidChangeActiveContainer.fire();
+ const activeContainerId = this.getActiveContainerId();
+ if (this.state.runtime.activeContainer !== activeContainerId) {
+ this.state.runtime.activeContainer = activeContainerId;
+ this._onDidChangeActiveContainer.fire();
+ }
}
if (this.state.runtime.hasFocus !== hasFocus) {
@@ -463,6 +465,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
}
}
+ private getActiveContainerId(): 'main' | number {
+ const activeContainer = this.activeContainer;
+ if (activeContainer !== this.container) {
+ const containerWindow = getWindow(activeContainer);
+ if (isAuxiliaryWindow(containerWindow)) {
+ return containerWindow.vscodeWindowId;
+ }
+ }
+
+ return 'main';
+ }
+
private doUpdateLayoutConfiguration(skipLayout?: boolean): void {
// Menubar visibility
@@ -601,6 +615,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
// Layout Runtime State
const layoutRuntimeState: ILayoutRuntimeState = {
+ activeContainer: this.getActiveContainerId(),
fullscreen: isFullscreen(),
hasFocus: this.hostService.hasFocus,
maximized: false,
@@ -1215,7 +1230,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
}
focus(): void {
- this.focusPart(Parts.EDITOR_PART);
+ const activeContainer = this.activeContainer;
+ if (activeContainer === this.container) {
+ // main window
+ this.focusPart(Parts.EDITOR_PART);
+ } else {
+ // auxiliary window
+ this.editorGroupService.getPart(activeContainer)?.activeGroup.focus();
+ }
}
getDimension(part: Parts): Dimension | undefined {
diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts
index e37f6f54a1f16..de768f434cb7b 100644
--- a/src/vs/workbench/browser/parts/editor/editor.ts
+++ b/src/vs/workbench/browser/parts/editor/editor.ts
@@ -223,7 +223,7 @@ export interface IEditorGroupsView {
getGroup(identifier: GroupIdentifier): IEditorGroupView | undefined;
getGroups(order: GroupsOrder): IEditorGroupView[];
- activateGroup(identifier: IEditorGroupView | GroupIdentifier): IEditorGroupView;
+ activateGroup(identifier: IEditorGroupView | GroupIdentifier, preserveWindowOrder?: boolean): IEditorGroupView;
restoreGroup(identifier: IEditorGroupView | GroupIdentifier): IEditorGroupView;
addGroup(location: IEditorGroupView | GroupIdentifier, direction: GroupDirection, groupToCopy?: IEditorGroupView): IEditorGroupView;
@@ -232,7 +232,7 @@ export interface IEditorGroupsView {
moveGroup(group: IEditorGroupView | GroupIdentifier, location: IEditorGroupView | GroupIdentifier, direction: GroupDirection): IEditorGroupView;
copyGroup(group: IEditorGroupView | GroupIdentifier, location: IEditorGroupView | GroupIdentifier, direction: GroupDirection): IEditorGroupView;
- removeGroup(group: IEditorGroupView | GroupIdentifier): void;
+ removeGroup(group: IEditorGroupView | GroupIdentifier, preserveFocus?: boolean): void;
arrangeGroups(arrangement: GroupsArrangement, target?: IEditorGroupView | GroupIdentifier): void;
toggleMaximizeGroup(group?: IEditorGroupView | GroupIdentifier): void;
diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts
index 64221b9382dcd..646f5869387c3 100644
--- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts
+++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts
@@ -373,7 +373,7 @@ class DropOverlay extends Themable {
// Check for URI transfer
else {
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: !isWeb || isTemporaryWorkspace(this.contextService.getWorkspace()) });
- dropHandler.handleDrop(event, () => ensureTargetGroup(), targetGroup => targetGroup?.focus());
+ dropHandler.handleDrop(event, getWindow(this.groupView.element), () => ensureTargetGroup(), targetGroup => targetGroup?.focus());
}
}
diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts
index 30219f5b97db8..bc5cd6777e6b9 100644
--- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts
+++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts
@@ -680,12 +680,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Close all inactive editors first to prevent UI flicker
for (const inactiveEditor of inactiveEditors) {
- this.doCloseEditor(inactiveEditor, false);
+ this.doCloseEditor(inactiveEditor, true);
}
// Close active one last
if (activeEditor) {
- this.doCloseEditor(activeEditor, false);
+ this.doCloseEditor(activeEditor, true);
}
}
@@ -1108,8 +1108,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Without an editor pane, recover by closing the active editor
// (if the input is still the active one)
if (!pane && this.activeEditor === editor) {
- const focusNext = !options || !options.preserveFocus;
- this.doCloseEditor(editor, focusNext, { fromError: true });
+ this.doCloseEditor(editor, options?.preserveFocus, { fromError: true });
}
return pane;
@@ -1283,7 +1282,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// ...and a close afterwards (unless we copy)
if (!keepCopy) {
- this.doCloseEditor(editor, false /* do not focus next one behind if any */, { ...internalOptions, context: EditorCloseContext.MOVE });
+ this.doCloseEditor(editor, true /* do not focus next one behind if any */, { ...internalOptions, context: EditorCloseContext.MOVE });
}
}
@@ -1348,12 +1347,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
// Do close
- this.doCloseEditor(editor, options?.preserveFocus ? false : undefined, internalOptions);
+ this.doCloseEditor(editor, options?.preserveFocus, internalOptions);
return true;
}
- private doCloseEditor(editor: EditorInput, focusNext = (this.groupsView.activeGroup === this), internalOptions?: IInternalEditorCloseOptions): void {
+ private doCloseEditor(editor: EditorInput, preserveFocus = (this.groupsView.activeGroup !== this), internalOptions?: IInternalEditorCloseOptions): void {
// Forward to title control unless skipped via internal options
if (!internalOptions?.skipTitleUpdate) {
@@ -1362,7 +1361,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Closing the active editor of the group is a bit more work
if (this.model.isActive(editor)) {
- this.doCloseActiveEditor(focusNext, internalOptions);
+ this.doCloseActiveEditor(preserveFocus, internalOptions);
}
// Closing inactive editor is just a model update
@@ -1376,9 +1375,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
- private doCloseActiveEditor(focusNext = (this.groupsView.activeGroup === this), internalOptions?: IInternalEditorCloseOptions): void {
+ private doCloseActiveEditor(preserveFocus = (this.groupsView.activeGroup !== this), internalOptions?: IInternalEditorCloseOptions): void {
const editorToClose = this.activeEditor;
- const restoreFocus = this.shouldRestoreFocus(this.element);
+ const restoreFocus = !preserveFocus && this.shouldRestoreFocus(this.element);
// Optimization: if we are about to close the last editor in this group and settings
// are configured to close the group since it will be empty, we first set the last
@@ -1395,7 +1394,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
if (restoreFocus) {
nextActiveGroup.focus();
} else {
- this.groupsView.activateGroup(nextActiveGroup);
+ this.groupsView.activateGroup(nextActiveGroup, true);
}
}
}
@@ -1408,8 +1407,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Open next active if there are more to show
const nextActiveEditor = this.model.activeEditor;
if (nextActiveEditor) {
- const preserveFocus = !focusNext;
-
let activation: EditorActivation | undefined = undefined;
if (preserveFocus && this.groupsView.activeGroup !== this) {
// If we are opening the next editor in an inactive group
@@ -1458,7 +1455,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Remove empty group if we should
if (closeEmptyGroup) {
- this.groupsView.removeGroup(this);
+ this.groupsView.removeGroup(this, preserveFocus);
}
}
}
@@ -1723,7 +1720,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Close active editor last if contained in editors list to close
if (closeActiveEditor) {
- this.doCloseActiveEditor(options?.preserveFocus ? false : undefined);
+ this.doCloseActiveEditor(options?.preserveFocus);
}
// Forward to title control
@@ -1827,7 +1824,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
if (!editor.matches(replacement)) {
let closed = false;
if (forceReplaceDirty) {
- this.doCloseEditor(editor, false, { context: EditorCloseContext.REPLACE });
+ this.doCloseEditor(editor, true, { context: EditorCloseContext.REPLACE });
closed = true;
} else {
closed = await this.doCloseEditorWithConfirmationHandling(editor, { preserveFocus: true }, { context: EditorCloseContext.REPLACE });
@@ -1848,7 +1845,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Close replaced active editor unless they match
if (!activeReplacement.editor.matches(activeReplacement.replacement)) {
if (activeReplacement.forceReplaceDirty) {
- this.doCloseEditor(activeReplacement.editor, false, { context: EditorCloseContext.REPLACE });
+ this.doCloseEditor(activeReplacement.editor, true, { context: EditorCloseContext.REPLACE });
} else {
await this.doCloseEditorWithConfirmationHandling(activeReplacement.editor, { preserveFocus: true }, { context: EditorCloseContext.REPLACE });
}
diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts
index 5c4832ccd65c3..57e9e784a1b7f 100644
--- a/src/vs/workbench/browser/parts/editor/editorPart.ts
+++ b/src/vs/workbench/browser/parts/editor/editorPart.ts
@@ -14,7 +14,7 @@ import { IView, orthogonal, LayoutPriority, IViewSize, Direction, SerializableGr
import { GroupIdentifier, EditorInputWithOptions, IEditorPartOptions, IEditorPartOptionsChangeEvent, GroupModelChangeKind } from 'vs/workbench/common/editor';
import { EDITOR_GROUP_BORDER, EDITOR_PANE_BACKGROUND } from 'vs/workbench/common/theme';
import { distinct, coalesce, firstOrDefault } from 'vs/base/common/arrays';
-import { IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartCreationOptions, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor';
+import { IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartCreationOptions, IEditorPartsView, IEditorGroupsView } from 'vs/workbench/browser/parts/editor/editor';
import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
@@ -79,7 +79,7 @@ class GridWidgetView implements IView {
}
}
-export class EditorPart extends Part implements IEditorPart {
+export class EditorPart extends Part implements IEditorPart, IEditorGroupsView {
private static readonly EDITOR_PART_UI_STATE_STORAGE_KEY = 'editorpart.state';
private static readonly EDITOR_PART_CENTERED_VIEW_STORAGE_KEY = 'editorpart.centeredview';
@@ -337,10 +337,15 @@ export class EditorPart extends Part implements IEditorPart {
}
}
- activateGroup(group: IEditorGroupView | GroupIdentifier): IEditorGroupView {
+ activateGroup(group: IEditorGroupView | GroupIdentifier, preserveWindowOrder?: boolean): IEditorGroupView {
const groupView = this.assertGroupView(group);
this.doSetGroupActive(groupView);
+ // Ensure window on top unless disabled
+ if (!preserveWindowOrder) {
+ this.hostService.moveTop(getWindow(this.element));
+ }
+
return groupView;
}
@@ -667,10 +672,6 @@ export class EditorPart extends Part implements IEditorPart {
}
private doSetGroupActive(group: IEditorGroupView): void {
-
- // Ensure window on top
- this.hostService.moveTop(getWindow(this.element));
-
if (this._activeGroup !== group) {
const previousActiveGroup = this._activeGroup;
this._activeGroup = group;
@@ -743,7 +744,7 @@ export class EditorPart extends Part implements IEditorPart {
return fallback;
}
- removeGroup(group: IEditorGroupView | GroupIdentifier): void {
+ removeGroup(group: IEditorGroupView | GroupIdentifier, preserveFocus?: boolean): void {
const groupView = this.assertGroupView(group);
if (this.count === 1) {
return; // Cannot remove the last root group
@@ -751,7 +752,7 @@ export class EditorPart extends Part implements IEditorPart {
// Remove empty group
if (groupView.isEmpty) {
- return this.doRemoveEmptyGroup(groupView);
+ return this.doRemoveEmptyGroup(groupView, preserveFocus);
}
// Remove group with editors
@@ -773,14 +774,14 @@ export class EditorPart extends Part implements IEditorPart {
this.mergeGroup(groupView, lastActiveGroup);
}
- private doRemoveEmptyGroup(groupView: IEditorGroupView): void {
- const restoreFocus = this.shouldRestoreFocus(this.container);
+ private doRemoveEmptyGroup(groupView: IEditorGroupView, preserveFocus?: boolean): void {
+ const restoreFocus = !preserveFocus && this.shouldRestoreFocus(this.container);
// Activate next group if the removed one was active
if (this._activeGroup === groupView) {
const mostRecentlyActiveGroups = this.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
const nextActiveGroup = mostRecentlyActiveGroups[1]; // [0] will be the current group we are about to dispose
- this.activateGroup(nextActiveGroup);
+ this.doSetGroupActive(nextActiveGroup);
}
// Remove from grid widget & dispose
@@ -830,7 +831,7 @@ export class EditorPart extends Part implements IEditorPart {
else {
movedView = targetView.groupsView.addGroup(targetView, direction, sourceView);
sourceView.closeAllEditors();
- this.removeGroup(sourceView);
+ this.removeGroup(sourceView, restoreFocus);
}
// Restore focus if we had it previously after completing the grid
@@ -892,7 +893,7 @@ export class EditorPart extends Part implements IEditorPart {
// Remove source if the view is now empty and not already removed
if (sourceView.isEmpty && !sourceView.disposed /* could have been disposed already via workbench.editor.closeEmptyGroups setting */) {
- this.removeGroup(sourceView);
+ this.removeGroup(sourceView, true);
}
return targetView;
diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts
index a9ed03f97c231..834e2d666440c 100644
--- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts
+++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts
@@ -32,7 +32,7 @@ import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdenti
import { Color } from 'vs/base/common/color';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { MergeGroupMode, IMergeGroupOptions, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
-import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode, DragAndDropObserver, isMouseEvent } from 'vs/base/browser/dom';
+import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode, DragAndDropObserver, isMouseEvent, getWindow } from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
import { IEditorGroupsView, EditorServiceImpl, IEditorGroupView, IInternalEditorOpenOptions, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor';
import { CloseOneEditorAction, UnpinEditorAction } from 'vs/workbench/browser/parts/editor/editorActions';
@@ -2069,7 +2069,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
// Check for URI transfer
else {
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: false });
- dropHandler.handleDrop(e, () => this.groupView, () => this.groupView.focus(), options);
+ dropHandler.handleDrop(e, getWindow(this.titleContainer), () => this.groupView, () => this.groupView.focus(), options);
}
}
diff --git a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts
index 5e97cbebd8571..48b791514f2c0 100644
--- a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts
+++ b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts
@@ -54,7 +54,7 @@ export class WindowTitle extends Disposable {
private readonly editorService: IEditorService;
constructor(
- private readonly targetWindow: Window & typeof globalThis,
+ private readonly targetWindow: Window,
editorGroupsContainer: IEditorGroupsContainer | 'main',
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IEditorService editorService: IEditorService,
diff --git a/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts b/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts
index 9319c21af7f9f..013341c5f1818 100644
--- a/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts
+++ b/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts
@@ -5,7 +5,7 @@
import { createStyleSheet } from 'vs/base/browser/dom';
import { Event } from 'vs/base/common/event';
-import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
+import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { clamp } from 'vs/base/common/numbers';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
@@ -13,6 +13,7 @@ import { AccessibilityWorkbenchSettingId, ViewDimUnfocusedOpacityProperties } fr
export class UnfocusedViewDimmingContribution extends Disposable implements IWorkbenchContribution {
private _styleElement?: HTMLStyleElement;
+ private _styleElementDisposables: DisposableStore | undefined = undefined;
constructor(
@IConfigurationService configurationService: IConfigurationService,
@@ -74,14 +75,16 @@ export class UnfocusedViewDimmingContribution extends Disposable implements IWor
private _getStyleElement(): HTMLStyleElement {
if (!this._styleElement) {
- this._styleElement = createStyleSheet();
+ this._styleElementDisposables = new DisposableStore();
+ this._styleElement = createStyleSheet(undefined, undefined, this._styleElementDisposables);
this._styleElement.className = 'accessibilityUnfocusedViewOpacity';
}
return this._styleElement;
}
private _removeStyleElement(): void {
- this._styleElement?.remove();
+ this._styleElementDisposables?.dispose();
+ this._styleElementDisposables = undefined;
this._styleElement = undefined;
}
}
diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts
index 8dbd2c26bfce5..48edae1a29a7f 100644
--- a/src/vs/workbench/contrib/debug/browser/debugSession.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts
@@ -1012,7 +1012,7 @@ export class DebugSession implements IDebugSession, IDisposable {
}
if (this.configurationService.getValue('debug').focusWindowOnBreak && !this.workbenchEnvironmentService.extensionTestsLocationURI) {
- await this.hostService.focus({ force: true /* Application may not be active */ });
+ await this.hostService.focus(window, { force: true /* Application may not be active */ });
}
}
}
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
index 3bb04e433a22d..55c422a93102e 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
@@ -1928,7 +1928,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
[extension] = await this.getExtensions([{ id: extensionId }], { source: 'uri' }, CancellationToken.None);
}
if (extension) {
- await this.hostService.focus();
+ await this.hostService.focus(window);
await this.open(extension);
}
}).then(undefined, error => this.onError(error));
diff --git a/src/vs/workbench/contrib/files/browser/fileImportExport.ts b/src/vs/workbench/contrib/files/browser/fileImportExport.ts
index 0ad3bfa636a0a..10a1453fd054e 100644
--- a/src/vs/workbench/contrib/files/browser/fileImportExport.ts
+++ b/src/vs/workbench/contrib/files/browser/fileImportExport.ts
@@ -402,7 +402,7 @@ export class ExternalFileImport {
) {
}
- async import(target: ExplorerItem, source: DragEvent): Promise {
+ async import(target: ExplorerItem, source: DragEvent, targetWindow: Window): Promise {
const cts = new CancellationTokenSource();
// Indicate progress globally
@@ -413,7 +413,7 @@ export class ExternalFileImport {
cancellable: true,
title: localize('copyingFiles', "Copying...")
},
- async () => await this.doImport(target, source, cts.token),
+ async () => await this.doImport(target, source, targetWindow, cts.token),
() => cts.dispose(true)
);
@@ -423,7 +423,7 @@ export class ExternalFileImport {
return importPromise;
}
- private async doImport(target: ExplorerItem, source: DragEvent, token: CancellationToken): Promise {
+ private async doImport(target: ExplorerItem, source: DragEvent, targetWindow: Window, token: CancellationToken): Promise {
// Activate all providers for the resources dropped
const candidateFiles = coalesce((await this.instantiationService.invokeFunction(accessor => extractEditorsAndFilesDropData(accessor, source))).map(editor => editor.resource));
@@ -438,7 +438,7 @@ export class ExternalFileImport {
}
// Pass focus to window
- this.hostService.focus();
+ this.hostService.focus(targetWindow);
// Handle folders by adding to workspace if we are in workspace context and if dropped on top
const folders = resolvedFiles.filter(resolvedFile => resolvedFile.success && resolvedFile.stat?.isDirectory).map(resolvedFile => ({ uri: resolvedFile.stat!.resource }));
diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts
index 4f450ff50e075..aba7df4c9427a 100644
--- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts
+++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts
@@ -20,7 +20,7 @@ import { IViewDescriptorService } from 'vs/workbench/common/views';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { isWeb } from 'vs/base/common/platform';
-import { DragAndDropObserver } from 'vs/base/browser/dom';
+import { DragAndDropObserver, getWindow } from 'vs/base/browser/dom';
import { ILocalizedString } from 'vs/platform/action/common/action';
export class EmptyView extends ViewPane {
@@ -60,7 +60,7 @@ export class EmptyView extends ViewPane {
onDrop: e => {
container.style.backgroundColor = '';
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: !isWeb || isTemporaryWorkspace(this.contextService.getWorkspace()) });
- dropHandler.handleDrop(e);
+ dropHandler.handleDrop(e, getWindow(container));
},
onDragEnter: () => {
const color = this.themeService.getColorTheme().getColor(listDropBackground);
diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
index d567ec809169a..ab9459d78a8a5 100644
--- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
+++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
@@ -1260,7 +1260,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop {
// Use local file import when supported
if (!isWeb || (isTemporaryWorkspace(this.contextService.getWorkspace()) && WebFileSystemAccess.supported(window))) {
const fileImport = this.instantiationService.createInstance(ExternalFileImport);
- await fileImport.import(resolvedTarget, originalEvent);
+ await fileImport.import(resolvedTarget, originalEvent, window);
}
// Otherwise fallback to browser based file upload
else {
diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts
index 5a881944d4fb1..96edb1c4abea4 100644
--- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts
+++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts
@@ -718,7 +718,7 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop group, () => group.focus(), { index });
+ this.dropHandler.handleDrop(originalEvent, window, () => group, () => group.focus(), { index });
}
}
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts
index 69b4fac15a9cc..8d88a891a5b79 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/conflictActions.ts
@@ -6,7 +6,7 @@
import { $, createStyleSheet, h, isInShadowDOM, reset } from 'vs/base/browser/dom';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { hash } from 'vs/base/common/hash';
-import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
+import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { autorun, derived, IObservable, transaction } from 'vs/base/common/observable';
import { ICodeEditor, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser';
import { EditorOption, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions';
@@ -32,13 +32,9 @@ export class ConflictActionsFactory extends Disposable {
this._styleElement = createStyleSheet(
isInShadowDOM(this._editor.getContainerDomNode())
? this._editor.getContainerDomNode()
- : undefined
+ : undefined, undefined, this._store
);
- this._register(toDisposable(() => {
- this._styleElement.remove();
- }));
-
this._updateLensStyle();
}
diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts
index 6fc13ccd0efa9..f330ff686e156 100644
--- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts
+++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts
@@ -723,8 +723,7 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu
) {
super();
this.enabled = !contextKeyService.getContextKeyValue('isInDiffEditor');
- this.stylesheet = dom.createStyleSheet();
- this._register(toDisposable(() => this.stylesheet.remove()));
+ this.stylesheet = dom.createStyleSheet(undefined, undefined, this._store);
if (this.enabled) {
this.isDirtyDiffVisible = isDirtyDiffVisible.bindTo(contextKeyService);
@@ -1557,8 +1556,7 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor
@ITextFileService private readonly textFileService: ITextFileService
) {
super();
- this.stylesheet = dom.createStyleSheet();
- this._register(toDisposable(() => this.stylesheet.parentElement!.removeChild(this.stylesheet)));
+ this.stylesheet = dom.createStyleSheet(undefined, undefined, this._store);
const onDidChangeConfiguration = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.diffDecorations'));
this._register(onDidChangeConfiguration(this.onDidChangeConfiguration, this));
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts
index 07b474a831fe7..1ea310c533f5a 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts
@@ -15,7 +15,7 @@ import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/termina
import { ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal';
import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
import { createStyleSheet } from 'vs/base/browser/dom';
-import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
+import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
export function getColorClass(colorKey: string): string;
@@ -50,8 +50,9 @@ export function getStandardColors(colorTheme: IColorTheme): string[] {
}
export function createColorStyleElement(colorTheme: IColorTheme): IDisposable {
+ const disposable = new DisposableStore();
const standardColors = getStandardColors(colorTheme);
- const styleElement = createStyleSheet();
+ const styleElement = createStyleSheet(undefined, undefined, disposable);
let css = '';
for (const colorKey of standardColors) {
const colorClass = getColorClass(colorKey);
@@ -64,7 +65,7 @@ export function createColorStyleElement(colorTheme: IColorTheme): IDisposable {
}
}
styleElement.textContent = css;
- return toDisposable(() => styleElement.remove());
+ return disposable;
}
export function getColorStyleContent(colorTheme: IColorTheme, editor?: boolean): string {
diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewIconManager.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewIconManager.ts
index 7ffb3adf21346..f24731711d3ca 100644
--- a/src/vs/workbench/contrib/webviewPanel/browser/webviewIconManager.ts
+++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewIconManager.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
-import { IDisposable } from 'vs/base/common/lifecycle';
+import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
@@ -19,6 +19,7 @@ export class WebviewIconManager implements IDisposable {
private readonly _icons = new Map();
private _styleElement: HTMLStyleElement | undefined;
+ private _styleElementDisposable: DisposableStore | undefined;
constructor(
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@@ -32,13 +33,15 @@ export class WebviewIconManager implements IDisposable {
}
dispose() {
- this._styleElement?.remove();
+ this._styleElementDisposable?.dispose();
+ this._styleElementDisposable = undefined;
this._styleElement = undefined;
}
private get styleElement(): HTMLStyleElement {
if (!this._styleElement) {
- this._styleElement = dom.createStyleSheet();
+ this._styleElementDisposable = new DisposableStore();
+ this._styleElement = dom.createStyleSheet(undefined, undefined, this._styleElementDisposable);
this._styleElement.className = 'webview-icons';
}
return this._styleElement;
diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts
index 6535966e5b9ed..2e1902cd78729 100644
--- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts
+++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts
@@ -12,7 +12,7 @@ import { getZoomLevel } from 'vs/base/browser/browser';
import { FileKind } from 'vs/platform/files/common/files';
import { IModelService } from 'vs/editor/common/services/model';
import { ILanguageService } from 'vs/editor/common/languages/language';
-import { IQuickInputService, IQuickInputButton } from 'vs/platform/quickinput/common/quickInput';
+import { IQuickInputService, IQuickInputButton, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { ICommandHandler } from 'vs/platform/commands/common/commands';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
@@ -29,8 +29,9 @@ import { isMacintosh } from 'vs/base/common/platform';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { getActiveWindow } from 'vs/base/browser/dom';
-import { isAuxiliaryWindow } from 'vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService';
+import { isAuxiliaryWindow } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
+import { IOpenedAuxiliaryWindow, IOpenedMainWindow, isOpenedAuxiliaryWindow } from 'vs/platform/window/common/window';
export class CloseWindowAction extends Action2 {
@@ -216,23 +217,70 @@ abstract class BaseSwitchWindow extends Action2 {
const languageService = accessor.get(ILanguageService);
const nativeHostService = accessor.get(INativeHostService);
- const currentWindowId = nativeHostService.windowId;
+ let currentWindowId: number;
+ const activeWindow = getActiveWindow();
+ if (isAuxiliaryWindow(activeWindow)) {
+ currentWindowId = activeWindow.vscodeWindowId;
+ } else {
+ currentWindowId = nativeHostService.windowId;
+ }
+
+ const windows = await nativeHostService.getWindows({ includeAuxiliaryWindows: true });
+
+ const mainWindows = new Set();
+ const mapMainWindowToAuxiliaryWindows = new Map>();
+ for (const window of windows) {
+ if (isOpenedAuxiliaryWindow(window)) {
+ let auxiliaryWindows = mapMainWindowToAuxiliaryWindows.get(window.parentId);
+ if (!auxiliaryWindows) {
+ auxiliaryWindows = new Set();
+ mapMainWindowToAuxiliaryWindows.set(window.parentId, auxiliaryWindows);
+ }
+ auxiliaryWindows.add(window);
+ } else {
+ mainWindows.add(window);
+ }
+ }
+
+ interface IWindowPickItem extends IQuickPickItem {
+ readonly windowId: number;
+ }
+
+ const picks: Array = [];
+ for (const window of mainWindows) {
+ const auxiliaryWindows = mapMainWindowToAuxiliaryWindows.get(window.id);
+ if (mapMainWindowToAuxiliaryWindows.size > 0) {
+ picks.push({ type: 'separator', payload: -1, label: auxiliaryWindows ? localize('windowGroup', "Window Group") : undefined } as unknown as IWindowPickItem);
+ }
- const windows = await nativeHostService.getWindows();
- const placeHolder = localize('switchWindowPlaceHolder', "Select a window to switch to");
- const picks = windows.map(window => {
const resource = window.filename ? URI.file(window.filename) : isSingleFolderWorkspaceIdentifier(window.workspace) ? window.workspace.uri : isWorkspaceIdentifier(window.workspace) ? window.workspace.configPath : undefined;
const fileKind = window.filename ? FileKind.FILE : isSingleFolderWorkspaceIdentifier(window.workspace) ? FileKind.FOLDER : isWorkspaceIdentifier(window.workspace) ? FileKind.ROOT_FOLDER : FileKind.FILE;
- return {
- payload: window.id,
+ const pick: IWindowPickItem = {
+ windowId: window.id,
label: window.title,
ariaLabel: window.dirty ? localize('windowDirtyAriaLabel', "{0}, window with unsaved changes", window.title) : window.title,
iconClasses: getIconClasses(modelService, languageService, resource, fileKind),
description: (currentWindowId === window.id) ? localize('current', "Current Window") : undefined,
buttons: currentWindowId !== window.id ? window.dirty ? [this.closeDirtyWindowAction] : [this.closeWindowAction] : undefined
};
- });
- const autoFocusIndex = (picks.indexOf(picks.filter(pick => pick.payload === currentWindowId)[0]) + 1) % picks.length;
+ picks.push(pick);
+
+ if (auxiliaryWindows) {
+ for (const auxiliaryWindow of auxiliaryWindows) {
+ const pick: IWindowPickItem = {
+ windowId: auxiliaryWindow.id,
+ label: auxiliaryWindow.title,
+ iconClasses: getIconClasses(modelService, languageService, auxiliaryWindow.filename ? URI.file(auxiliaryWindow.filename) : undefined, FileKind.FILE),
+ description: (currentWindowId === auxiliaryWindow.id) ? localize('current', "Current Window") : undefined,
+ buttons: [this.closeWindowAction]
+ };
+ picks.push(pick);
+ }
+ }
+ }
+
+ const placeHolder = localize('switchWindowPlaceHolder', "Select a window to switch to");
+ const autoFocusIndex = (picks.indexOf(picks.filter(pick => pick.windowId === currentWindowId)[0]) + 1) % picks.length;
const pick = await quickInputService.pick(picks, {
contextKey: 'inWindowsPicker',
@@ -241,13 +289,13 @@ abstract class BaseSwitchWindow extends Action2 {
quickNavigate: this.isQuickNavigate() ? { keybindings: keybindingService.lookupKeybindings(this.desc.id) } : undefined,
hideInput: this.isQuickNavigate(),
onDidTriggerItemButton: async context => {
- await nativeHostService.closeWindowById(context.item.payload);
+ await nativeHostService.closeWindowById(context.item.windowId);
context.removeItem();
}
});
if (pick) {
- nativeHostService.focusWindow({ targetWindowId: pick.payload });
+ nativeHostService.focusWindow({ targetWindowId: pick.windowId });
}
}
}
diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts
index 0c606c68a2c91..bddfc483eb496 100644
--- a/src/vs/workbench/electron-sandbox/window.ts
+++ b/src/vs/workbench/electron-sandbox/window.ts
@@ -70,6 +70,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { IUtilityProcessWorkerWorkbenchService } from 'vs/workbench/services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService';
import { registerWindowDriver } from 'vs/workbench/services/driver/electron-sandbox/driver';
+import { IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService';
export class NativeWindow extends Disposable {
@@ -86,6 +87,8 @@ export class NativeWindow extends Disposable {
private isDocumentedEdited = false;
+ private readonly mainPartEditorService: IEditorService;
+
constructor(
@IEditorService private readonly editorService: IEditorService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@@ -122,10 +125,13 @@ export class NativeWindow extends Disposable {
@IBannerService private readonly bannerService: IBannerService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@IPreferencesService private readonly preferencesService: IPreferencesService,
- @IUtilityProcessWorkerWorkbenchService private readonly utilityProcessWorkerWorkbenchService: IUtilityProcessWorkerWorkbenchService
+ @IUtilityProcessWorkerWorkbenchService private readonly utilityProcessWorkerWorkbenchService: IUtilityProcessWorkerWorkbenchService,
+ @IAuxiliaryWindowService private readonly auxiliaryWindowService: IAuxiliaryWindowService
) {
super();
+ this.mainPartEditorService = editorService.createScoped('main', this._store);
+
this.registerListeners();
this.create();
}
@@ -334,7 +340,7 @@ export class NativeWindow extends Disposable {
}));
// Listen to visible editor changes
- this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidChangeVisibleEditors()));
+ this._register(this.mainPartEditorService.onDidVisibleEditorsChange(() => this.onDidChangeVisibleEditors()));
// Listen to editor closing (if we run with --wait)
const filesToWait = this.environmentService.filesToWait;
@@ -344,14 +350,26 @@ export class NativeWindow extends Disposable {
// macOS OS integration
if (isMacintosh) {
- this._register(this.editorService.onDidActiveEditorChange(() => {
- const file = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file });
+ const updateRepresentedFilename = (editorService: IEditorService, targetWindowId: number | undefined) => {
+ const file = EditorResourceAccessor.getOriginalUri(editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file });
// Represented Filename
- this.nativeHostService.setRepresentedFilename(file?.fsPath ?? '');
+ this.nativeHostService.setRepresentedFilename(file?.fsPath ?? '', { targetWindowId });
- // Custom title menu
- this.provideCustomTitleContextMenu(file?.fsPath);
+ // Custom title menu (main window only currently)
+ if (typeof targetWindowId !== 'number') {
+ this.provideCustomTitleContextMenu(file?.fsPath);
+ }
+ };
+
+ this._register(this.mainPartEditorService.onDidActiveEditorChange(() => updateRepresentedFilename(this.mainPartEditorService, undefined)));
+
+ this._register(this.auxiliaryWindowService.onDidOpenAuxiliaryWindow(({ window, disposables }) => {
+ const auxiliaryWindowEditorPart = this.editorGroupService.getPart(window.container);
+ if (auxiliaryWindowEditorPart) {
+ const auxiliaryEditorService = this.editorService.createScoped(auxiliaryWindowEditorPart, disposables);
+ disposables.add(auxiliaryEditorService.onDidActiveEditorChange(() => updateRepresentedFilename(auxiliaryEditorService, window.window.vscodeWindowId)));
+ }
}));
}
@@ -585,7 +603,7 @@ export class NativeWindow extends Disposable {
// Close when empty: check if we should close the window based on the setting
// Overruled by: window has a workspace opened or this window is for extension development
// or setting is disabled. Also enabled when running with --wait from the command line.
- const visibleEditorPanes = this.editorService.visibleEditorPanes;
+ const visibleEditorPanes = this.mainPartEditorService.visibleEditorPanes;
if (visibleEditorPanes.length === 0 && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && !this.environmentService.isExtensionDevelopment) {
const closeWhenEmpty = this.configurationService.getValue('window.closeWhenEmpty');
if (closeWhenEmpty || this.environmentService.args.wait) {
@@ -595,7 +613,7 @@ export class NativeWindow extends Disposable {
}
private onDidAllEditorsClose(): void {
- const visibleEditorPanes = this.editorService.visibleEditorPanes.length;
+ const visibleEditorPanes = this.mainPartEditorService.visibleEditorPanes.length;
if (visibleEditorPanes === 0) {
this.nativeHostService.closeWindow();
}
@@ -683,7 +701,7 @@ export class NativeWindow extends Disposable {
originalWindowFocus();
if (getActiveWindow() !== window) {
- that.nativeHostService.focusWindow();
+ that.nativeHostService.focusWindow({ targetWindowId: that.nativeHostService.windowId });
}
};
}
diff --git a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts
index 8c1a57c8aa806..909a16a2afd59 100644
--- a/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts
+++ b/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts
@@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
+import { mark } from 'vs/base/common/performance';
import { Emitter, Event } from 'vs/base/common/event';
-import { Dimension, EventHelper, EventType, addDisposableListener, cloneGlobalStylesheets, copyAttributes, createMetaElement, getActiveWindow, getClientArea, isGlobalStylesheet, position, registerWindow, size, trackAttributes } from 'vs/base/browser/dom';
+import { Dimension, EventHelper, EventType, addDisposableListener, cloneGlobalStylesheets, copyAttributes, createMetaElement, getActiveWindow, getClientArea, isGlobalStylesheet, position, registerWindow, sharedMutationObserver, size, trackAttributes } from 'vs/base/browser/dom';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
@@ -37,13 +38,21 @@ export interface IAuxiliaryWindow extends IDisposable {
readonly onDidLayout: Event;
readonly onDidClose: Event;
- readonly window: Window & typeof globalThis;
+ readonly window: AuxiliaryWindow;
readonly container: HTMLElement;
layout(): void;
}
-export type AuxiliaryWindow = Window & typeof globalThis;
+export type AuxiliaryWindow = Window & typeof globalThis & {
+ readonly vscodeWindowId: number;
+};
+
+export function isAuxiliaryWindow(obj: unknown): obj is AuxiliaryWindow {
+ const candidate = obj as AuxiliaryWindow | undefined;
+
+ return !!candidate && Object.hasOwn(candidate, 'vscodeWindowId');
+}
export class BrowserAuxiliaryWindowService extends Disposable implements IAuxiliaryWindowService {
@@ -51,6 +60,8 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili
private static readonly DEFAULT_SIZE = { width: 800, height: 600 };
+ private static WINDOW_IDS = 0;
+
private readonly _onDidOpenAuxiliaryWindow = this._register(new Emitter());
readonly onDidOpenAuxiliaryWindow = this._onDidOpenAuxiliaryWindow.event;
@@ -62,6 +73,8 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili
}
async open(options?: { position?: IRectangle }): Promise {
+ mark('code/auxiliaryWindow/willOpen');
+
const disposables = new DisposableStore();
const auxiliaryWindow = await this.doOpen(options);
@@ -72,7 +85,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili
disposables.add(registerWindow(auxiliaryWindow));
disposables.add(toDisposable(() => auxiliaryWindow.close()));
- const { container, onDidLayout, onDidClose } = this.create(auxiliaryWindow, disposables);
+ const { container, onDidLayout, onDidClose } = await this.create(auxiliaryWindow, disposables);
const result: IAuxiliaryWindow = {
window: auxiliaryWindow,
@@ -87,6 +100,8 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili
disposables.add(eventDisposables);
this._onDidOpenAuxiliaryWindow.fire({ window: result, disposables: eventDisposables });
+ mark('code/auxiliaryWindow/didOpen');
+
return result;
}
@@ -118,11 +133,11 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili
})).result;
}
- return auxiliaryWindow?.window;
+ return auxiliaryWindow?.window as AuxiliaryWindow | undefined;
}
- protected create(auxiliaryWindow: AuxiliaryWindow, disposables: DisposableStore) {
- this.patchMethods(auxiliaryWindow);
+ protected async create(auxiliaryWindow: AuxiliaryWindow, disposables: DisposableStore) {
+ await this.patchMethods(auxiliaryWindow);
this.applyMeta(auxiliaryWindow);
this.applyCSS(auxiliaryWindow, disposables);
@@ -151,6 +166,8 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili
}
protected applyCSS(auxiliaryWindow: AuxiliaryWindow, disposables: DisposableStore): void {
+ mark('code/auxiliaryWindow/willApplyCSS');
+
const mapOriginalToClone = new Map();
function cloneNode(originalNode: Node): void {
@@ -174,7 +191,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili
// Listen to new stylesheets as they are being added or removed in the main window
// and apply to child window (including changes to existing stylesheets elements)
- const observer = new MutationObserver(mutations => {
+ disposables.add(sharedMutationObserver.observe(document.head, disposables, { childList: true, subtree: true })(mutations => {
for (const mutation of mutations) {
if (
mutation.type !== 'childList' || // only interested in added/removed nodes
@@ -209,13 +226,13 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili
}
}
}
- });
+ }));
- observer.observe(document.head, { childList: true, subtree: true });
- disposables.add(toDisposable(() => observer.disconnect()));
+ mark('code/auxiliaryWindow/didApplyCSS');
}
private applyHTML(auxiliaryWindow: AuxiliaryWindow, disposables: DisposableStore): HTMLElement {
+ mark('code/auxiliaryWindow/willApplyHTML');
// Create workbench container and apply classes
const container = document.createElement('div');
@@ -226,6 +243,8 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili
disposables.add(trackAttributes(document.body, auxiliaryWindow.document.body));
disposables.add(trackAttributes(this.layoutService.container, container, ['class'])); // only class attribute
+ mark('code/auxiliaryWindow/didApplyHTML');
+
return container;
}
@@ -249,7 +268,7 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili
onDidLayout.fire(dimension);
}));
- this._register(addDisposableListener(container, EventType.SCROLL, () => container.scrollTop = 0)); // // Prevent container from scrolling (#55456)
+ this._register(addDisposableListener(container, EventType.SCROLL, () => container.scrollTop = 0)); // Prevent container from scrolling (#55456)
if (isWeb) {
disposables.add(addDisposableListener(container, EventType.DROP, e => EventHelper.stop(e, true))); // Prevent default navigation on drop
@@ -263,7 +282,18 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili
return { onDidLayout, onDidClose };
}
- protected patchMethods(auxiliaryWindow: AuxiliaryWindow): void {
+ protected async resolveWindowId(auxiliaryWindow: AuxiliaryWindow): Promise {
+ return BrowserAuxiliaryWindowService.WINDOW_IDS++;
+ }
+
+ protected async patchMethods(auxiliaryWindow: AuxiliaryWindow): Promise {
+ mark('code/auxiliaryWindow/willPatchMethods');
+
+ // Add a `vscodeWindowId` property to identify auxiliary windows
+ const resolvedWindowId = await this.resolveWindowId(auxiliaryWindow);
+ Object.defineProperty(auxiliaryWindow, 'vscodeWindowId', {
+ get: () => resolvedWindowId
+ });
// Disallow `createElement` because it would create
// HTML Elements in the "wrong" context and break
@@ -271,6 +301,8 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili
auxiliaryWindow.document.createElement = function () {
throw new Error('Not allowed to create elements in child window JavaScript context. Always use the main window so that "xyz instanceof HTMLElement" continues to work.');
};
+
+ mark('code/auxiliaryWindow/didPatchMethods');
}
}
diff --git a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts
index 717e29eabd973..eb51b35529554 100644
--- a/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts
+++ b/src/vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService.ts
@@ -5,7 +5,7 @@
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
-import { BrowserAuxiliaryWindowService, IAuxiliaryWindowService, AuxiliaryWindow as BaseAuxiliaryWindow } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService';
+import { BrowserAuxiliaryWindowService, IAuxiliaryWindowService, AuxiliaryWindow as BrowserAuxiliaryWindow } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService';
import { ISandboxGlobals } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWindowsConfiguration } from 'vs/platform/window/common/window';
@@ -14,17 +14,10 @@ import { INativeHostService } from 'vs/platform/native/common/native';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { getActiveWindow } from 'vs/base/browser/dom';
-type AuxiliaryWindow = BaseAuxiliaryWindow & {
+type NativeAuxiliaryWindow = BrowserAuxiliaryWindow & {
readonly vscode: ISandboxGlobals;
- readonly vscodeWindowId: number;
};
-export function isAuxiliaryWindow(obj: unknown): obj is AuxiliaryWindow {
- const candidate = obj as AuxiliaryWindow | undefined;
-
- return !!candidate?.vscode && Object.hasOwn(candidate, 'vscodeWindowId');
-}
-
export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService {
constructor(
@@ -36,7 +29,7 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService
super(layoutService, dialogService);
}
- protected override create(auxiliaryWindow: AuxiliaryWindow, disposables: DisposableStore) {
+ protected override create(auxiliaryWindow: NativeAuxiliaryWindow, disposables: DisposableStore) {
// Zoom level
const windowConfig = this.configurationService.getValue();
@@ -46,19 +39,12 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService
return super.create(auxiliaryWindow, disposables);
}
- protected override patchMethods(auxiliaryWindow: AuxiliaryWindow): void {
- super.patchMethods(auxiliaryWindow);
-
- // Obtain window identifier
- let resolvedWindowId: number;
- (async () => {
- resolvedWindowId = await auxiliaryWindow.vscode.ipcRenderer.invoke('vscode:getWindowId');
- })();
+ protected override resolveWindowId(auxiliaryWindow: NativeAuxiliaryWindow): Promise {
+ return auxiliaryWindow.vscode.ipcRenderer.invoke('vscode:registerAuxiliaryWindow', this.nativeHostService.windowId);
+ }
- // Add a `windowId` property
- Object.defineProperty(auxiliaryWindow, 'vscodeWindowId', {
- get: () => resolvedWindowId
- });
+ protected override async patchMethods(auxiliaryWindow: NativeAuxiliaryWindow): Promise {
+ await super.patchMethods(auxiliaryWindow);
// Enable `window.focus()` to work in Electron by
// asking the main process to focus the window.
@@ -69,7 +55,7 @@ export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService
originalWindowFocus();
if (getActiveWindow() !== auxiliaryWindow) {
- that.nativeHostService.focusWindow({ targetWindowId: resolvedWindowId });
+ that.nativeHostService.focusWindow({ targetWindowId: auxiliaryWindow.vscodeWindowId });
}
};
}
diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts
index e725eccdd1bf7..31f1057999d9a 100644
--- a/src/vs/workbench/services/decorations/browser/decorationsService.ts
+++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts
@@ -158,16 +158,15 @@ class DecorationRule {
class DecorationStyles {
- private readonly _styleElement = createStyleSheet();
- private readonly _decorationRules = new Map();
private readonly _dispoables = new DisposableStore();
+ private readonly _styleElement = createStyleSheet(undefined, undefined, this._dispoables);
+ private readonly _decorationRules = new Map();
constructor(private readonly _themeService: IThemeService) {
}
dispose(): void {
this._dispoables.dispose();
- this._styleElement.remove();
}
asDecoration(data: IDecorationData[], onlyChildren: boolean): IDecoration {
diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts
index ba0188a48b873..047a7e177c603 100644
--- a/src/vs/workbench/services/editor/common/editorGroupsService.ts
+++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts
@@ -492,6 +492,11 @@ export interface IEditorGroupsService extends IEditorGroupsContainer {
*/
getPart(group: IEditorGroup | GroupIdentifier): IEditorPart;
+ /**
+ * Get the editor part that is rooted in the provided container.
+ */
+ getPart(container: unknown /* HTMLElement */): IEditorPart | undefined;
+
/**
* Opens a new window with a full editor part instantiated
* in there at the optional position on screen.
diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts
index 4f56301f2f9af..40f79b6a74c2b 100644
--- a/src/vs/workbench/services/host/browser/browserHostService.ts
+++ b/src/vs/workbench/services/host/browser/browserHostService.ts
@@ -14,7 +14,7 @@ import { isResourceEditorInput, pathsToEditors } from 'vs/workbench/common/edito
import { whenEditorClosed } from 'vs/workbench/browser/editor';
import { IFileService } from 'vs/platform/files/common/files';
import { ILabelService, Verbosity } from 'vs/platform/label/common/label';
-import { ModifierKeyEmitter, getActiveDocument, getActiveWindow, onDidRegisterWindow, trackFocus } from 'vs/base/browser/dom';
+import { ModifierKeyEmitter, getActiveDocument, onDidRegisterWindow, trackFocus } from 'vs/base/browser/dom';
import { Disposable } from 'vs/base/common/lifecycle';
import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
import { memoize } from 'vs/base/common/decorators';
@@ -201,8 +201,8 @@ export class BrowserHostService extends Disposable implements IHostService {
return true;
}
- async focus(): Promise {
- getActiveWindow().focus();
+ async focus(window: Window): Promise {
+ window.focus();
}
//#endregion
@@ -505,7 +505,7 @@ export class BrowserHostService extends Disposable implements IHostService {
}
}
- async moveTop(window: Window & typeof globalThis): Promise {
+ async moveTop(window: Window): Promise {
// There seems to be no API to bring a window to front in browsers
}
diff --git a/src/vs/workbench/services/host/browser/host.ts b/src/vs/workbench/services/host/browser/host.ts
index 569f40e6c6035..fb2eb102556f9 100644
--- a/src/vs/workbench/services/host/browser/host.ts
+++ b/src/vs/workbench/services/host/browser/host.ts
@@ -46,7 +46,7 @@ export interface IHostService {
* focused application which may not be VSCode. It may not be supported
* in all environments.
*/
- focus(options?: { force: boolean }): Promise;
+ focus(window: Window, options?: { force: boolean }): Promise;
//#endregion
@@ -72,7 +72,7 @@ export interface IHostService {
/**
* Bring a window to the front and restore it if needed.
*/
- moveTop(window: Window & typeof globalThis): Promise;
+ moveTop(window: Window): Promise;
//#endregion
diff --git a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts
index 8909c6bd0f5a5..d33a776105ada 100644
--- a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts
+++ b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts
@@ -14,7 +14,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IMainProcessService } from 'vs/platform/ipc/common/mainProcessService';
-import { isAuxiliaryWindow } from 'vs/workbench/services/auxiliaryWindow/electron-sandbox/auxiliaryWindowService';
+import { isAuxiliaryWindow } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService';
import { getActiveDocument, getWindowsCount, onDidRegisterWindow, trackFocus } from 'vs/base/browser/dom';
import { DomEmitter } from 'vs/base/browser/event';
import { memoize } from 'vs/base/common/decorators';
@@ -133,7 +133,7 @@ class WorkbenchHostService extends Disposable implements IHostService {
return this.nativeHostService.toggleFullScreen();
}
- async moveTop(window: Window & typeof globalThis): Promise {
+ async moveTop(window: Window): Promise {
if (getWindowsCount() <= 1) {
return; // does not apply when only one window is opened
}
@@ -146,8 +146,11 @@ class WorkbenchHostService extends Disposable implements IHostService {
//#region Lifecycle
- focus(options?: { force: boolean }): Promise {
- return this.nativeHostService.focusWindow(options);
+ focus(window: Window, options?: { force: boolean }): Promise {
+ return this.nativeHostService.focusWindow({
+ force: options?.force,
+ targetWindowId: isAuxiliaryWindow(window) ? window.vscodeWindowId : this.nativeHostService.windowId
+ });
}
restart(): Promise {
diff --git a/src/vs/workbench/services/url/electron-sandbox/urlService.ts b/src/vs/workbench/services/url/electron-sandbox/urlService.ts
index 3b3000abce61a..6e3662033e61c 100644
--- a/src/vs/workbench/services/url/electron-sandbox/urlService.ts
+++ b/src/vs/workbench/services/url/electron-sandbox/urlService.ts
@@ -70,7 +70,7 @@ export class RelayURLService extends NativeURLService implements IURLHandler, IO
if (result) {
this.logService.trace('URLService#handleURL(): handled', uri.toString(true));
- await this.nativeHostService.focusWindow({ force: true /* Application may not be active */ });
+ await this.nativeHostService.focusWindow({ force: true /* Application may not be active */, targetWindowId: this.nativeHostService.windowId });
} else {
this.logService.trace('URLService#handleURL(): not handled', uri.toString(true));
}
diff --git a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts
index a354835131419..ad00669d146cd 100644
--- a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts
+++ b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts
@@ -138,7 +138,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi
}
override async isValidTargetWorkspacePath(workspaceUri: URI): Promise {
- const windows = await this.nativeHostService.getWindows();
+ const windows = await this.nativeHostService.getWindows({ includeAuxiliaryWindows: false });
// Prevent overwriting a workspace that is currently opened in another window
if (windows.some(window => isWorkspaceIdentifier(window.workspace) && this.uriIdentityService.extUri.isEqual(window.workspace.configPath, workspaceUri))) {
diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts
index c225393802587..bbbe0bdd5fcd3 100644
--- a/src/vs/workbench/test/browser/workbenchTestServices.ts
+++ b/src/vs/workbench/test/browser/workbenchTestServices.ts
@@ -1485,7 +1485,7 @@ export class TestHostService implements IHostService {
return await expectedShutdownTask();
}
- async focus(options?: { force: boolean }): Promise { }
+ async focus(): Promise { }
async moveTop(): Promise { }
async openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise { }
diff --git a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts
index a6e43ee500099..c378338205760 100644
--- a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts
+++ b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts
@@ -12,7 +12,7 @@ import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IFileDialogService, INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { IPartsSplash } from 'vs/platform/theme/common/themeService';
-import { IOpenedWindow, IOpenEmptyWindowOptions, IWindowOpenable, IOpenWindowOptions, IColorScheme, IRectangle } from 'vs/platform/window/common/window';
+import { IOpenedMainWindow, IOpenEmptyWindowOptions, IWindowOpenable, IOpenWindowOptions, IColorScheme, IRectangle } from 'vs/platform/window/common/window';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -78,7 +78,7 @@ export class TestNativeHostService implements INativeHostService {
windowCount = Promise.resolve(1);
getWindowCount(): Promise { return this.windowCount; }
- async getWindows(): Promise { return []; }
+ async getWindows(): Promise { return []; }
async getActiveWindowId(): Promise { return undefined; }
openWindow(options?: IOpenEmptyWindowOptions): Promise;