Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 10, 2025

Extracts TerminalInlineHintWidget (lines 209-357) from terminal.initialHint.contribution.ts into a separate module for reuse as a lightweight presentation mechanism in terminal inline chat suggestions.

Changes

  • Created terminalInlineHintWidget.ts: Standalone widget with self-contained dependencies, JSDoc documentation, and exported TerminalInitialHintConstants enum
  • Updated terminal.initialHint.contribution.ts: Imports and uses extracted widget, removed redundant code (~170 lines)
  • Fixed pre-existing typo: Corrected action ID from workench.action.disableTerminalInitialHint to workbench.action.disableTerminalInitialHint

Usage

import { TerminalInlineHintWidget } from './terminalInlineHintWidget.js';

const widget = instantiationService.createInstance(TerminalInlineHintWidget, terminalInstance);
const domNode = widget.getDomNode(chatAgents);
container.appendChild(domNode);

The widget handles keybinding display, accessibility, auto-dismissal, and telemetry tracking.

Original prompt

This section details on the original issue you should resolve

<issue_title>Explore reworking terminal inline chat to be more lightweight</issue_title>
<issue_description>Extract TerminalInlineHintWidget:

class TerminalInitialHintWidget extends Disposable {
private _domNode: HTMLElement | undefined;
private readonly _toDispose: DisposableStore = this._register(new DisposableStore());
private _isVisible = false;
private _ariaLabel: string = '';
constructor(
private readonly _instance: ITerminalInstance,
@ICommandService private readonly _commandService: ICommandService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@IStorageService private readonly _storageService: IStorageService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@ITerminalService private readonly _terminalService: ITerminalService,
) {
super();
this._toDispose.add(_instance.onDidFocus(() => {
if (this._instance.hasFocus && this._isVisible && this._ariaLabel && this._configurationService.getValue(AccessibilityVerbositySettingId.TerminalInlineChat)) {
status(this._ariaLabel);
}
}));
this._toDispose.add(_terminalService.onDidChangeInstances(() => {
if (this._terminalService.instances.length !== 1) {
this.dispose();
}
}));
this._toDispose.add(this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(TerminalInitialHintSettingId.Enabled) && !this._configurationService.getValue(TerminalInitialHintSettingId.Enabled)) {
this.dispose();
}
}));
}
private _getHintInlineChat(agents: IChatAgent[]) {
let ariaLabel = `Open chat.`;
const handleClick = () => {
this._storageService.store(Constants.InitialHintHideStorageKey, true, StorageScope.APPLICATION, StorageTarget.USER);
this._telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', {
id: 'terminalInlineChat.hintAction',
from: 'hint'
});
this._commandService.executeCommand(TerminalChatCommandId.Start, { from: 'hint' });
};
this._toDispose.add(this._commandService.onDidExecuteCommand(e => {
if (e.commandId === TerminalChatCommandId.Start) {
this._storageService.store(Constants.InitialHintHideStorageKey, true, StorageScope.APPLICATION, StorageTarget.USER);
this.dispose();
}
}));
const hintHandler: IContentActionHandler = {
disposables: this._toDispose,
callback: (index, _event) => {
switch (index) {
case '0':
handleClick();
break;
}
}
};
const hintElement = $('div.terminal-initial-hint');
hintElement.style.display = 'block';
const keybindingHint = this._keybindingService.lookupKeybinding(TerminalChatCommandId.Start);
const keybindingHintLabel = keybindingHint?.getLabel();
if (keybindingHint && keybindingHintLabel) {
const actionPart = localize('emptyHintText', 'Open chat {0}. ', keybindingHintLabel);
const [before, after] = actionPart.split(keybindingHintLabel).map((fragment) => {
const hintPart = $('a', undefined, fragment);
this._toDispose.add(dom.addDisposableListener(hintPart, dom.EventType.CLICK, handleClick));
return hintPart;
});
hintElement.appendChild(before);
const label = hintHandler.disposables.add(new KeybindingLabel(hintElement, OS));
label.set(keybindingHint);
label.element.style.width = 'min-content';
label.element.style.display = 'inline';
label.element.style.cursor = 'pointer';
this._toDispose.add(dom.addDisposableListener(label.element, dom.EventType.CLICK, handleClick));
hintElement.appendChild(after);
const typeToDismiss = localize('hintTextDismiss', 'Start typing to dismiss.');
const textHint2 = $('span.detail', undefined, typeToDismiss);
hintElement.appendChild(textHint2);
ariaLabel = actionPart.concat(typeToDismiss);
} else {
const hintMsg = localize({
key: 'inlineChatHint',
comment: [
'Preserve double-square brackets and their order',
]
}, '[[Open chat]] or start typing to dismiss.');
const rendered = renderFormattedText(hintMsg, { actionHandler: hintHandler });
hintElement.appendChild(rendered);
}
return { ariaLabel, hintHandler, hintElement };
}
getDomNode(agents: IChatAgent[]): HTMLElement {
if (!this._domNode) {
this._domNode = $('.terminal-initial-hint');
this._domNode!.style.paddingLeft = '4px';
const { hintElement, ariaLabel } = this._getHintInlineChat(agents);
this._domNode.append(hintElement);
this._ariaLabel = ariaLabel.concat(localize('disableHint', ' Toggle {0} in settings to disable this hint.', AccessibilityVerbositySettingId.TerminalInlineChat));
this._toDispose.add(dom.addDisposableListener(this._domNode, 'click', () => {
this._domNode?.remove();
this._domNode = undefined;
}));
this._toDispose.add(dom.addDisposableListener(this._domNode, dom.EventType.CONTEXT_MENU, (e) => {
this._contextMenuService.showContextMenu({
getAnchor: () => { return new StandardMouseEvent(dom.getActiveWindow(), e); },
getActions: () => {
return [{
id: 'workench.action.disableTerminalInitialHint',
label: localize('disableInitialHint', "Disable Initial Hint"),
tooltip: localize('disableInitialHint', "Disable Initial Hint"),
enabled: true,
class: undefined,
run: () => this._configurationService.updateValue(TerminalInitialHintSettingId.Enabled, false)
}
];
}
});
}));
}
return this._domNode;
}
override dispose(): void {
this._domNode?.remove();
super.dispose();
}
}

Then use it as the presentation mechanism for inline chat suggestions. This could potentially allow us to replace the current implementation in TerminalChatWidget</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] Rework terminal inline chat for lightweight implementation Extract TerminalInlineHintWidget for reuse in terminal inline chat Dec 10, 2025
Copilot AI requested a review from Tyriar December 10, 2025 13:40
* - Can be disabled via context menu
* - Tracks telemetry for user interactions
*/
export class TerminalInlineHintWidget extends Disposable {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot now use it instead of TerminalChatWidget

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to clarify the scope of this change. The TerminalInlineHintWidget is a lightweight hint that displays "Open chat" with a keybinding - it doesn't have chat functionality itself.

Should I:

  1. Replace TerminalChatWidget entirely with just the hint widget (removing the full chat interface)?
  2. Use the hint widget as an initial prompt/suggestion, but keep TerminalChatWidget for the actual chat interaction?
  3. Something else?

Could you clarify what behavior you'd like to see?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Explore reworking terminal inline chat to be more lightweight

2 participants