Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
3c8a70a
inline chat - use native private properties in InlineChatAffordance
jrieken Feb 16, 2026
98c9ef8
Merge remote-tracking branch 'origin/main' into joh/inline-chat-nativ…
jrieken Feb 16, 2026
c271611
Inline chat editor affordance with lightbulb integration
jrieken Feb 16, 2026
a2ed684
Enhance computeLightBulbInfo to support gutter context and simplify code
jrieken Feb 16, 2026
4871bc4
fix: add border color for widget in 2026 dark theme
mrleemurray Feb 16, 2026
6cb8d0a
Merge pull request #295568 from microsoft/joh/inline-chat-native-private
jrieken Feb 16, 2026
08c0128
modal - separate setting for opening extensions/mcp in modal editor (…
bpasero Feb 16, 2026
290c008
Add rename tool and related helper functions for symbol renaming (#29…
jrieken Feb 16, 2026
547a36e
sessions - stop blocking shutdown for archived sessions even if in pr…
bpasero Feb 16, 2026
0955181
fix: update command center active background and add toolbar hover ba…
mrleemurray Feb 16, 2026
ea14b65
edit mode from chat extension
benibenj Feb 16, 2026
0af57b1
Merge pull request #295611 from microsoft/mrleemurray/occupational-co…
mrleemurray Feb 16, 2026
69d110f
Merge pull request #295618 from microsoft/benibenj/collective-tiger
benibenj Feb 16, 2026
86296d1
Refactor inline chat affordance classes to streamline telemetry loggi…
jrieken Feb 16, 2026
d85f99b
Increase amount of request scrolled into view by default (#295637)
roblourens Feb 16, 2026
33dc3c2
Modal view leaves quick pick out of focus (fix #295619) (#295644)
bpasero Feb 16, 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
1 change: 1 addition & 0 deletions extensions/theme-2026/themes/2026-dark.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
"editorSuggestWidget.selectedBackground": "#3994BC26",
"editorHoverWidget.background": "#202122",
"editorHoverWidget.border": "#2A2B2CFF",
"widget.border": "#2A2B2CFF",
"peekView.border": "#2A2B2CFF",
"peekViewEditor.background": "#191A1B",
"peekViewEditor.matchHighlightBackground": "#3994BC33",
Expand Down
3 changes: 2 additions & 1 deletion extensions/theme-2026/themes/2026-light.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
"commandCenter.foreground": "#202020",
"commandCenter.activeForeground": "#202020",
"commandCenter.background": "#FAFAFD",
"commandCenter.activeBackground": "#EEEEEE",
"commandCenter.activeBackground": "#DADADA4f",
"commandCenter.border": "#D8D8D8",
"editor.background": "#FFFFFF",
"editor.foreground": "#202020",
Expand Down Expand Up @@ -185,6 +185,7 @@
"statusBarItem.prominentBackground": "#0069CCDD",
"statusBarItem.prominentForeground": "#FFFFFF",
"statusBarItem.prominentHoverBackground": "#0069CC",
"toolbar.hoverBackground": "#DADADA4f",
"tab.activeBackground": "#FFFFFF",
"tab.activeForeground": "#202020",
"tab.inactiveBackground": "#FAFAFD",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class QuickFixAction extends EditorAction2 {
},
menu: {
id: MenuId.InlineChatEditorAffordance,
group: '0_quickfix',
group: '1_quickfix',
order: 0,
when: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider)
}
Expand Down
47 changes: 40 additions & 7 deletions src/vs/editor/contrib/codeAction/browser/codeActionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import { onUnexpectedError } from '../../../../base/common/errors.js';
import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js';
import { Lazy } from '../../../../base/common/lazy.js';
import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js';
import { derived, IObservable } from '../../../../base/common/observable.js';
import { derivedOpts, IObservable, observableValue } from '../../../../base/common/observable.js';
import { Event } from '../../../../base/common/event.js';
import { localize } from '../../../../nls.js';
import { IActionListDelegate } from '../../../../platform/actionWidget/browser/actionList.js';
import { IActionWidgetService } from '../../../../platform/actionWidget/browser/actionWidget.js';
Expand All @@ -35,11 +36,12 @@ import { ModelDecorationOptions } from '../../../common/model/textModel.js';
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
import { MessageController } from '../../message/browser/messageController.js';
import { CodeActionAutoApply, CodeActionFilter, CodeActionItem, CodeActionKind, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from '../common/types.js';
import { ApplyCodeActionReason, applyCodeAction } from './codeAction.js';
import { ApplyCodeActionReason, applyCodeAction, autoFixCommandId, quickFixCommandId } from './codeAction.js';
import { CodeActionKeybindingResolver } from './codeActionKeybindingResolver.js';
import { toMenuItems } from './codeActionMenu.js';
import { CodeActionModel, CodeActionsState } from './codeActionModel.js';
import { LightBulbInfo, LightBulbWidget } from './lightBulbWidget.js';
import { computeLightBulbInfo, LightBulbInfo, LightBulbWidget } from './lightBulbWidget.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';

interface IActionShowOptions {
readonly includeDisabledActions?: boolean;
Expand Down Expand Up @@ -68,12 +70,34 @@ export class CodeActionController extends Disposable implements IEditorContribut

private _disposed = false;

public readonly lightBulbState: IObservable<LightBulbInfo | undefined> = derived(this, reader => {
set onlyLightBulbWithEmptySelection(value: boolean) {
const widget = this._lightBulbWidget.rawValue;
if (!widget) {
return undefined;
if (widget) {
widget.onlyWithEmptySelection = value;
}
return widget.lightBulbInfo.read(reader);
this._onlyLightBulbWithEmptySelection = value;
}

private _onlyLightBulbWithEmptySelection = false;

private readonly _lightBulbInfoObs = observableValue<LightBulbInfo | undefined>(this, undefined);
private readonly _preferredKbLabel = observableValue<string | undefined>(this, undefined);
private readonly _quickFixKbLabel = observableValue<string | undefined>(this, undefined);

private _hasLightBulbStateObservers = false;

public readonly lightBulbState: IObservable<LightBulbInfo | undefined> = derivedOpts<LightBulbInfo | undefined>({
owner: this,
onLastObserverRemoved: () => {
this._hasLightBulbStateObservers = false;
this._model.ignoreLightbulbOff = false;
},
}, reader => {
if (!this._hasLightBulbStateObservers) {
this._hasLightBulbStateObservers = true;
this._model.ignoreLightbulbOff = true;
}
return this._lightBulbInfoObs.read(reader);
});

constructor(
Expand All @@ -88,17 +112,24 @@ export class CodeActionController extends Disposable implements IEditorContribut
@IActionWidgetService private readonly _actionWidgetService: IActionWidgetService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IEditorProgressService private readonly _progressService: IEditorProgressService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
) {
super();

this._editor = editor;
this._model = this._register(new CodeActionModel(this._editor, languageFeaturesService.codeActionProvider, markerService, contextKeyService, progressService, _configurationService));
this._register(this._model.onDidChangeState(newState => this.update(newState)));

this._register(Event.runAndSubscribe(this._keybindingService.onDidUpdateKeybindings, () => {
this._preferredKbLabel.set(this._keybindingService.lookupKeybinding(autoFixCommandId)?.getLabel() ?? undefined, undefined);
this._quickFixKbLabel.set(this._keybindingService.lookupKeybinding(quickFixCommandId)?.getLabel() ?? undefined, undefined);
}));

this._lightBulbWidget = new Lazy(() => {
const widget = this._editor.getContribution<LightBulbWidget>(LightBulbWidget.ID);
if (widget) {
this._register(widget.onClick(e => this.showCodeActionsFromLightbulb(e.actions, e)));
widget.onlyWithEmptySelection = this._onlyLightBulbWithEmptySelection;
}
return widget;
});
Expand Down Expand Up @@ -175,6 +206,7 @@ export class CodeActionController extends Disposable implements IEditorContribut
private async update(newState: CodeActionsState.State): Promise<void> {
if (newState.type !== CodeActionsState.Type.Triggered) {
this.hideLightBulbWidget();
this._lightBulbInfoObs.set(undefined, undefined);
return;
}

Expand All @@ -197,6 +229,7 @@ export class CodeActionController extends Disposable implements IEditorContribut
}

this._lightBulbWidget.value?.update(actions, newState.trigger, newState.position);
this._lightBulbInfoObs.set(computeLightBulbInfo(actions, newState.trigger, this._preferredKbLabel.get(), this._quickFixKbLabel.get()), undefined);

if (newState.trigger.type === CodeActionTriggerType.Invoke) {
if (newState.trigger.filter?.include) { // Triggered for specific scope
Expand Down
26 changes: 23 additions & 3 deletions src/vs/editor/contrib/codeAction/browser/codeActionModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class CodeActionOracle extends Disposable {

private readonly _autoTriggerTimer = this._register(new TimeoutTimer());

ignoreLightbulbOff = false;

constructor(
private readonly _editor: ICodeEditor,
private readonly _markerService: IMarkerService,
Expand Down Expand Up @@ -74,9 +76,9 @@ class CodeActionOracle extends Disposable {
return selection;
}
const enabled = this._editor.getOption(EditorOption.lightbulb).enabled;
if (enabled === ShowLightbulbIconMode.Off) {
if (enabled === ShowLightbulbIconMode.Off && !this.ignoreLightbulbOff) {
return undefined;
} else if (enabled === ShowLightbulbIconMode.On) {
} else if (enabled === ShowLightbulbIconMode.Off || enabled === ShowLightbulbIconMode.On) {
return selection;
} else if (enabled === ShowLightbulbIconMode.OnCode) {
const isSelectionEmpty = selection.isEmpty();
Expand Down Expand Up @@ -167,6 +169,22 @@ export class CodeActionModel extends Disposable {

private _disposed = false;

private _ignoreLightbulbOff = false;

set ignoreLightbulbOff(value: boolean) {
if (this._ignoreLightbulbOff === value) {
return;
}
this._ignoreLightbulbOff = value;
const oracle = this._codeActionOracle.value;
if (oracle) {
oracle.ignoreLightbulbOff = value;
if (value) {
oracle.trigger({ type: CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default });
}
}
}

constructor(
private readonly _editor: ICodeEditor,
private readonly _registry: LanguageFeatureRegistry<CodeActionProvider>,
Expand Down Expand Up @@ -221,7 +239,7 @@ export class CodeActionModel extends Disposable {
const supportedActions: string[] = this._registry.all(model).flatMap(provider => provider.providedCodeActionKinds ?? []);
this._supportedCodeActions.set(supportedActions.join(' '));

this._codeActionOracle.value = new CodeActionOracle(this._editor, this._markerService, trigger => {
const oracle = new CodeActionOracle(this._editor, this._markerService, trigger => {
if (!trigger) {
this.setState(CodeActionsState.Empty);
return;
Expand Down Expand Up @@ -363,6 +381,8 @@ export class CodeActionModel extends Disposable {
}, 500);
}
}, undefined);
oracle.ignoreLightbulbOff = this._ignoreLightbulbOff;
this._codeActionOracle.value = oracle;
this._codeActionOracle.value.trigger({ type: CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default });
} else {
this._supportedCodeActions.reset();
Expand Down
83 changes: 48 additions & 35 deletions src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { autorun, derived, IObservable, observableValue } from '../../../../base
import { ThemeIcon } from '../../../../base/common/themables.js';
import './lightBulbWidget.css';
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition, IEditorMouseEvent } from '../../../browser/editorBrowser.js';
import { EditorOption } from '../../../common/config/editorOptions.js';
import { EditorOption, ShowLightbulbIconMode } from '../../../common/config/editorOptions.js';
import { IPosition } from '../../../common/core/position.js';
import { GlyphMarginLane, IModelDecorationsChangeAccessor, TrackedRangeStickiness } from '../../../common/model.js';
import { ModelDecorationOptions } from '../../../common/model/textModel.js';
Expand Down Expand Up @@ -62,9 +62,49 @@ namespace LightBulbState {
export type State = typeof Hidden | Showing;
}

export function computeLightBulbInfo(actions: CodeActionSet, trigger: CodeActionTrigger, preferredKbLabel: string | undefined, quickFixKbLabel: string | undefined, forGutter: boolean = false): LightBulbInfo | undefined {
if (actions.validActions.length <= 0) {
return undefined;
}

let icon: ThemeIcon;
let autoRun = false;
if (actions.allAIFixes) {
icon = forGutter ? GUTTER_SPARKLE_FILLED_ICON : Codicon.sparkleFilled;
if (actions.validActions.length === 1) {
autoRun = true;
}
} else if (actions.hasAutoFix) {
if (actions.hasAIFix) {
icon = forGutter ? GUTTER_LIGHTBULB_AIFIX_AUTO_FIX_ICON : Codicon.lightbulbSparkleAutofix;
} else {
icon = forGutter ? GUTTER_LIGHTBULB_AUTO_FIX_ICON : Codicon.lightbulbAutofix;
}
} else if (actions.hasAIFix) {
icon = forGutter ? GUTTER_LIGHTBULB_AIFIX_ICON : Codicon.lightbulbSparkle;
} else {
icon = forGutter ? GUTTER_LIGHTBULB_ICON : Codicon.lightBulb;
}

let title: string;
if (autoRun) {
title = nls.localize('codeActionAutoRun', "Run: {0}", actions.validActions[0].action.title);
} else if (actions.hasAutoFix && preferredKbLabel) {
title = nls.localize('preferredcodeActionWithKb', "Show Code Actions. Preferred Quick Fix Available ({0})", preferredKbLabel);
} else if (!actions.hasAutoFix && quickFixKbLabel) {
title = nls.localize('codeActionWithKb', "Show Code Actions ({0})", quickFixKbLabel);
} else {
title = nls.localize('codeAction', "Show Code Actions");
}

return { actions, trigger, icon, autoRun, title, isGutter: forGutter };
}

export class LightBulbWidget extends Disposable implements IContentWidget {
private _gutterDecorationID: string | undefined;

onlyWithEmptySelection = false;

private static readonly GUTTER_DECORATION = ModelDecorationOptions.register({
description: 'codicon-gutter-lightbulb-decoration',
glyphMarginClassName: ThemeIcon.asClassName(Codicon.lightBulb),
Expand Down Expand Up @@ -117,39 +157,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
if (state.type !== LightBulbState.Type.Showing) {
return undefined;
}

const { actions, trigger } = state;
let icon: ThemeIcon;
let autoRun = false;
if (actions.allAIFixes) {
icon = forGutter ? GUTTER_SPARKLE_FILLED_ICON : Codicon.sparkleFilled;
if (actions.validActions.length === 1) {
autoRun = true;
}
} else if (actions.hasAutoFix) {
if (actions.hasAIFix) {
icon = forGutter ? GUTTER_LIGHTBULB_AIFIX_AUTO_FIX_ICON : Codicon.lightbulbSparkleAutofix;
} else {
icon = forGutter ? GUTTER_LIGHTBULB_AUTO_FIX_ICON : Codicon.lightbulbAutofix;
}
} else if (actions.hasAIFix) {
icon = forGutter ? GUTTER_LIGHTBULB_AIFIX_ICON : Codicon.lightbulbSparkle;
} else {
icon = forGutter ? GUTTER_LIGHTBULB_ICON : Codicon.lightBulb;
}

let title: string;
if (autoRun) {
title = nls.localize('codeActionAutoRun', "Run: {0}", actions.validActions[0].action.title);
} else if (actions.hasAutoFix && preferredKbLabel) {
title = nls.localize('preferredcodeActionWithKb', "Show Code Actions. Preferred Quick Fix Available ({0})", preferredKbLabel);
} else if (!actions.hasAutoFix && quickFixKbLabel) {
title = nls.localize('codeActionWithKb', "Show Code Actions ({0})", quickFixKbLabel);
} else {
title = nls.localize('codeAction', "Show Code Actions");
}

return { actions, trigger, icon, autoRun, title, isGutter: forGutter };
return computeLightBulbInfo(state.actions, state.trigger, preferredKbLabel, quickFixKbLabel, forGutter);
}

constructor(
Expand Down Expand Up @@ -288,14 +296,19 @@ export class LightBulbWidget extends Disposable implements IContentWidget {
return this.hide();
}

if (this.onlyWithEmptySelection && !this._editor.getSelection()?.isEmpty()) {
this.gutterHide();
return this.hide();
}

const hasTextFocus = this._editor.hasTextFocus();
if (!hasTextFocus) {
this.gutterHide();
return this.hide();
}

const options = this._editor.getOptions();
if (!options.get(EditorOption.lightbulb).enabled) {
if (options.get(EditorOption.lightbulb).enabled === ShowLightbulbIconMode.Off) {
this.gutterHide();
return this.hide();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
height: 100%;
top: 0;
left: 0;
/* z-index for modal editors: above titlebar (2500) but below dialogs (2575) */
z-index: 2550;
/* z-index for modal editors: above titlebar (2500) but below quick input (2550) and dialogs (2575) */
z-index: 2540;
display: flex;
justify-content: center;
align-items: center;
Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/contrib/chat/browser/chat.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import { IPromptsService } from '../common/promptSyntax/service/promptsService.j
import { PromptsService } from '../common/promptSyntax/service/promptsServiceImpl.js';
import { LanguageModelToolsExtensionPointHandler } from '../common/tools/languageModelToolsContribution.js';
import { BuiltinToolsContribution } from '../common/tools/builtinTools/tools.js';
import { RenameToolContribution } from './tools/renameTool.js';
import { UsagesToolContribution } from './tools/usagesTool.js';
import { IVoiceChatService, VoiceChatService } from '../common/voiceChatService.js';
import { registerChatAccessibilityActions } from './actions/chatAccessibilityActions.js';
Expand Down Expand Up @@ -1438,6 +1439,7 @@ registerWorkbenchContribution2(ChatTeardownContribution.ID, ChatTeardownContribu
registerWorkbenchContribution2(ChatStatusBarEntry.ID, ChatStatusBarEntry, WorkbenchPhase.BlockRestore);
registerWorkbenchContribution2(BuiltinToolsContribution.ID, BuiltinToolsContribution, WorkbenchPhase.Eventually);
registerWorkbenchContribution2(UsagesToolContribution.ID, UsagesToolContribution, WorkbenchPhase.BlockRestore);
registerWorkbenchContribution2(RenameToolContribution.ID, RenameToolContribution, WorkbenchPhase.BlockRestore);
registerWorkbenchContribution2(ChatAgentSettingContribution.ID, ChatAgentSettingContribution, WorkbenchPhase.AfterRestored);
registerWorkbenchContribution2(ChatAgentActionsContribution.ID, ChatAgentActionsContribution, WorkbenchPhase.Eventually);
registerWorkbenchContribution2(ToolReferenceNamesContribution.ID, ToolReferenceNamesContribution, WorkbenchPhase.AfterRestored);
Expand Down
Loading
Loading