Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
bf81772
feat: add support for sandbox bypass in terminal commands with confir…
alexdima Mar 18, 2026
fae1afa
refactor: rename sandbox bypass properties for clarity and consistency
alexdima Mar 18, 2026
89b9b4a
Remove legacy sessionOptions string value
mjbvz Mar 18, 2026
059ff0e
allow requesting multiple code reviews for same session version, up t…
benibenj Mar 18, 2026
3cbeadf
Update more references
mjbvz Mar 18, 2026
bf56017
Add submenu support to ActionList and model picker configuration UI (…
sandy081 Mar 18, 2026
0ce1617
Potential fix for pull request finding
benibenj Mar 18, 2026
b39a991
Potential fix for pull request finding
benibenj Mar 18, 2026
4732213
Potential fix for pull request finding
benibenj Mar 18, 2026
d7e54b2
Remove one more raw value ref
mjbvz Mar 18, 2026
b564a17
Enable reflowCursorLinefor Windows when conpty dll enabled (#303000)
anthonykim1 Mar 18, 2026
0c3cae9
Allow the LLM to request unsandboxed execution in case of failure
alexdima Mar 18, 2026
9b39e51
show generic editing text for complex tool names (#302994)
justschen Mar 18, 2026
4a0c7fc
Sessions - support last turn's changes when using checkpoints (#303001)
lszomoru Mar 18, 2026
7ddad41
Merge pull request #303002 from mjbvz/dev/mjbvz/medical-lobster
mjbvz Mar 18, 2026
ce54ea9
Sessions - attempt to fix issue with revealing the changes view (#303…
lszomoru Mar 18, 2026
86e6dd0
Merge pull request #303004 from microsoft/benibenj/voluminous-aardvark
benibenj Mar 18, 2026
983f73d
feat: enhance focus management in QuickInput components to prevent fo…
TylerLeonhardt Mar 18, 2026
16a4128
Merge pull request #302902 from microsoft/copilot/linguistic-angelfish
alexdima Mar 19, 2026
6edd566
Sessions: Gray out cloud delegation option for non-git workspaces (#3…
joshspicer Mar 19, 2026
fe98f2b
feat: enable image carousel viewer for chat image attachments (#303028)
rebornix Mar 19, 2026
b537005
Revert "Add chat perf markers" (#303030)
pwang347 Mar 19, 2026
61b17f5
Update Copilot CLI chat command targets (#303016)
DonJayamanne Mar 19, 2026
3fe2bce
Revert "Remove legacy sessionOptions string value"
mjbvz Mar 19, 2026
cc5c18d
Merge pull request #303040 from microsoft/revert-303002-dev/mjbvz/med…
mjbvz Mar 19, 2026
eeb4486
Add disable/enable toggle for built-in skills in AI Customization edi…
joshspicer Mar 19, 2026
f5a3b7a
customizations: harness filtering for Claude, agent gating, general h…
joshspicer Mar 19, 2026
599de13
enhance image carousel with caption and counter display (#303026)
rebornix Mar 19, 2026
4956c4b
Send along full `ChatSessionProviderOptionItem` in update callback
mjbvz Mar 19, 2026
631a8bb
new thinking design!!!! (#303043)
justschen Mar 19, 2026
640cb86
Merge pull request #303048 from mjbvz/dev/mjbvz/raw-roundworm
mjbvz Mar 19, 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
659 changes: 518 additions & 141 deletions src/vs/platform/actionWidget/browser/actionList.ts

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions src/vs/platform/actionWidget/browser/actionWidget.css
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,44 @@
.action-widget .action-list-filter-actions .action-label:hover {
background-color: var(--vscode-toolbar-hoverBackground);
}

/* Anchor for the absolutely-positioned submenu panel */
.action-widget .actionList {
position: relative;
}

.action-widget .action-list-submenu-indicator {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
flex-shrink: 0;
border-radius: 4px;
margin-right: 2px;
}

.action-widget .action-list-submenu-indicator.has-submenu {
cursor: pointer;
opacity: 0.6;
}

.action-widget .monaco-list-row.action .action-list-submenu-indicator.codicon {
display: flex;
font-size: 16px;
}

.action-widget .action-list-submenu-indicator.has-submenu:hover {
opacity: 1;
background-color: var(--vscode-toolbar-hoverBackground);
}

.action-list-submenu-panel {
background-color: var(--vscode-menu-background);
color: var(--vscode-menu-foreground);
border: 1px solid var(--vscode-menu-border, var(--vscode-editorHoverWidget-border));
border-radius: 5px;
box-shadow: 0 2px 8px var(--vscode-widget-shadow);
z-index: 50;
width: fit-content;
}
4 changes: 2 additions & 2 deletions src/vs/platform/actionWidget/browser/actionWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,9 @@ class ActionWidgetService extends Disposable implements IActionWidgetService {

const focusTracker = renderDisposables.add(dom.trackFocus(element));
renderDisposables.add(focusTracker.onDidBlur(() => {
// Don't hide if focus moved to a hover that belongs to this action widget
// Don't hide if focus moved to a hover or submenu that belongs to this action widget
const activeElement = dom.getActiveElement();
if (activeElement?.closest('.action-widget-hover')) {
if (activeElement?.closest('.action-widget-hover') || activeElement?.closest('.action-list-submenu-panel')) {
return;
}
this.hide(true);
Expand Down
25 changes: 21 additions & 4 deletions src/vs/platform/quickinput/browser/quickInputList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,14 +321,26 @@ class QuickInputAccessibilityProvider implements IListAccessibilityProvider<IQui
}
}

abstract class BaseQuickInputListRenderer<T extends IQuickPickElement> implements ITreeRenderer<T, void, IQuickInputItemTemplateData> {
abstract class BaseQuickInputListRenderer<T extends IQuickPickElement> extends Disposable implements ITreeRenderer<T, void, IQuickInputItemTemplateData> {
abstract templateId: string;

private readonly _onDidDisposeFocusedElement = this._register(new Emitter<void>());

/**
* This event is emitted when the renderer disposes an element that has focus.
* This allows the list to re-focus itself and prevent focus from being lost
* (potentially causing quickinput to dismiss itself) when an element is
* removed while focused.
*/
readonly onDidDisposeFocusedElement = this._onDidDisposeFocusedElement.event;

constructor(
private readonly hoverDelegate: IHoverDelegate | undefined,
private readonly toggleStyles: IToggleStyles,
private readonly contextMenuService: IContextMenuService
) { }
) {
super();
}

// TODO: only do the common stuff here and have a subclass handle their specific stuff
renderTemplate(container: HTMLElement): IQuickInputItemTemplateData {
Expand Down Expand Up @@ -392,6 +404,9 @@ abstract class BaseQuickInputListRenderer<T extends IQuickPickElement> implement
}

disposeElement(_element: ITreeNode<IQuickPickElement, void>, _index: number, data: IQuickInputItemTemplateData): void {
if (dom.isAncestorOfActiveElement(data.entry)) {
this._onDidDisposeFocusedElement.fire();
}
data.toDisposeElement.clear();
data.toolBar.setActions([]);
}
Expand Down Expand Up @@ -746,8 +761,8 @@ export class QuickInputList extends Disposable {
) {
super();
this._container = dom.append(this.parent, $('.quick-input-list'));
this._separatorRenderer = instantiationService.createInstance(QuickPickSeparatorElementRenderer, hoverDelegate, this.styles.toggle);
this._itemRenderer = instantiationService.createInstance(QuickPickItemElementRenderer, hoverDelegate, this.styles.toggle);
this._separatorRenderer = this._register(instantiationService.createInstance(QuickPickSeparatorElementRenderer, hoverDelegate, this.styles.toggle));
this._itemRenderer = this._register(instantiationService.createInstance(QuickPickItemElementRenderer, hoverDelegate, this.styles.toggle));
this._tree = this._register(instantiationService.createInstance(
WorkbenchObjectTree<IQuickPickElement, void>,
'QuickInput',
Expand Down Expand Up @@ -786,6 +801,8 @@ export class QuickInputList extends Disposable {
}
));
this._tree.getHTMLElement().id = id;
this._register(this._itemRenderer.onDidDisposeFocusedElement(() => this._tree.domFocus()));
this._register(this._separatorRenderer.onDidDisposeFocusedElement(() => this._tree.domFocus()));
this._registerListeners();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ export class QuickInputTreeController extends Disposable {
identityProvider: new QuickInputTreeIdentityProvider()
}
));
this._register(this._renderer.onDidDisposeFocusedElement(() => {
this._tree.domFocus();
}));
this.registerCheckboxStateListeners();
this.registerOnDidChangeFocus();
}
Expand Down Expand Up @@ -297,18 +300,22 @@ export class QuickInputTreeController extends Disposable {
}));

this._register(this._checkboxStateHandler.onDidChangeCheckboxState(e => {
this.updateCheckboxState(e.item, e.checked === true);
this.updateCheckboxState(e.item, e.checked === true, true);
this._tree.setFocus([e.item]);
this._tree.setSelection([e.item]);
}));
}

private updateCheckboxState(item: IQuickTreeItem, newState: boolean): void {
private updateCheckboxState(item: IQuickTreeItem, newState: boolean, skipItemRerender = false): void {
if ((item.checked ?? false) === newState) {
return; // No change
}

// Handle checked item
item.checked = newState;
this._tree.rerender(item);
if (!skipItemRerender) {
this._tree.rerender(item);
}

// Handle children of the checked item
const updateSet = new Set<IQuickTreeItem>();
Expand Down
13 changes: 13 additions & 0 deletions src/vs/platform/quickinput/browser/tree/quickInputTreeRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ export class QuickInputTreeRenderer<T extends IQuickTreeItem> extends Disposable
static readonly ID = 'quickInputTreeElement';
templateId = QuickInputTreeRenderer.ID;

private readonly _onDidDisposeFocusedElement = this._register(new Emitter<void>());

/**
* This event is emitted when the renderer disposes an element that has focus.
* This allows the list to re-focus itself and prevent focus from being lost
* (potentially causing quickinput to dismiss itself) when an element is
* removed while focused.
*/
public readonly onDidDisposeFocusedElement = this._onDidDisposeFocusedElement.event;

constructor(
private readonly _hoverDelegate: IHoverDelegate | undefined,
private readonly _buttonTriggeredEmitter: Emitter<IQuickTreeItemButtonEvent<T>>,
Expand Down Expand Up @@ -172,6 +182,9 @@ export class QuickInputTreeRenderer<T extends IQuickTreeItem> extends Disposable
}

disposeElement(_element: ITreeNode<T, IQuickTreeFilterData>, _index: number, templateData: IQuickTreeTemplateData, _details?: ITreeElementRenderDetails): void {
if (dom.isAncestorOfActiveElement(templateData.entry)) {
this._onDidDisposeFocusedElement.fire();
}
templateData.toDisposeElement.clear();
templateData.actionBar.setActions([]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import assert from 'assert';
import { URI } from '../../../../../base/common/uri.js';
import { Range } from '../../../../../editor/common/core/range.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
import { CodeReviewStateKind, ICodeReviewState, IPRReviewState, PRReviewStateKind } from '../../../codeReview/browser/codeReviewService.js';
import { getResourceEditorComments, getSessionEditorComments, groupNearbySessionEditorComments, hasAgentFeedbackComments, SessionEditorCommentSource } from '../../browser/sessionEditorComments.js';
import { ICodeReviewState, CodeReviewStateKind, IPRReviewState, PRReviewStateKind } from '../../../codeReview/browser/codeReviewService.js';

type ICodeReviewResultState = Extract<ICodeReviewState, { kind: CodeReviewStateKind.Result }>;

Expand All @@ -23,7 +23,9 @@ suite('SessionEditorComments', () => {
return {
kind: CodeReviewStateKind.Result,
version: 'v1',
reviewCount: 1,
comments,
didProduceComments: comments.length > 0,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { localize, localize2 } from '../../../../nls.js';
import { Action2, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
import { AICustomizationItemMenuId } from './aiCustomizationTreeView.js';
import { AICustomizationItemTypeContextKey } from './aiCustomizationTreeViewViews.js';
import { AI_CUSTOMIZATION_VIEW_ID, AICustomizationItemMenuId } from './aiCustomizationTreeView.js';
import { AICustomizationItemDisabledContextKey, AICustomizationItemStorageContextKey, AICustomizationItemTypeContextKey, AICustomizationViewPane } from './aiCustomizationTreeViewViews.js';
import { PromptsType } from '../../../../workbench/contrib/chat/common/promptSyntax/promptTypes.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
Expand All @@ -17,20 +17,23 @@ import { IEditorService } from '../../../../workbench/services/editor/common/edi
import { IFileService, FileSystemProviderCapabilities } from '../../../../platform/files/common/files.js';
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
import { IPromptsService } from '../../../../workbench/contrib/chat/common/promptSyntax/service/promptsService.js';
import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js';
import { BUILTIN_STORAGE } from '../../chat/common/builtinPromptsStorage.js';

//#region Utilities

/**
* Type for context passed to actions from tree context menus.
* Handles both direct URI arguments and serialized context objects.
*/
type URIContext = { uri: URI | string;[key: string]: unknown } | URI | string;
type ItemContext = { uri: URI | string; promptType?: string; disabled?: boolean;[key: string]: unknown } | URI | string;

/**
* Extracts a URI from various context formats.
* Context can be a URI, string, or an object with uri property.
*/
function extractURI(context: URIContext): URI {
function extractURI(context: ItemContext): URI {
if (URI.isUri(context)) {
return context;
}
Expand All @@ -57,7 +60,7 @@ registerAction2(class extends Action2 {
icon: Codicon.goToFile,
});
}
async run(accessor: ServicesAccessor, context: URIContext): Promise<void> {
async run(accessor: ServicesAccessor, context: ItemContext): Promise<void> {
const editorService = accessor.get(IEditorService);
await editorService.openEditor({
resource: extractURI(context)
Expand All @@ -76,7 +79,7 @@ registerAction2(class extends Action2 {
icon: Codicon.play,
});
}
async run(accessor: ServicesAccessor, context: URIContext): Promise<void> {
async run(accessor: ServicesAccessor, context: ItemContext): Promise<void> {
const commandService = accessor.get(ICommandService);
await commandService.executeCommand('workbench.action.chat.run.prompt.current', extractURI(context));
}
Expand All @@ -92,7 +95,7 @@ registerAction2(class extends Action2 {
icon: Codicon.trash,
});
}
async run(accessor: ServicesAccessor, context: URIContext): Promise<void> {
async run(accessor: ServicesAccessor, context: ItemContext): Promise<void> {
const fileService = accessor.get(IFileService);
const dialogService = accessor.get(IDialogService);
const uri = extractURI(context);
Expand Down Expand Up @@ -124,7 +127,7 @@ registerAction2(class extends Action2 {
icon: Codicon.clippy,
});
}
async run(accessor: ServicesAccessor, context: URIContext): Promise<void> {
async run(accessor: ServicesAccessor, context: ItemContext): Promise<void> {
const clipboardService = accessor.get(IClipboardService);
const uri = extractURI(context);
const textToCopy = uri.scheme === 'file' ? uri.fsPath : uri.toString(true);
Expand Down Expand Up @@ -167,4 +170,114 @@ MenuRegistry.appendMenuItem(AICustomizationItemMenuId, {
order: 10,
});

// Disable item action
const DISABLE_AI_CUSTOMIZATION_ITEM_ID = 'aiCustomization.disableItem';
registerAction2(class extends Action2 {
constructor() {
super({
id: DISABLE_AI_CUSTOMIZATION_ITEM_ID,
title: localize2('disable', "Disable"),
icon: Codicon.eyeClosed,
});
}
async run(accessor: ServicesAccessor, context: ItemContext): Promise<void> {
if (typeof context !== 'object' || URI.isUri(context)) {
return;
}
const promptsService = accessor.get(IPromptsService);
const viewsService = accessor.get(IViewsService);
const uri = extractURI(context);
const promptType = context.promptType as PromptsType | undefined;
if (!promptType) {
return;
}

const disabled = promptsService.getDisabledPromptFiles(promptType);
disabled.add(uri);
promptsService.setDisabledPromptFiles(promptType, disabled);

const view = viewsService.getActiveViewWithId<AICustomizationViewPane>(AI_CUSTOMIZATION_VIEW_ID);
view?.refresh();
}
});

// Enable item action
const ENABLE_AI_CUSTOMIZATION_ITEM_ID = 'aiCustomization.enableItem';
registerAction2(class extends Action2 {
constructor() {
super({
id: ENABLE_AI_CUSTOMIZATION_ITEM_ID,
title: localize2('enable', "Enable"),
icon: Codicon.eye,
});
}
async run(accessor: ServicesAccessor, context: ItemContext): Promise<void> {
if (typeof context !== 'object' || URI.isUri(context)) {
return;
}
const promptsService = accessor.get(IPromptsService);
const viewsService = accessor.get(IViewsService);
const uri = extractURI(context);
const promptType = context.promptType as PromptsType | undefined;
if (!promptType) {
return;
}

const disabled = promptsService.getDisabledPromptFiles(promptType);
disabled.delete(uri);
promptsService.setDisabledPromptFiles(promptType, disabled);

const view = viewsService.getActiveViewWithId<AICustomizationViewPane>(AI_CUSTOMIZATION_VIEW_ID);
view?.refresh();
}
});

// Context menu: Disable (shown when builtin item is enabled)
MenuRegistry.appendMenuItem(AICustomizationItemMenuId, {
command: { id: DISABLE_AI_CUSTOMIZATION_ITEM_ID, title: localize('disable', "Disable") },
group: '4_toggle',
order: 1,
when: ContextKeyExpr.and(
ContextKeyExpr.equals(AICustomizationItemDisabledContextKey.key, false),
ContextKeyExpr.equals(AICustomizationItemStorageContextKey.key, BUILTIN_STORAGE),
ContextKeyExpr.equals(AICustomizationItemTypeContextKey.key, PromptsType.skill),
),
});

// Context menu: Enable (shown when builtin item is disabled)
MenuRegistry.appendMenuItem(AICustomizationItemMenuId, {
command: { id: ENABLE_AI_CUSTOMIZATION_ITEM_ID, title: localize('enable', "Enable") },
group: '4_toggle',
order: 1,
when: ContextKeyExpr.and(
ContextKeyExpr.equals(AICustomizationItemDisabledContextKey.key, true),
ContextKeyExpr.equals(AICustomizationItemStorageContextKey.key, BUILTIN_STORAGE),
ContextKeyExpr.equals(AICustomizationItemTypeContextKey.key, PromptsType.skill),
),
});

// Inline hover: Disable (shown when builtin item is enabled)
MenuRegistry.appendMenuItem(AICustomizationItemMenuId, {
command: { id: DISABLE_AI_CUSTOMIZATION_ITEM_ID, title: localize('disable', "Disable"), icon: Codicon.eyeClosed },
group: 'inline',
order: 5,
when: ContextKeyExpr.and(
ContextKeyExpr.equals(AICustomizationItemDisabledContextKey.key, false),
ContextKeyExpr.equals(AICustomizationItemStorageContextKey.key, BUILTIN_STORAGE),
ContextKeyExpr.equals(AICustomizationItemTypeContextKey.key, PromptsType.skill),
),
});

// Inline hover: Enable (shown when builtin item is disabled)
MenuRegistry.appendMenuItem(AICustomizationItemMenuId, {
command: { id: ENABLE_AI_CUSTOMIZATION_ITEM_ID, title: localize('enable', "Enable"), icon: Codicon.eye },
group: 'inline',
order: 5,
when: ContextKeyExpr.and(
ContextKeyExpr.equals(AICustomizationItemDisabledContextKey.key, true),
ContextKeyExpr.equals(AICustomizationItemStorageContextKey.key, BUILTIN_STORAGE),
ContextKeyExpr.equals(AICustomizationItemTypeContextKey.key, PromptsType.skill),
),
});

//#endregion
Loading
Loading