From d4ef702a11c23a0b5257fc0277cf7c7228d9ff63 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 8 Mar 2026 19:58:47 +0100 Subject: [PATCH 1/2] modal editor - tweaks to UI depending on setting --- .../browser/parts/editor/editorPart.ts | 2 +- .../browser/parts/editor/modalEditorPart.ts | 37 +++++++----- .../test/browser/modalEditorGroup.test.ts | 58 +++++++++++++++++++ 3 files changed, 82 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index c172d537754ca..31f83c70a1140 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -168,7 +168,7 @@ export class EditorPart extends Part implements IEditorPart, readonly windowId: number, @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IConfigurationService protected readonly configurationService: IConfigurationService, @IStorageService storageService: IStorageService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IHostService private readonly hostService: IHostService, diff --git a/src/vs/workbench/browser/parts/editor/modalEditorPart.ts b/src/vs/workbench/browser/parts/editor/modalEditorPart.ts index 9d282dcd1a09e..9faca0f31c7f4 100644 --- a/src/vs/workbench/browser/parts/editor/modalEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/modalEditorPart.ts @@ -103,6 +103,8 @@ const defaultModalEditorAllowableCommands = new Set([ NAVIGATE_MODAL_EDITOR_NEXT_COMMAND_ID, ]); +const USE_MODAL_EDITOR_SETTING = 'workbench.editor.useModal'; + export interface ICreateModalEditorPartResult { readonly part: ModalEditorPartImpl; readonly instantiationService: IInstantiationService; @@ -139,10 +141,10 @@ export class ModalEditorPart { } })); - let useModalMode = this.configurationService.getValue('workbench.editor.useModal'); + let useModalMode = this.configurationService.getValue(USE_MODAL_EDITOR_SETTING); disposables.add(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('workbench.editor.useModal')) { - useModalMode = this.configurationService.getValue('workbench.editor.useModal'); + if (e.affectsConfiguration(USE_MODAL_EDITOR_SETTING)) { + useModalMode = this.configurationService.getValue(USE_MODAL_EDITOR_SETTING); } })); @@ -271,6 +273,7 @@ export class ModalEditorPart { setVisibility(hasActions, editorActionsSeparator); }; disposables.add(Event.runAndSubscribe(modalEditorService.onDidActiveEditorChange, () => updateEditorActions())); + disposables.add(modalEditorService.onDidVisibleEditorsChange(() => editorPart.enforceModalPartOptions())); // Create global toolbar disposables.add(scopedInstantiationService.createInstance(MenuWorkbenchToolBar, actionBarContainer, MenuId.ModalEditorTitle, { @@ -346,20 +349,19 @@ export class ModalEditorPart { const titleBarOffset = this.layoutService.mainContainerOffset.top; const dialogWidth = resizableElement.size.width; const dialogHeight = resizableElement.size.height; - const availableHeight = Math.max(containerDimension.height - titleBarOffset, 0); // Clamp to window bounds const minLeft = 0; const minTop = titleBarOffset; const maxLeft = Math.max(minLeft, containerDimension.width - dialogWidth); - const maxTop = Math.max(minTop, titleBarOffset + availableHeight - dialogHeight); + const maxTop = Math.max(minTop, containerDimension.height - dialogHeight); let newLeft = Math.max(minLeft, Math.min(maxLeft, startLeft + (moveEvent.clientX - startX))); let newTop = Math.max(minTop, Math.min(maxTop, startTop + (moveEvent.clientY - startY))); // Snap to center position when close const centerLeft = (containerDimension.width - dialogWidth) / 2; - const centerTop = titleBarOffset + (availableHeight - dialogHeight) / 2; + const centerTop = Math.max(titleBarOffset, (containerDimension.height - dialogHeight) / 2); if (Math.abs(newLeft - centerLeft) < MODAL_SNAP_THRESHOLD && Math.abs(newTop - centerTop) < MODAL_SNAP_THRESHOLD) { newLeft = centerLeft; @@ -381,9 +383,8 @@ export class ModalEditorPart { // Check if snapped to center — if so, clear custom position const containerDimension = this.layoutService.mainContainerDimension; const titleBarOffset = this.layoutService.mainContainerOffset.top; - const availableHeight = Math.max(containerDimension.height - titleBarOffset, 0); const centerLeft = (containerDimension.width - resizableElement.size.width) / 2; - const centerTop = titleBarOffset + (availableHeight - resizableElement.size.height) / 2; + const centerTop = Math.max(titleBarOffset, (containerDimension.height - resizableElement.size.height) / 2); if (Math.abs(currentLeft - centerLeft) < 1 && Math.abs(currentTop - centerTop) < 1) { editorPart.position = undefined; @@ -516,9 +517,7 @@ export class ModalEditorPart { resizableElement.domNode.style.top = `${clampedTop}px`; } else { const left = (containerDimension.width - width) / 2; - const top = editorPart.maximized - ? (containerDimension.height - height) / 2 // center in full window to stay close to title bar - : titleBarOffset + (availableHeight - height) / 2; + const top = Math.max(titleBarOffset, (containerDimension.height - height) / 2); // center in full window, but clamp to stay below the title bar resizableElement.domNode.style.left = `${left}px`; resizableElement.domNode.style.top = `${top}px`; } @@ -615,6 +614,12 @@ class ModalEditorPartImpl extends EditorPart implements IModalEditorPart { } this.enforceModalPartOptions(); + + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(USE_MODAL_EDITOR_SETTING)) { + this.enforceModalPartOptions(); + } + })); } override create(parent: HTMLElement, options?: object): void { @@ -623,12 +628,16 @@ class ModalEditorPartImpl extends EditorPart implements IModalEditorPart { super.create(parent, options); } - private enforceModalPartOptions(): void { + enforceModalPartOptions(): void { + const useModalForAll = this.configurationService.getValue(USE_MODAL_EDITOR_SETTING) === 'all'; + const editorCount = this.groups.reduce((count, group) => count + group.count, 0); + const showTabs = useModalForAll && editorCount > 1 ? 'multiple' : 'none'; + this.optionsDisposable.value = this.enforcePartOptions({ - showTabs: 'none', + showTabs, enablePreview: true, closeEmptyGroups: true, - tabActionCloseVisibility: false, + tabActionCloseVisibility: showTabs !== 'none', editorActionsLocation: 'hidden', tabHeight: 'default', wrapTabs: false, diff --git a/src/vs/workbench/services/editor/test/browser/modalEditorGroup.test.ts b/src/vs/workbench/services/editor/test/browser/modalEditorGroup.test.ts index 763402f42102f..7d37cde03e3cf 100644 --- a/src/vs/workbench/services/editor/test/browser/modalEditorGroup.test.ts +++ b/src/vs/workbench/services/editor/test/browser/modalEditorGroup.test.ts @@ -615,6 +615,64 @@ suite('Modal Editor Group', () => { assert.strictEqual(parts.activeModalEditorPart, undefined); }); + + test('shows tabs when multiple editors are open', async () => { + const instantiationService = workbenchInstantiationService({ contextKeyService: instantiationService => instantiationService.createInstance(MockScopableContextKeyService) }, disposables); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); + const configurationService = new TestConfigurationService(); + await configurationService.setUserConfiguration('workbench.editor.useModal', 'all'); + instantiationService.stub(IConfigurationService, configurationService); + const parts = await createEditorParts(instantiationService, disposables); + instantiationService.stub(IEditorGroupsService, parts); + + const editorService = disposables.add(instantiationService.createInstance(EditorService, undefined)); + instantiationService.stub(IEditorService, editorService); + + const input1 = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + await editorService.openEditor(input1, { pinned: true }, MODAL_GROUP); + + const modalPart = parts.activeModalEditorPart!; + assert.ok(modalPart); + + // With 1 editor, tabs should be hidden + assert.strictEqual(modalPart.partOptions.showTabs, 'none'); + + // Open a second editor + const input2 = createTestFileEditorInput(URI.file('foo/baz'), TEST_EDITOR_INPUT_ID); + await editorService.openEditor(input2, { pinned: true }, MODAL_GROUP); + + // With 2 editors, tabs should be visible + assert.strictEqual(modalPart.partOptions.showTabs, 'multiple'); + + modalPart.close(); + }); + + test('hides tabs when not in all mode even with multiple editors', async () => { + const instantiationService = workbenchInstantiationService({ contextKeyService: instantiationService => instantiationService.createInstance(MockScopableContextKeyService) }, disposables); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); + const configurationService = new TestConfigurationService(); + await configurationService.setUserConfiguration('workbench.editor.useModal', 'some'); + instantiationService.stub(IConfigurationService, configurationService); + const parts = await createEditorParts(instantiationService, disposables); + instantiationService.stub(IEditorGroupsService, parts); + + const editorService = disposables.add(instantiationService.createInstance(EditorService, undefined)); + instantiationService.stub(IEditorService, editorService); + + const input1 = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + await editorService.openEditor(input1, { pinned: true }, MODAL_GROUP); + + const modalPart = parts.activeModalEditorPart!; + assert.ok(modalPart); + + const input2 = createTestFileEditorInput(URI.file('foo/baz'), TEST_EDITOR_INPUT_ID); + await editorService.openEditor(input2, { pinned: true }, MODAL_GROUP); + + // With 'some' mode, tabs should remain hidden even with multiple editors + assert.strictEqual(modalPart.partOptions.showTabs, 'none'); + + modalPart.close(); + }); }); test('modal editor part editors can be moved to another group', async () => { From 45b8a3d8309a32c07462e385927c343f60b5eee8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 8 Mar 2026 20:06:01 +0100 Subject: [PATCH 2/2] Update src/vs/workbench/browser/parts/editor/modalEditorPart.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/vs/workbench/browser/parts/editor/modalEditorPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/modalEditorPart.ts b/src/vs/workbench/browser/parts/editor/modalEditorPart.ts index 9faca0f31c7f4..db862b6096c01 100644 --- a/src/vs/workbench/browser/parts/editor/modalEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/modalEditorPart.ts @@ -273,7 +273,7 @@ export class ModalEditorPart { setVisibility(hasActions, editorActionsSeparator); }; disposables.add(Event.runAndSubscribe(modalEditorService.onDidActiveEditorChange, () => updateEditorActions())); - disposables.add(modalEditorService.onDidVisibleEditorsChange(() => editorPart.enforceModalPartOptions())); + disposables.add(modalEditorService.onDidEditorsChange(() => editorPart.enforceModalPartOptions())); // Create global toolbar disposables.add(scopedInstantiationService.createInstance(MenuWorkbenchToolBar, actionBarContainer, MenuId.ModalEditorTitle, {