Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/vs/workbench/browser/parts/editor/editorPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export class EditorPart extends Part<IEditorPartMemento> 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,
Expand Down
37 changes: 23 additions & 14 deletions src/vs/workbench/browser/parts/editor/modalEditorPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -139,10 +141,10 @@ export class ModalEditorPart {
}
}));

let useModalMode = this.configurationService.getValue<string>('workbench.editor.useModal');
let useModalMode = this.configurationService.getValue<string>(USE_MODAL_EDITOR_SETTING);
disposables.add(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('workbench.editor.useModal')) {
useModalMode = this.configurationService.getValue<string>('workbench.editor.useModal');
if (e.affectsConfiguration(USE_MODAL_EDITOR_SETTING)) {
useModalMode = this.configurationService.getValue<string>(USE_MODAL_EDITOR_SETTING);
}
}));

Expand Down Expand Up @@ -271,6 +273,7 @@ export class ModalEditorPart {
setVisibility(hasActions, editorActionsSeparator);
};
disposables.add(Event.runAndSubscribe(modalEditorService.onDidActiveEditorChange, () => updateEditorActions()));
disposables.add(modalEditorService.onDidEditorsChange(() => editorPart.enforceModalPartOptions()));

// Create global toolbar
disposables.add(scopedInstantiationService.createInstance(MenuWorkbenchToolBar, actionBarContainer, MenuId.ModalEditorTitle, {
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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`;
}
Expand Down Expand Up @@ -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 {
Expand All @@ -623,12 +628,16 @@ class ModalEditorPartImpl extends EditorPart implements IModalEditorPart {
super.create(parent, options);
}

private enforceModalPartOptions(): void {
enforceModalPartOptions(): void {
const useModalForAll = this.configurationService.getValue<string>(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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<IEditorFactoryRegistry>(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<IEditorFactoryRegistry>(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');

Comment thread
bpasero marked this conversation as resolved.
modalPart.close();
});
});

test('modal editor part editors can be moved to another group', async () => {
Expand Down
Loading