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
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,13 @@
content: none;
}

/* Terminal shoulders turn inside the available slot rather than being cut off. */
.modern-ui.modern-ui-connected-editor-tabs.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs .tabs-container > .tab.active:not(.connected-tab-upper-row):is(:last-child, .last-in-row, .connected-tab-right-edge) > :is(.tab-fill, .tab-connected-edge) {
right: var(--modern-ui-connected-tab-shoulder-radius);
/* Terminal shoulders keep additional space rather than being cut off. */
.modern-ui.modern-ui-connected-editor-tabs.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs .tabs-container > .tab.active:not(.connected-tab-upper-row):is(:last-child, .last-in-row) {
Comment thread
Copilot marked this conversation as resolved.
margin-right: var(--modern-ui-connected-tab-shoulder-radius);
}

.modern-ui.modern-ui-connected-editor-tabs.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.wrapping .tabs-container > .tab:last-child {
margin-right: calc(var(--last-tab-margin-right) + var(--modern-ui-connected-tab-shoulder-radius));
}

.modern-ui.modern-ui-connected-editor-tabs.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element > .tab-connected-overflow-edge.connected-tab-right-clipped::after {
Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/contrib/modernUI/browser/media/tabs.css
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@
padding-left: var(--vscode-spacing-size80) !important;
}

.modern-ui-tabs .part.editor .tabs-and-actions-container.wrapping .tabs-container > .tab:last-child {
margin-right: calc(var(--last-tab-margin-right) + var(--vscode-spacing-size40)) !important;
.modern-ui-tabs.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab:last-child {
margin-right: calc(var(--last-tab-margin-right) + var(--vscode-spacing-size40));
}

.modern-ui-tabs.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2273,7 +2273,7 @@ suite('ModernUIContribution', () => {
}
});

test('paints connected tab strokes outside the fill without moving tab content', () => {
test('reserves the connected terminal shoulder without moving tab content', () => {
const root = document.createElement('div');
root.style.setProperty('--vscode-spacing-size20', '2px');
root.style.setProperty('--vscode-spacing-size40', '4px');
Expand Down Expand Up @@ -2313,6 +2313,8 @@ suite('ModernUIContribution', () => {
tab.classList.add('active');
const activeFillBounds = fill.getBoundingClientRect();
const activeFillStyle = targetWindow.getComputedStyle(fill);
const shoulderWidth = targetWindow.getComputedStyle(fill, '::after').width;
const activeTabStyle = targetWindow.getComputedStyle(tab);

assert.deepStrictEqual({
tabBounds: tab.getBoundingClientRect().toJSON(),
Expand All @@ -2322,11 +2324,13 @@ suite('ModernUIContribution', () => {
fillBounds.left - activeFillBounds.left,
activeFillBounds.right - fillBounds.right,
],
marginRight: activeTabStyle.marginRight,
topRadius: activeFillStyle.borderTopLeftRadius,
}, {
tabBounds: tabBounds.toJSON(),
labelBounds: labelBounds.toJSON(),
fillExpansion: [0, 0, connected ? -5 : 0],
fillExpansion: [0, 0, 0],
marginRight: connected ? shoulderWidth : '0px',
topRadius: connected ? '5px' : '4px',
}, JSON.stringify({ classes, theme, activeGroup, compact }));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ suite('MultiEditorTabsControl', () => {
await new Promise<void>(resolve => disposables.add(scheduleAtNextAnimationFrame(mainWindow, () => resolve())));
};
const results = [];
for (const { width, from } of [{ width: 240, from: 3 }, { width: 167, from: 0 }, { width: 120, from: 0 }]) {
for (const { width, from } of [{ width: 240, from: 3 }, { width: 172, from: 0 }, { width: 120, from: 0 }]) {
await reveal(from, width);
await reveal(1, width);
const tab = container.querySelector<HTMLElement>('.tab.active')!;
Expand All @@ -418,7 +418,7 @@ suite('MultiEditorTabsControl', () => {
}, {
results: [
{ width: 240, leftShoulderVisible: true, rightShoulderVisible: true },
{ width: 167, leftShoulderVisible: true, rightShoulderVisible: true },
{ width: 172, leftShoulderVisible: true, rightShoulderVisible: true },
{ width: 120, leftShoulderVisible: true, rightShoulderVisible: false },
],
firstTabFlush: true,
Expand Down Expand Up @@ -472,7 +472,7 @@ suite('MultiEditorTabsControl', () => {
assert.deepStrictEqual(results, [4, 3, 2].map(count => ({
clampedBeforeLayout: true,
activeIndex: count - 1,
scrollLeft: count * 160 - 200,
scrollLeft: count * 160 - 200 + 5,
fillLeft: (count - 1) * 160,
hidden: false,
fillDisplay: 'block',
Expand Down Expand Up @@ -518,6 +518,30 @@ suite('MultiEditorTabsControl', () => {
assert.deepStrictEqual({ wrapped, upper, unwrapped }, { wrapped: [true, false], upper: { inset: '-2px', shoulder: 'none' }, unwrapped: [false, false] });
});

test('connected wrapped last tab adds its shoulder to the editor actions margin', async () => {
const group = connectedGroup();
const oldOptions = partOptions;
partOptions = { ...partOptions, wrapTabs: true, tabSizing: 'fixed', tabSizingFixedMinWidth: 120, tabSizingFixedMaxWidth: 120, editorActionsLocation: 'hidden' };
control.updateOptions(oldOptions, partOptions);

await layoutConnectedGroup(group, 150);
const tabsAndActionsContainer = container.querySelector<HTMLElement>('.tabs-and-actions-container')!;
const tabsContainer = container.querySelector<HTMLElement>('.tabs-container')!;
tabsContainer.style.setProperty('--last-tab-margin-right', '17px');
tabsContainer.style.setProperty('--modern-ui-connected-tab-shoulder-radius', '5px');
const lastTab = tabsContainer.querySelector<HTMLElement>('.tab:last-child')!;

assert.deepStrictEqual({
wrapping: tabsAndActionsContainer.classList.contains('wrapping'),
active: lastTab.classList.contains('active'),
margin: mainWindow.getComputedStyle(lastTab).marginRight,
}, {
wrapping: true,
active: false,
margin: '22px',
});
});

test('selected wrapped tabs and focused actions use the document surface on every row', async () => {
const group = connectedGroup();
const root = group.closest('.monaco-workbench')!;
Expand Down Expand Up @@ -918,7 +942,7 @@ suite('MultiEditorTabsControl', () => {
});
});

test('refreshes connected clipping geometry after dirty width changes', async () => {
test('invalidates connected clipping geometry after dirty and capability changes', async () => {
const root = $('.monaco-workbench.modern-ui.modern-ui-tabs.modern-ui-connected-editor-tabs');
root.style.cssText = '--vscode-spacing-size20: 2px; --vscode-spacing-size40: 4px; --vscode-spacing-size60: 6px; --vscode-spacing-size80: 8px; --vscode-spacing-size280: 28px; --vscode-strokeThickness: 1px; --vscode-cornerRadius-small: 4px; --vscode-editor-background: #ffffff; --modern-ui-connected-tab-surface: #333333;';
mainWindow.document.body.appendChild(root);
Expand All @@ -943,49 +967,33 @@ suite('MultiEditorTabsControl', () => {

const tabs = container.querySelector<HTMLElement>('.tabs-container')!;
const [firstTab, activeTab] = tabs.querySelectorAll<HTMLElement>('.tab');
const activeFill = activeTab.querySelector<HTMLElement>('.tab-fill')!;
const overflowEdge = container.querySelector<HTMLElement>('.tab-connected-overflow-edge')!;
const scroll = (left: number) => {
tabs.classList.add('scroll');
tabs.scrollLeft = left;
tabs.dispatchEvent(new UIEvent(EventType.SCROLL));
};
const getLogicalFillRight = () => activeFill.getBoundingClientRect().right - tabs.getBoundingClientRect().left + tabs.scrollLeft;
scroll(0);
const cleanFillRight = getLogicalFillRight();
const firstEditor = model.getEditorByIndex(0) as TestFileEditorInput;
firstEditor.setDirty();
control.updateEditorDirty(firstEditor);
const dirtyFillRight = getLogicalFillRight();
const invalidatedBeforeLayout = overflowEdge.style.left === '' && !activeTab.classList.contains('connected-tab-right-edge');
await new Promise<void>(resolve => disposables.add(scheduleAtNextAnimationFrame(mainWindow, () => resolve())));

const shoulderExtent = Number.parseFloat(mainWindow.getComputedStyle(activeFill, '::after').width);
const targetVisibleRight = (cleanFillRight + dirtyFillRight) / 2 + shoulderExtent;
scroll(targetVisibleRight - tabs.clientWidth);
const visibleRight = tabs.scrollLeft + tabs.clientWidth;
const rightEdge = activeTab.classList.contains('connected-tab-right-edge');
const currentGeometryNeedsEdge = dirtyFillRight + shoulderExtent > visibleRight;
const staleGeometryWouldNeedEdge = cleanFillRight + shoulderExtent > visibleRight;
const rebuiltAfterLayout = overflowEdge.style.left !== '';
firstEditor.capabilities = EditorInputCapabilities.CannotClose;
control.updateEditorCapabilities(firstEditor);
const capabilityUpdateInvalidated = overflowEdge.style.left === '' && !activeTab.classList.contains('connected-tab-right-edge');

assert.deepStrictEqual({
firstTabDirty: firstTab.classList.contains('dirty'),
widthIncreased: dirtyFillRight > cleanFillRight,
invalidatedBeforeLayout,
rightEdge,
currentGeometryNeedsEdge,
staleGeometryWouldNeedEdge,
rebuiltAfterLayout,
capabilityUpdateInvalidated,
}, {
firstTabDirty: true,
widthIncreased: true,
invalidatedBeforeLayout: true,
rightEdge: true,
currentGeometryNeedsEdge: true,
staleGeometryWouldNeedEdge: false,
rebuiltAfterLayout: true,
capabilityUpdateInvalidated: true,
});
});
Expand Down
Loading