mirror of
https://github.com/microsoft/vscode.git
synced 2026-10-01 20:03:17 +01:00
feat: enhance editor and chat UI with new styles and layout adjustments
This commit is contained in:
@@ -1092,6 +1092,7 @@
|
||||
"--modern-ui-editor-tab-unfocused-hover-foreground",
|
||||
"--modern-ui-editor-tab-unfocused-inactive-background",
|
||||
"--modern-ui-editor-tab-unfocused-inactive-foreground",
|
||||
"--modern-ui-editor-tabs-border",
|
||||
"--modern-ui-floating-card-border-color",
|
||||
"--modern-ui-floating-card-corner-bottom-left-border-image",
|
||||
"--modern-ui-floating-card-corner-bottom-right-border-image",
|
||||
@@ -1114,6 +1115,7 @@
|
||||
"--modern-ui-shell-background",
|
||||
"--modern-ui-tab-active-background",
|
||||
"--modern-ui-tab-hover-background",
|
||||
"--search-editor-query-layout-offset",
|
||||
"--scroll-shadow-surface",
|
||||
"--vscode-chat-list-background",
|
||||
"--vscode-chat-persistent-content-height",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
/* Tabs host: the chat tab strip, shown only when the session has multiple chats. */
|
||||
.chat-composite-bar.session-chat-tabs-bar {
|
||||
padding: 0 var(--vscode-spacing-size100);
|
||||
padding: 0 var(--vscode-spacing-size20);
|
||||
box-sizing: border-box;
|
||||
container-type: inline-size;
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
.chat-composite-bar-tabs-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 35px;
|
||||
height: var(--vscode-spacing-size320, 32px);
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,61 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.agent-sessions-workbench.dock-detail-panel .part.editor .editor-group-container > .title.tabs {
|
||||
--modern-ui-editor-tabs-border: var(--vscode-editorGroupHeader-tabsBorder, var(--vscode-editorGroup-border));
|
||||
}
|
||||
|
||||
.agent-sessions-workbench.dock-detail-panel .part.editor .browser-root > .browser-navbar {
|
||||
box-sizing: border-box;
|
||||
min-height: var(--vscode-spacing-size320);
|
||||
padding: var(--vscode-spacing-size40);
|
||||
gap: var(--vscode-spacing-size40);
|
||||
border-bottom: var(--vscode-strokeThickness) solid var(--vscode-editorGroupHeader-tabsBorder, var(--vscode-editorGroup-border));
|
||||
}
|
||||
|
||||
.agent-sessions-workbench.dock-detail-panel .part.editor .editor-tabs-compact-height .browser-root > .browser-navbar {
|
||||
min-height: var(--vscode-spacing-size280);
|
||||
padding-block: var(--vscode-spacing-size20);
|
||||
}
|
||||
|
||||
.agent-sessions-workbench.dock-detail-panel .part.editor .search-editor > .query-container {
|
||||
--search-editor-query-layout-offset: var(--vscode-spacing-size80, 8px);
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
column-gap: var(--vscode-spacing-size40);
|
||||
box-sizing: border-box;
|
||||
min-height: var(--vscode-spacing-size320);
|
||||
margin: 0;
|
||||
padding: var(--vscode-spacing-size20) var(--vscode-spacing-size40);
|
||||
border-bottom: var(--vscode-strokeThickness) solid var(--vscode-editorGroupHeader-tabsBorder, var(--vscode-editorGroup-border));
|
||||
}
|
||||
|
||||
.agent-sessions-workbench.dock-detail-panel .part.editor .editor-tabs-compact-height .search-editor > .query-container {
|
||||
min-height: var(--vscode-spacing-size280);
|
||||
padding-block: var(--vscode-spacing-sizeNone);
|
||||
}
|
||||
|
||||
.agent-sessions-workbench.dock-detail-panel .part.editor .search-editor > .query-container > .search-widget {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.agent-sessions-workbench.dock-detail-panel .part.editor .search-editor > .query-container > .includes-excludes {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.agent-sessions-workbench.dock-detail-panel .part.editor .search-editor > .query-container > .includes-excludes:not(.expanded) > .expand {
|
||||
position: static;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.agent-sessions-workbench.dock-detail-panel .part.editor .search-editor > .query-container > .includes-excludes.expanded {
|
||||
grid-column: 1 / -1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Editor Layout Actions Toolbar */
|
||||
|
||||
.agent-sessions-workbench .part.editor > .content .editor-group-container > .title .editor-actions {
|
||||
|
||||
@@ -122,7 +122,7 @@ export class NewBrowserTabAction extends Action2 {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const browserInput = browserViewWorkbenchService.getOrCreateLazy({ id: generateUuid() });
|
||||
|
||||
await editorService.openEditor(browserInput);
|
||||
await editorService.openEditor(browserInput, { pinned: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,15 @@
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-height: 29px;
|
||||
min-height: var(--vscode-spacing-size320);
|
||||
overflow: hidden;
|
||||
padding: var(--vscode-spacing-size20, 2px) var(--vscode-spacing-size40, 4px);
|
||||
}
|
||||
|
||||
.agent-sessions-workbench.dock-detail-panel .part.editor .editor-group-container.editor-tabs-compact-height .title > .editor-group-header {
|
||||
min-height: var(--vscode-spacing-size280);
|
||||
}
|
||||
|
||||
.agent-sessions-workbench.dock-detail-panel .part.editor .editor-group-header-actions {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
|
||||
@@ -27,6 +27,8 @@ import { IEditorService } from '../../../../../workbench/services/editor/common/
|
||||
import { IEditorGroup, IEditorGroupsService } from '../../../../../workbench/services/editor/common/editorGroupsService.js';
|
||||
import { generateColorThemeCSS } from '../../../../../workbench/services/themes/browser/colorThemeCss.js';
|
||||
import { ColorThemeData } from '../../../../../workbench/services/themes/common/colorThemeData.js';
|
||||
import { BrowserEditorInput } from '../../../../../workbench/contrib/browserView/common/browserEditorInput.js';
|
||||
import { IBrowserViewWorkbenchService } from '../../../../../workbench/contrib/browserView/common/browserView.js';
|
||||
import { TERMINAL_VIEW_ID } from '../../../../../workbench/contrib/terminal/common/terminal.js';
|
||||
import { openNewSearchEditor } from '../../../../../workbench/contrib/searchEditor/browser/searchEditorActions.js';
|
||||
import { IAgentWorkbenchLayoutService } from '../../../../browser/workbench.js';
|
||||
@@ -34,7 +36,7 @@ import { ISessionWorkspace } from '../../../../services/sessions/common/session.
|
||||
import { IActiveSession } from '../../../../services/sessions/common/sessionsManagement.js';
|
||||
import { ISessionsService } from '../../../../services/sessions/browser/sessionsService.js';
|
||||
import { ISessionChangesService } from '../../../changes/browser/sessionChangesService.js';
|
||||
import { NewChangesTabAction, NewFileTabAction, NewSearchTabAction } from '../../browser/addTabActions.js';
|
||||
import { NewBrowserTabAction, NewChangesTabAction, NewFileTabAction, NewSearchTabAction } from '../../browser/addTabActions.js';
|
||||
import { EmptyFileEditorInput, EmptyFileEditorSerializer } from '../../browser/emptyFileEditorInput.js';
|
||||
import { EditorTabsVisibleContext, IsAuxiliaryWindowContext, IsSessionsWindowContext, IsTopRightEditorGroupContext } from '../../../../../workbench/common/contextkeys.js';
|
||||
import { TestEnvironmentService } from '../../../../../workbench/test/browser/workbenchTestServices.js';
|
||||
@@ -123,6 +125,30 @@ suite('Sessions - Editor Contribution', () => {
|
||||
})), [{ isEmptyFileEditor: true, resource: workspaceFolder.toString(), pinned: true, index: 7 }]);
|
||||
});
|
||||
|
||||
test('new browser tab action opens a pinned browser editor', async () => {
|
||||
const instantiationService = store.add(new TestInstantiationService());
|
||||
const browserInput = new class extends mock<BrowserEditorInput>() { };
|
||||
const opened: { editor: unknown; options: IEditorOptions | undefined }[] = [];
|
||||
instantiationService.stub(IBrowserViewWorkbenchService, new class extends mock<IBrowserViewWorkbenchService>() {
|
||||
override getOrCreateLazy(): BrowserEditorInput {
|
||||
return browserInput;
|
||||
}
|
||||
});
|
||||
instantiationService.stub(IEditorService, new class extends mock<IEditorService>() {
|
||||
override async openEditor(...args: unknown[]): Promise<undefined> {
|
||||
opened.push({ editor: args[0], options: args[1] as IEditorOptions | undefined });
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
await new NewBrowserTabAction().run(instantiationService);
|
||||
|
||||
assert.deepStrictEqual(opened.map(({ editor, options }) => ({
|
||||
isBrowserEditor: editor === browserInput,
|
||||
pinned: options?.pinned,
|
||||
})), [{ isBrowserEditor: true, pinned: true }]);
|
||||
});
|
||||
|
||||
test('Add Tab menu stays available in dock-only mode', () => {
|
||||
const getWhen = (action: NewFileTabAction | NewChangesTabAction | NewSearchTabAction): ContextKeyExpression => {
|
||||
const menu = action.desc.menu;
|
||||
|
||||
@@ -80,12 +80,12 @@ MenuRegistry.appendMenuItem(addTabMenu, {
|
||||
group: 'navigation',
|
||||
});
|
||||
|
||||
function renderHeader(ctx: ComponentFixtureContext, breadcrumbs: boolean, primaryAction: boolean, secondaryAction = false, layoutActions = false, showTabs: 'multiple' | 'single' | 'none' = 'multiple', addTab = false): void {
|
||||
function renderHeader(ctx: ComponentFixtureContext, breadcrumbs: boolean, primaryAction: boolean, secondaryAction = false, layoutActions = false, showTabs: 'multiple' | 'single' | 'none' = 'multiple', addTab = false, tabHeight: 'default' | 'compact' = 'default'): void {
|
||||
ctx.container.classList.add('agent-sessions-workbench', 'dock-detail-panel');
|
||||
|
||||
renderEditorTabBarFixture(ctx, {
|
||||
modernUI: true,
|
||||
partOptions: { showTabs },
|
||||
partOptions: { showTabs, tabHeight },
|
||||
breadcrumbs: breadcrumbs ? { filePath: 'on', icons: true } : undefined,
|
||||
showHeader: true,
|
||||
headerMenuIds: {
|
||||
@@ -98,7 +98,8 @@ function renderHeader(ctx: ComponentFixtureContext, breadcrumbs: boolean, primar
|
||||
}
|
||||
|
||||
export default defineThemedFixtureGroup({ path: 'sessions/editorHeader/' }, {
|
||||
FullHeader: defineComponentFixture({ render: ctx => renderHeader(ctx, true, true, true, true) }),
|
||||
FullHeader: defineComponentFixture({ render: ctx => renderHeader(ctx, true, true, true, true), additionalThemes: ['darkHighContrast'] }),
|
||||
CompactFullHeader: defineComponentFixture({ render: ctx => renderHeader(ctx, true, true, true, true, 'multiple', false, 'compact'), additionalThemes: ['darkHighContrast'] }),
|
||||
BreadcrumbsAndAction: defineComponentFixture({ render: ctx => renderHeader(ctx, true, true) }),
|
||||
BreadcrumbsAndSecondaryAction: defineComponentFixture({ render: ctx => renderHeader(ctx, true, false, true) }),
|
||||
BreadcrumbsOnly: defineComponentFixture({ render: ctx => renderHeader(ctx, true, false) }),
|
||||
|
||||
@@ -110,6 +110,7 @@ interface IChatCompositeBarHarness {
|
||||
readonly commandService: TestCommandService;
|
||||
readonly sessionsService: TestSessionsService;
|
||||
readonly bar: ChatCompositeBar;
|
||||
readonly container: HTMLElement;
|
||||
readonly session: IActiveSession;
|
||||
readonly tabs: readonly HTMLElement[];
|
||||
readonly chats: ISettableObservable<readonly IChat[]>;
|
||||
@@ -158,7 +159,7 @@ function createHarness(disposables: Pick<DisposableStore, 'add'>, options?: { re
|
||||
container.appendChild(bar.element);
|
||||
const tabs = Array.from(bar.element.querySelectorAll<HTMLElement>('.chat-composite-bar-tab'));
|
||||
|
||||
return { store, instantiationService, commandService, sessionsService, bar, session, tabs, chats, activeChatResource, visible, showSessionActions };
|
||||
return { store, instantiationService, commandService, sessionsService, bar, container, session, tabs, chats, activeChatResource, visible, showSessionActions };
|
||||
}
|
||||
|
||||
suite('Sessions - ChatCompositeBar', () => {
|
||||
@@ -191,6 +192,24 @@ suite('Sessions - ChatCompositeBar', () => {
|
||||
assert.strictEqual(bar.element.querySelector('.chat-composite-bar-new-chat'), null);
|
||||
});
|
||||
|
||||
test('matches the editor tab strip height', () => {
|
||||
const { bar, container } = createHarness(disposables);
|
||||
mainWindow.document.body.appendChild(container);
|
||||
|
||||
try {
|
||||
const tabsRow = bar.element.querySelector<HTMLElement>('.chat-composite-bar-tabs-row');
|
||||
assert.deepStrictEqual({
|
||||
barHeight: mainWindow.getComputedStyle(bar.element).height,
|
||||
tabsRowHeight: tabsRow && mainWindow.getComputedStyle(tabsRow).height,
|
||||
}, {
|
||||
barHeight: '32px',
|
||||
tabsRowHeight: '32px',
|
||||
});
|
||||
} finally {
|
||||
container.remove();
|
||||
}
|
||||
});
|
||||
|
||||
test('updates active, visibility, and session action state without rebuilding tabs', () => {
|
||||
const { activeChatResource, bar, showSessionActions, tabs, visible } = createHarness(disposables);
|
||||
const secondaryResource = tabs[1].dataset.chatResource!;
|
||||
@@ -236,8 +255,8 @@ suite('Sessions - ChatCompositeBar', () => {
|
||||
const observedHeights: number[] = [];
|
||||
disposables.add(bar.onDidChangeHeight(() => observedHeights.push(bar.height)));
|
||||
|
||||
resizeObserver.fire(35);
|
||||
resizeObserver.fire(35);
|
||||
resizeObserver.fire(32);
|
||||
resizeObserver.fire(32);
|
||||
resizeObserver.fire(0);
|
||||
|
||||
assert.deepStrictEqual({
|
||||
@@ -246,7 +265,7 @@ suite('Sessions - ChatCompositeBar', () => {
|
||||
observedBox: resizeObserver.observedBox,
|
||||
}, {
|
||||
height: 0,
|
||||
observedHeights: [35, 0],
|
||||
observedHeights: [32, 0],
|
||||
observedBox: 'border-box',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,8 @@ import { IEditorGroupMenuIds, IEditorGroupsView, IEditorGroupView } from './edit
|
||||
|
||||
export class EditorHeaderControl extends Disposable {
|
||||
|
||||
static readonly HEIGHT = 29;
|
||||
static readonly DEFAULT_HEIGHT = 32;
|
||||
static readonly COMPACT_HEIGHT = 28;
|
||||
|
||||
private readonly headerContainer: HTMLElement | undefined;
|
||||
private readonly actionsContainer: HTMLElement | undefined;
|
||||
@@ -33,7 +34,10 @@ export class EditorHeaderControl extends Disposable {
|
||||
|
||||
get height(): number {
|
||||
if (this.headerContainer) {
|
||||
return this.visible ? EditorHeaderControl.HEIGHT : 0;
|
||||
if (!this.visible) {
|
||||
return 0;
|
||||
}
|
||||
return this.groupsView.partOptions.tabHeight === 'compact' ? EditorHeaderControl.COMPACT_HEIGHT : EditorHeaderControl.DEFAULT_HEIGHT;
|
||||
}
|
||||
return this.breadcrumbsControl?.isHidden() === false ? BreadcrumbsControl.HEIGHT : 0;
|
||||
}
|
||||
@@ -41,7 +45,7 @@ export class EditorHeaderControl extends Disposable {
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
private readonly groupView: IEditorGroupView,
|
||||
groupsView: IEditorGroupsView,
|
||||
private readonly groupsView: IEditorGroupsView,
|
||||
private readonly menuIds: IEditorGroupMenuIds | undefined,
|
||||
showHeader: boolean,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
|
||||
@@ -646,10 +646,12 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC
|
||||
}
|
||||
|
||||
protected updateTabHeight(): void {
|
||||
const isCompact = this.groupsView.partOptions.tabHeight === 'compact';
|
||||
this.parent.style.setProperty('--editor-group-tab-height', `${this.tabHeight}px`);
|
||||
// Signal compact mode via a CSS class so the modern tab rules in tabs.css
|
||||
// can apply a proportionally smaller --editor-group-tab-height value.
|
||||
this.parent.classList.toggle('compact-height', this.groupsView.partOptions.tabHeight === 'compact');
|
||||
this.parent.classList.toggle('compact-height', isCompact);
|
||||
this.parent.parentElement?.classList.toggle('editor-tabs-compact-height', isCompact);
|
||||
}
|
||||
|
||||
private updateTabActionSpaceReservation(): void {
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
--modern-ui-editor-tab-action-unfocused-hover-background: var(--modern-ui-editor-tab-action-hover-background);
|
||||
--modern-ui-editor-tab-action-active-hover-background: var(--vscode-modernEditorTab-activeHoverActionBackground);
|
||||
--modern-ui-editor-tab-action-unfocused-active-hover-background: var(--modern-ui-editor-tab-action-active-hover-background);
|
||||
--modern-ui-editor-tabs-border: transparent;
|
||||
}
|
||||
|
||||
.modern-ui-tabs.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs {
|
||||
@@ -188,11 +189,28 @@
|
||||
|
||||
/* Center tabs vertically and strip the bottom border so the pills float. */
|
||||
.modern-ui-tabs .part.editor .tabs-and-actions-container {
|
||||
--tabs-border-bottom-color: transparent !important;
|
||||
--tabs-border-bottom-color: var(--modern-ui-editor-tabs-border) !important;
|
||||
align-items: center;
|
||||
padding: var(--vscode-spacing-size20) 0 0 var(--vscode-spacing-size20);
|
||||
}
|
||||
|
||||
.modern-ui-tabs .part.editor .title:not(.two-tab-bars) > .tabs-and-actions-container::after,
|
||||
.modern-ui-tabs .part.editor .title.two-tab-bars > .tabs-and-actions-container:not(:first-child)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 9;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
height: var(--vscode-strokeThickness);
|
||||
background-color: var(--tabs-border-bottom-color);
|
||||
}
|
||||
|
||||
.modern-ui-tabs .part.editor .title.two-tab-bars > .tabs-and-actions-container:first-child.tabs-border-bottom::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* In two-tab-bars mode the border-block-width rules below own every vertical gutter, so the
|
||||
* container must not add its own top padding to either the pinned or the unpinned row. */
|
||||
.modern-ui-tabs .part.editor .title.two-tab-bars > .tabs-and-actions-container {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
}
|
||||
|
||||
.search-editor .query-container {
|
||||
--search-editor-query-layout-offset: 28px;
|
||||
margin: 0px 12px 12px 19px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ import { ISearchResult } from '../../search/browser/searchTreeModel/searchTreeCo
|
||||
|
||||
const RESULT_LINE_REGEX = /^(\s+)(\d+)(: | )(\s*)(.*)$/;
|
||||
const FILE_LINE_REGEX = /^(\S.*):$/;
|
||||
const DEFAULT_QUERY_EDITOR_LAYOUT_OFFSET = 28;
|
||||
|
||||
type SearchEditorViewState = ICodeEditorViewState & { focused: 'input' | 'editor' };
|
||||
|
||||
@@ -677,10 +678,12 @@ export class SearchEditor extends AbstractTextCodeEditor<SearchEditorViewState>
|
||||
|
||||
private reLayout() {
|
||||
if (this.dimension) {
|
||||
this.queryEditorWidget.setWidth(this.dimension.width - 28 /* container margin */);
|
||||
const configuredOffset = Number.parseFloat(DOM.getWindow(this.queryEditorContainer).getComputedStyle(this.queryEditorContainer).getPropertyValue('--search-editor-query-layout-offset'));
|
||||
const queryEditorWidth = this.dimension.width - (Number.isFinite(configuredOffset) ? configuredOffset : DEFAULT_QUERY_EDITOR_LAYOUT_OFFSET);
|
||||
this.queryEditorWidget.setWidth(queryEditorWidth);
|
||||
this.searchResultEditor.layout({ height: this.dimension.height - DOM.getTotalHeight(this.queryEditorContainer), width: this.dimension.width });
|
||||
this.inputPatternExcludes.setWidth(this.dimension.width - 28 /* container margin */);
|
||||
this.inputPatternIncludes.setWidth(this.dimension.width - 28 /* container margin */);
|
||||
this.inputPatternExcludes.setWidth(queryEditorWidth);
|
||||
this.inputPatternIncludes.setWidth(queryEditorWidth);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user