Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
254fd04
make it possible to hide edit mode.
benibenj Jan 20, 2026
ac86b00
Git - Do not provide original resource for hidden repositories (#289128)
lszomoru Jan 20, 2026
cb9e108
fix #283701 (#289134)
sandy081 Jan 20, 2026
6d297b7
fix: use right configuration file (#289149)
sandy081 Jan 20, 2026
519d487
fix: Screencast Mode - keyboard overlay timeout (#238860)
newminkyung Jan 20, 2026
1e99235
add filterFontDecoration to decorationProvider (#289146)
aiday-mar Jan 20, 2026
c302ab7
Hide the chat context widget for non-local sessions (background, clou…
DonJayamanne Jan 20, 2026
4311d8c
Merge pull request #289152 from microsoft/benibenj/cooperative-coral
benibenj Jan 20, 2026
0c90923
fix compliation (#289153)
sandy081 Jan 20, 2026
1bda099
More sessionId -> sessionResource adoption
mjbvz Jan 20, 2026
653e4d8
Integrated Browser: Fix buggy Right Click -> Copy into New Window (#2…
jruales Jan 20, 2026
a766d18
Add hover options for `ActionList`, implement for chat model picker (…
meganrogge Jan 20, 2026
31bc1ff
Fix tests
mjbvz Jan 20, 2026
ef8efa2
Merge pull request #289175 from mjbvz/dev/mjbvz/frantic-lizard
mjbvz Jan 20, 2026
a02f43b
Minor chat optimizations and fixes (#289181)
roblourens Jan 20, 2026
7ab682a
agent sessions - add verbose logging to figure out issues (#289192)
bpasero Jan 20, 2026
501401e
refactor: update chat session handling to use sessionResource instead…
bhavyaus Jan 20, 2026
73f17ed
action list hover follow ups (#289185)
meganrogge Jan 20, 2026
b158a78
Agent Sessions - add view all changes action to sessions list (#289142)
lszomoru Jan 20, 2026
22e81e3
Allow setting simpleBrowser.useIntegratedBrowser to be targeted by Ex…
jruales Jan 20, 2026
679ec21
Use session resource to get telemetry id
mjbvz Jan 20, 2026
a461bab
Fixes https://github.com/microsoft/vscode/issues/277133 (#289198)
hediet Jan 20, 2026
ada46cb
Merge pull request #289213 from mjbvz/dev/mjbvz/correct-xerinae
mjbvz Jan 20, 2026
93faffe
Merge pull request #289217 from microsoft/connor4312/fix-app-rerenders
connor4312 Jan 20, 2026
c058856
Add agent diagnostics metrics (#289209)
digitarald Jan 20, 2026
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
20 changes: 11 additions & 9 deletions extensions/git/src/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1127,10 +1127,11 @@ export class Repository implements Disposable {
return undefined;
}

// Since we are inspecting the resource groups
// we have to ensure that the repository state
// is up to date
// await this.status();
// Ignore path that is inside a hidden repository
if (this.isHidden === true) {
this.logger.trace(`[Repository][provideOriginalResource] Repository is hidden: ${uri.toString()}`);
return undefined;
}

// Ignore path that is inside a merge group
if (this.mergeGroup.resourceStates.some(r => pathEquals(r.resourceUri.fsPath, uri.fsPath))) {
Expand Down Expand Up @@ -3293,18 +3294,19 @@ export class StagedResourceQuickDiffProvider implements QuickDiffProvider {
return undefined;
}

// Ignore path that is inside a hidden repository
if (this._repository.isHidden === true) {
this.logger.trace(`[StagedResourceQuickDiffProvider][provideOriginalResource] Repository is hidden: ${uri.toString()}`);
return undefined;
}

// Ignore symbolic links
const stat = await workspace.fs.stat(uri);
if ((stat.type & FileType.SymbolicLink) !== 0) {
this.logger.trace(`[StagedResourceQuickDiffProvider][provideOriginalResource] Resource is a symbolic link: ${uri.toString()}`);
return undefined;
}

// Since we are inspecting the resource groups
// we have to ensure that the repository state
// is up to date
// await this._repository.status();

// Ignore resources that are not in the index group
if (!this._repository.indexGroup.resourceStates.some(r => pathEquals(r.resourceUri.fsPath, uri.fsPath))) {
this.logger.trace(`[StagedResourceQuickDiffProvider][provideOriginalResource] Resource is not part of a index group: ${uri.toString()}`);
Expand Down
3 changes: 2 additions & 1 deletion extensions/simple-browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
"markdownDescription": "%configuration.useIntegratedBrowser.description%",
"scope": "application",
"tags": [
"experimental"
"experimental",
"onExP"
]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class ColorizedBracketPairsDecorationProvider extends Disposable implemen

//#endregion

getDecorationsInRange(range: Range, ownerId?: number, filterOutValidation?: boolean, onlyMinimapDecorations?: boolean): IModelDecoration[] {
getDecorationsInRange(range: Range, ownerId?: number, filterOutValidation?: boolean, filterFontDecorations?: boolean, onlyMinimapDecorations?: boolean): IModelDecoration[] {
if (onlyMinimapDecorations) {
// Bracket pair colorization decorations are not rendered in the minimap
return [];
Expand Down Expand Up @@ -70,7 +70,7 @@ export class ColorizedBracketPairsDecorationProvider extends Disposable implemen
return result;
}

getAllDecorations(ownerId?: number, filterOutValidation?: boolean): IModelDecoration[] {
getAllDecorations(ownerId?: number, filterOutValidation?: boolean, filterFontDecorations?: boolean): IModelDecoration[] {
if (ownerId === undefined) {
return [];
}
Expand All @@ -80,7 +80,8 @@ export class ColorizedBracketPairsDecorationProvider extends Disposable implemen
return this.getDecorationsInRange(
new Range(1, 1, this.textModel.getLineCount(), 1),
ownerId,
filterOutValidation
filterOutValidation,
filterFontDecorations
);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/vs/editor/common/model/decorationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ export interface DecorationProvider {
* @param filterOutValidation If set, it will ignore decorations specific to validation (i.e. warnings, errors).
* @return An array with the decorations
*/
getDecorationsInRange(range: Range, ownerId?: number, filterOutValidation?: boolean): IModelDecoration[];
getDecorationsInRange(range: Range, ownerId?: number, filterOutValidation?: boolean, filterFontDecorations?: boolean): IModelDecoration[];

/**
* Gets all the decorations as an array.
* @param ownerId If set, it will ignore decorations belonging to other owners.
* @param filterOutValidation If set, it will ignore decorations specific to validation (i.e. warnings, errors).
*/
getAllDecorations(ownerId?: number, filterOutValidation?: boolean, onlyMinimapDecorations?: boolean): IModelDecoration[];
getAllDecorations(ownerId?: number, filterOutValidation?: boolean, filterFontDecorations?: boolean, onlyMinimapDecorations?: boolean): IModelDecoration[];

}

Expand Down
8 changes: 4 additions & 4 deletions src/vs/editor/common/model/textModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1819,17 +1819,17 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
const range = new Range(startLineNumber, 1, endLineNumber, endColumn);

const decorations = this._getDecorationsInRange(range, ownerId, filterOutValidation, filterFontDecorations, onlyMarginDecorations);
pushMany(decorations, this._decorationProvider.getDecorationsInRange(range, ownerId, filterOutValidation));
pushMany(decorations, this._fontTokenDecorationsProvider.getDecorationsInRange(range, ownerId, filterOutValidation));
pushMany(decorations, this._decorationProvider.getDecorationsInRange(range, ownerId, filterOutValidation, filterFontDecorations));
pushMany(decorations, this._fontTokenDecorationsProvider.getDecorationsInRange(range, ownerId, filterOutValidation, filterFontDecorations));
return decorations;
}

public getDecorationsInRange(range: IRange, ownerId: number = 0, filterOutValidation: boolean = false, filterFontDecorations: boolean = false, onlyMinimapDecorations: boolean = false, onlyMarginDecorations: boolean = false): model.IModelDecoration[] {
const validatedRange = this.validateRange(range);

const decorations = this._getDecorationsInRange(validatedRange, ownerId, filterOutValidation, filterFontDecorations, onlyMarginDecorations);
pushMany(decorations, this._decorationProvider.getDecorationsInRange(validatedRange, ownerId, filterOutValidation, onlyMinimapDecorations));
pushMany(decorations, this._fontTokenDecorationsProvider.getDecorationsInRange(validatedRange, ownerId, filterOutValidation, onlyMinimapDecorations));
pushMany(decorations, this._decorationProvider.getDecorationsInRange(validatedRange, ownerId, filterOutValidation, filterFontDecorations, onlyMinimapDecorations));
pushMany(decorations, this._fontTokenDecorationsProvider.getDecorationsInRange(validatedRange, ownerId, filterOutValidation, filterFontDecorations, onlyMinimapDecorations));
return decorations;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,31 +118,34 @@ export class TokenizationFontDecorationProvider extends Disposable implements De
this._onDidChangeFont.fire(affectedLineFonts);
}

public getDecorationsInRange(range: Range, ownerId?: number, filterOutValidation?: boolean, onlyMinimapDecorations?: boolean): IModelDecoration[] {
public getDecorationsInRange(range: Range, ownerId?: number, filterOutValidation?: boolean, filterFontDecorations?: boolean, onlyMinimapDecorations?: boolean): IModelDecoration[] {
const startOffsetOfRange = this.textModel.getOffsetAt(range.getStartPosition());
const endOffsetOfRange = this.textModel.getOffsetAt(range.getEndPosition());
const annotations = this._fontAnnotatedString.getAnnotationsIntersecting(new OffsetRange(startOffsetOfRange, endOffsetOfRange));

const decorations: IModelDecoration[] = [];
for (const annotation of annotations) {
const annotationStartPosition = this.textModel.getPositionAt(annotation.range.start);
const annotationEndPosition = this.textModel.getPositionAt(annotation.range.endExclusive);
const range = Range.fromPositions(annotationStartPosition, annotationEndPosition);
const anno = annotation.annotation;
const className = classNameForFontTokenDecorations(anno.fontToken.fontFamily ?? '', anno.fontToken.fontSizeMultiplier ?? 0);
const affectsFont = !!(anno.fontToken.fontFamily || anno.fontToken.fontSizeMultiplier);
const id = anno.decorationId;
decorations.push({
id: id,
options: {
description: 'FontOptionDecoration',
inlineClassName: className,
lineHeight: anno.fontToken.lineHeightMultiplier,
affectsFont
},
ownerId: 0,
range
});
if (!(affectsFont && filterFontDecorations)) {
const annotationStartPosition = this.textModel.getPositionAt(annotation.range.start);
const annotationEndPosition = this.textModel.getPositionAt(annotation.range.endExclusive);
const range = Range.fromPositions(annotationStartPosition, annotationEndPosition);
const anno = annotation.annotation;
const className = classNameForFontTokenDecorations(anno.fontToken.fontFamily ?? '', anno.fontToken.fontSizeMultiplier ?? 0);
const id = anno.decorationId;
decorations.push({
id: id,
options: {
description: 'FontOptionDecoration',
inlineClassName: className,
lineHeight: anno.fontToken.lineHeightMultiplier,
affectsFont
},
ownerId: 0,
range
});
}
}
return decorations;
}
Expand Down
88 changes: 83 additions & 5 deletions src/vs/platform/actionWidget/browser/actionList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { IListAccessibilityProvider, List } from '../../../base/browser/ui/list/
import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js';
import { Codicon } from '../../../base/common/codicons.js';
import { ResolvedKeybinding } from '../../../base/common/keybindings.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { Disposable, toDisposable } from '../../../base/common/lifecycle.js';
import { OS } from '../../../base/common/platform.js';
import { ThemeIcon } from '../../../base/common/themables.js';
import './actionWidget.css';
Expand All @@ -19,6 +19,10 @@ import { IKeybindingService } from '../../keybinding/common/keybinding.js';
import { defaultListStyles } from '../../theme/browser/defaultStyles.js';
import { asCssVariable } from '../../theme/common/colorRegistry.js';
import { ILayoutService } from '../../layout/browser/layoutService.js';
import { IHoverService } from '../../hover/browser/hover.js';
import { MarkdownString } from '../../../base/common/htmlContent.js';
import { HoverPosition } from '../../../base/browser/ui/hover/hoverWidget.js';
import { IHoverWidget } from '../../../base/browser/ui/hover/hover.js';

export const acceptSelectedActionCommand = 'acceptSelectedCodeAction';
export const previewSelectedActionCommand = 'previewSelectedCodeAction';
Expand All @@ -30,13 +34,27 @@ export interface IActionListDelegate<T> {
onFocus?(action: T | undefined): void;
}

/**
* Optional hover configuration shown when focusing/hovering over an action list item.
*/
export interface IActionListItemHover {
/**
* Content to display in the hover.
*/
readonly content?: string;
}

export interface IActionListItem<T> {
readonly item?: T;
readonly kind: ActionListItemKind;
readonly group?: { kind?: unknown; icon?: ThemeIcon; title: string };
readonly disabled?: boolean;
readonly label?: string;
readonly description?: string;
/**
* Optional hover configuration shown when focusing/hovering over the item.
*/
readonly hover?: IActionListItemHover;
readonly keybinding?: ResolvedKeybinding;
canPreview?: boolean | undefined;
readonly hideIcon?: boolean;
Expand Down Expand Up @@ -179,6 +197,9 @@ class ActionItemRenderer<T> implements IListRenderer<IActionListItem<T>, IAction
data.container.title = element.tooltip;
} else if (element.disabled) {
data.container.title = element.label;
} else if (element.hover?.content) {
// Don't show tooltip when hover content is configured - the rich hover will show instead
data.container.title = '';
} else if (actionTitle && previewTitle) {
if (this._supportsPreview && element.canPreview) {
data.container.title = localize({ key: 'label-preview', comment: ['placeholders are keybindings, e.g "F2 to Apply, Shift+F2 to Preview"'] }, "{0} to Apply, {1} to Preview", actionTitle, previewTitle);
Expand Down Expand Up @@ -225,6 +246,8 @@ export class ActionList<T> extends Disposable {

private readonly cts = this._register(new CancellationTokenSource());

private hover: { index: number; hover: IHoverWidget } | undefined;

constructor(
user: string,
preview: boolean,
Expand All @@ -234,6 +257,7 @@ export class ActionList<T> extends Disposable {
@IContextViewService private readonly _contextViewService: IContextViewService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@ILayoutService private readonly _layoutService: ILayoutService,
@IHoverService private readonly _hoverService: IHoverService,
) {
super();
this.domNode = document.createElement('div');
Expand Down Expand Up @@ -298,6 +322,9 @@ export class ActionList<T> extends Disposable {
this._register(this._list.onDidChangeFocus(() => this.onFocus()));
this._register(this._list.onDidChangeSelection(e => this.onListSelection(e)));

// Ensure hover is hidden when ActionList is disposed
this._register(toDisposable(() => this.hideHover()));

this._allMenuItems = items;
this._list.splice(0, this._list.length, this._allMenuItems);

Expand All @@ -313,6 +340,7 @@ export class ActionList<T> extends Disposable {
hide(didCancel?: boolean): void {
this._delegate.onHide(didCancel);
this.cts.cancel();
this.hideHover();
this._contextViewService.hideContextView();
}

Expand All @@ -331,8 +359,7 @@ export class ActionList<T> extends Disposable {
} else {
// For finding width dynamically (not using resize observer)
const itemWidths: number[] = this._allMenuItems.map((_, index): number => {
// eslint-disable-next-line no-restricted-syntax
const element = this.domNode.ownerDocument.getElementById(this._list.getElementID(index));
const element = this._getRowElement(index);
if (element) {
element.style.width = 'auto';
const width = element.getBoundingClientRect().width;
Expand Down Expand Up @@ -393,6 +420,15 @@ export class ActionList<T> extends Disposable {
}
}

private hideHover() {
if (this.hover) {
if (!this.hover.hover.isDisposed) {
this.hover.hover.dispose();
}
this.hover = undefined;
}
}

private onFocus() {
const focused = this._list.getFocus();
if (focused.length === 0) {
Expand All @@ -401,10 +437,52 @@ export class ActionList<T> extends Disposable {
const focusIndex = focused[0];
const element = this._list.element(focusIndex);
this._delegate.onFocus?.(element.item);

// Show hover on focus change
this._showHoverForElement(element, focusIndex);
}

private _getRowElement(index: number): HTMLElement | null {
// eslint-disable-next-line no-restricted-syntax
return this.domNode.ownerDocument.getElementById(this._list.getElementID(index));
}

private _showHoverForElement(element: IActionListItem<T>, index: number): void {
// Hide any existing hover when moving to a different item
if (this.hover) {
if (this.hover.index === index && !this.hover.hover.isDisposed) {
return;
}
this.hideHover();
}

// Show hover if the element has hover content
if (element.hover?.content && this.focusCondition(element)) {
// The List widget separates data models from DOM elements, so we need to
// look up the actual DOM node to use as the hover target.
const rowElement = this._getRowElement(index);
if (rowElement) {
const markdown = element.hover.content ? new MarkdownString(element.hover.content) : undefined;
const hover = this._hoverService.showInstantHover({
content: markdown ?? '',
target: rowElement,
additionalClasses: ['action-widget-hover'],
position: {
hoverPosition: HoverPosition.LEFT,
forcePosition: false,
},
appearance: {
showPointer: true,
},
});
this.hover = hover ? { index, hover } : undefined;
}
}
}

private async onListHover(e: IListMouseEvent<IActionListItem<T>>) {
const element = e.element;

if (element && element.item && this.focusCondition(element)) {
if (this._delegate.onHover && !element.disabled && element.kind === ActionListItemKind.Action) {
const result = await this._delegate.onHover(element.item, this.cts.token);
Expand All @@ -413,9 +491,9 @@ export class ActionList<T> extends Disposable {
if (e.index) {
this._list.splice(e.index, 1, [element]);
}
}

this._list.setFocus(typeof e.index === 'number' ? [e.index] : []);
this._list.setFocus(typeof e.index === 'number' ? [e.index] : []);
}
}

private onListClick(e: IListMouseEvent<IActionListItem<T>>): void {
Expand Down
9 changes: 8 additions & 1 deletion src/vs/platform/actionWidget/browser/actionWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,14 @@ class ActionWidgetService extends Disposable implements IActionWidgetService {
widget.style.width = `${width}px`;

const focusTracker = renderDisposables.add(dom.trackFocus(element));
renderDisposables.add(focusTracker.onDidBlur(() => this.hide(true)));
renderDisposables.add(focusTracker.onDidBlur(() => {
// Don't hide if focus moved to a hover that belongs to this action widget
const activeElement = dom.getActiveElement();
if (activeElement?.closest('.action-widget-hover')) {
return;
}
this.hide(true);
}));

return renderDisposables;
}
Expand Down
7 changes: 6 additions & 1 deletion src/vs/platform/actionWidget/browser/actionWidgetDropdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { IActionWidgetService } from './actionWidget.js';
import { IAction } from '../../../base/common/actions.js';
import { BaseDropdown, IActionProvider, IBaseDropdownOptions } from '../../../base/browser/ui/dropdown/dropdown.js';
import { ActionListItemKind, IActionListDelegate, IActionListItem } from './actionList.js';
import { ActionListItemKind, IActionListDelegate, IActionListItem, IActionListItemHover } from './actionList.js';
import { ThemeIcon } from '../../../base/common/themables.js';
import { Codicon } from '../../../base/common/codicons.js';
import { getActiveElement, isHTMLElement } from '../../../base/browser/dom.js';
Expand All @@ -17,6 +17,10 @@ export interface IActionWidgetDropdownAction extends IAction {
category?: { label: string; order: number; showHeader?: boolean };
icon?: ThemeIcon;
description?: string;
/**
* Optional flyout hover configuration shown when focusing/hovering over the action.
*/
hover?: IActionListItemHover;
}

// TODO @lramos15 - Should we just make IActionProvider templated?
Expand Down Expand Up @@ -103,6 +107,7 @@ export class ActionWidgetDropdown extends BaseDropdown {
item: action,
tooltip: action.tooltip,
description: action.description,
hover: action.hover,
kind: ActionListItemKind.Action,
canPreview: false,
group: { title: '', icon: action.icon ?? ThemeIcon.fromId(action.checked ? Codicon.check.id : Codicon.blank.id) },
Expand Down
Loading
Loading