diff --git a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts index 0a17dd5fb810d..80d1943ed2fa8 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts @@ -27,6 +27,7 @@ export class MultiDiffEditorWidget extends Disposable { private readonly _dimension = observableValue(this, undefined); private readonly _viewModel = observableValue(this, undefined); private readonly _renderSideBySide = observableValue(this, undefined); + private readonly _paddingBottomPx = observableValue(this, 0); private readonly _widgetImpl = derived(this, (reader) => { readHotReloadableExport(DiffEditorItemTemplate, reader); @@ -38,6 +39,7 @@ export class MultiDiffEditorWidget extends Disposable { this._workbenchUIElementFactory, this._renderSideBySide, this._diffEditorOptions, + this._paddingBottomPx, )); }); @@ -108,6 +110,11 @@ export class MultiDiffEditorWidget extends Disposable { this._renderSideBySide.set(!(this._renderSideBySide.get() ?? true), undefined); } + /** Reserves empty space below the last diff entry. */ + public setPaddingBottom(px: number): void { + this._paddingBottomPx.set(px, undefined); + } + private readonly _activeControl = derived(this, (reader) => this._widgetImpl.read(reader).activeControl.read(reader)); public getActiveControl(): DiffEditorWidget | undefined { diff --git a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts index 8e93b4f8962b8..693ae82008c82 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts @@ -79,6 +79,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _workbenchUIElementFactory: IWorkbenchUIElementFactory, private readonly _renderSideBySide: IObservable, private readonly _diffEditorOptions: IDiffEditorOptions | undefined, + private readonly _paddingBottomPx: IObservable, @IContextKeyService private readonly _parentContextKeyService: IContextKeyService, @IInstantiationService private readonly _parentInstantiationService: IInstantiationService, ) { @@ -153,7 +154,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { ); this._viewItems = this._viewItemsInfo.map(this, items => items.items); this._spaceBetweenPx = 0; - this._totalHeight = this._viewItems.map(this, (items, reader) => items.reduce((r, i) => r + i.contentHeight.read(reader) + this._spaceBetweenPx, 0)); + this._totalHeight = this._viewItems.map(this, (items, reader) => items.reduce((r, i) => r + i.contentHeight.read(reader) + this._spaceBetweenPx, 0) + this._paddingBottomPx.read(reader)); this.activeControl = derived(this, reader => { const activeDiffItem = this._viewModel.read(reader)?.activeDiffItem.read(reader); if (!activeDiffItem) { return undefined; } diff --git a/src/vs/sessions/contrib/changes/browser/changesActions.ts b/src/vs/sessions/contrib/changes/browser/changesActions.ts index 312182784aec5..4ed08f68f9c99 100644 --- a/src/vs/sessions/contrib/changes/browser/changesActions.ts +++ b/src/vs/sessions/contrib/changes/browser/changesActions.ts @@ -10,6 +10,7 @@ import { structuralEquals } from '../../../../base/common/equals.js'; import { Emitter } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { autorun, derivedOpts, IObservable, observableValue, transaction } from '../../../../base/common/observable.js'; +import { isEqual } from '../../../../base/common/resources.js'; import { URI } from '../../../../base/common/uri.js'; import { localize, localize2 } from '../../../../nls.js'; import { IActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js'; @@ -150,6 +151,20 @@ function getChangesDiffEditor(pane: IEditorPane | undefined, resource: URI): Dif return codeEditor?.diffEditor instanceof DiffEditorWidget ? codeEditor.diffEditor : undefined; } +function getExpandedChangesDiffEditor(pane: IEditorPane | undefined, resource: URI): DiffEditorWidget | undefined { + if (pane instanceof SessionChangesEditor) { + pane.expand(resource); + } else if (pane instanceof MultiDiffEditor) { + const viewModel = pane.viewModel; + const item = viewModel?.items.read(undefined) + .find(i => isEqual(i.modifiedUri, resource) || isEqual(i.originalUri, resource)); + if (viewModel && item) { + viewModel.expand(item); + } + } + return getChangesDiffEditor(pane, resource); +} + /** * Reveals all hidden unchanged regions for the file shown in a diff row of the * Agents window's Changes editor, showing the whole file at once (a per-file @@ -182,7 +197,8 @@ class ExpandFullFileAction extends Action2 { return; } - getChangesDiffEditor(accessor.get(IEditorService).activeEditorPane, resource)?.showAllUnchangedRegions(); + const activePane = accessor.get(IEditorService).activeEditorPane; + getExpandedChangesDiffEditor(activePane, resource)?.showAllUnchangedRegions(); } } registerAction2(ExpandFullFileAction); @@ -223,7 +239,8 @@ class CollapseUnchangedRegionsAction extends Action2 { return; } - getChangesDiffEditor(accessor.get(IEditorService).activeEditorPane, resource)?.collapseAllUnchangedRegions(); + const activePane = accessor.get(IEditorService).activeEditorPane; + getExpandedChangesDiffEditor(activePane, resource)?.collapseAllUnchangedRegions(); } } registerAction2(CollapseUnchangedRegionsAction); diff --git a/src/vs/sessions/contrib/changes/browser/media/multiFileDiffEditor.css b/src/vs/sessions/contrib/changes/browser/media/multiFileDiffEditor.css index 8b7f5564ba23b..97e196ae052e5 100644 --- a/src/vs/sessions/contrib/changes/browser/media/multiFileDiffEditor.css +++ b/src/vs/sessions/contrib/changes/browser/media/multiFileDiffEditor.css @@ -7,39 +7,41 @@ * used by the Changes view / editor). Kept out of the shared workbench style so * these overrides live next to the Changes contribution that owns them. */ -/* The per-file diff rows intentionally render flush with the panel (the old - * `padding: 0 8px` inset was removed as part of the spacing rework); only - * `box-sizing` is kept here. */ +/* Keep each diff card inset from the panel walls. */ .agent-sessions-workbench .part.editor .multiDiffEntry { box-sizing: border-box; + padding: 0 var(--vscode-spacing-size80); } .agent-sessions-workbench .part.editor .multiDiffEntry .header { cursor: pointer; - background: transparent; + background: var(--vscode-multiDiffEditor-background); position: relative; } -.agent-sessions-workbench .part.editor .multiDiffEntry .header::before { - content: ''; - position: absolute; - inset: 0; - background: var(--vscode-agentsPanel-background); - z-index: -1; - pointer-events: none; -} - +/* The header and editor borders form one continuous card. */ .agent-sessions-workbench .part.editor .multiDiffEntry .header-content { - border-radius: var(--vscode-cornerRadius-medium); - border: none; + border-top-left-radius: var(--vscode-cornerRadius-medium); + border-top-right-radius: var(--vscode-cornerRadius-medium); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border: var(--vscode-strokeThickness) solid var(--vscode-widget-border, var(--vscode-editorWidget-border, transparent)); + border-bottom: none; background: var(--vscode-sideBarSectionHeader-background); - margin: 0; + margin: var(--vscode-spacing-size80) 0 0 0; padding-left: var(--vscode-spacing-sizeNone); align-items: center; } -.agent-sessions-workbench .part.editor .multiDiffEntry .header:not(.collapsed) .header-content { - border-bottom: none; +/* A collapsed header is the whole card. */ +.agent-sessions-workbench .part.editor .multiDiffEntry .header[aria-expanded="false"] .header-content { + border-bottom: var(--vscode-strokeThickness) solid var(--vscode-widget-border, var(--vscode-editorWidget-border, transparent)); + border-bottom-left-radius: var(--vscode-cornerRadius-medium); + border-bottom-right-radius: var(--vscode-cornerRadius-medium); +} + +.agent-sessions-workbench .part.editor .multiDiffEntry .header[aria-expanded="false"] + .editorParent { + border: none; } .agent-sessions-workbench .part.editor .multiDiffEntry .header-content .file-path .title { @@ -85,17 +87,58 @@ outline: none; } -.agent-sessions-workbench .part.editor .multiDiffEntry .header:focus .header-content, -.agent-sessions-workbench .part.editor .multiDiffEntry .header:focus-visible .header-content { - box-shadow: inset 0 0 0 1px var(--vscode-focusBorder); +/* Keep secondary actions quiet until the card is hovered or focused. */ +.agent-sessions-workbench .part.editor .multiDiffEntry .header-content .actions-container .action-item:not(.changeset-review-action) { + visibility: hidden; } +.agent-sessions-workbench .part.editor .multiDiffEntry:hover .header-content .actions-container .action-item:not(.changeset-review-action), +.agent-sessions-workbench .part.editor .multiDiffEntry:focus-within .header-content .actions-container .action-item:not(.changeset-review-action) { + visibility: visible; +} + +/* Match the collapse chevron's target and states to the file toolbar actions. */ .agent-sessions-workbench .part.editor .multiDiffEntry .collapse-button a { - border-radius: var(--vscode-cornerRadius-small); + display: flex; + align-items: center; + justify-content: center; + padding: 3px; + border-radius: var(--vscode-cornerRadius-medium); } +.agent-sessions-workbench .part.editor .multiDiffEntry .collapse-button a:hover, +.agent-sessions-workbench .part.editor .multiDiffEntry .collapse-button a:focus { + background: var(--vscode-toolbar-hoverBackground); +} + +.agent-sessions-workbench .part.editor .multiDiffEntry .collapse-button a:focus:not(:focus-visible) { + outline: none; +} + +/* The editor completes the card and clips content to its rounded corners. */ .agent-sessions-workbench .part.editor .multiDiffEntry .editorParent { - border-bottom: none; + border: var(--vscode-strokeThickness) solid var(--vscode-widget-border, var(--vscode-editorWidget-border, transparent)); + border-top: none; + border-bottom-left-radius: var(--vscode-cornerRadius-medium); + border-bottom-right-radius: var(--vscode-cornerRadius-medium); + overflow: hidden; +} + +/* Keep the active card quiet while preserving a clear keyboard focus indicator. */ +.agent-sessions-workbench .part.editor .multiDiffEntry:focus-within .header .header-content, +.agent-sessions-workbench .part.editor .multiDiffEntry:focus-within .editorParent { + border-color: color-mix(in srgb, var(--vscode-focusBorder) 40%, transparent); +} + +.agent-sessions-workbench .part.editor .multiDiffEntry .header:focus-visible .header-content { + border-color: var(--vscode-focusBorder); +} + +.hc-black .agent-sessions-workbench .part.editor .multiDiffEntry:focus-within .header .header-content, +.hc-black .agent-sessions-workbench .part.editor .multiDiffEntry:focus-within .editorParent, +.hc-light .agent-sessions-workbench .part.editor .multiDiffEntry:focus-within .header .header-content, +.hc-light .agent-sessions-workbench .part.editor .multiDiffEntry:focus-within .editorParent { + border-color: var(--vscode-contrastActiveBorder, var(--vscode-focusBorder)); } .agent-sessions-workbench .part.editor .diff-hidden-lines .center { diff --git a/src/vs/sessions/contrib/changes/browser/sessionChangesEditor.ts b/src/vs/sessions/contrib/changes/browser/sessionChangesEditor.ts index 00fa50da668f9..d3cec29e6f512 100644 --- a/src/vs/sessions/contrib/changes/browser/sessionChangesEditor.ts +++ b/src/vs/sessions/contrib/changes/browser/sessionChangesEditor.ts @@ -64,6 +64,9 @@ const CHANGES_DIFF_EDITOR_OPTIONS: IDiffEditorOptions = { lineNumbersMinChars: 3, }; +/** Keeps the overlay scrollbar clear of the final diff card. */ +const CHANGES_LIST_BOTTOM_PADDING_PX = 24; + class SessionChangesUIElementFactory implements IWorkbenchUIElementFactory { readonly headerClickToCollapse = true; @@ -237,6 +240,7 @@ export class SessionChangesEditor extends AbstractEditorWithViewState { if (e.affectsConfiguration('diffEditor.renderSideBySide')) {