Skip to content

Conversation

@meganrogge
Copy link
Contributor

@meganrogge meganrogge commented Nov 21, 2025

see #278684 for some todos

cc @Tyriar

Copilot AI review requested due to automatic review settings November 21, 2025 23:22
@meganrogge meganrogge marked this pull request as draft November 21, 2025 23:22
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds streaming support for terminal command output displayed inline in chat responses. Instead of capturing a static snapshot of terminal output, the implementation now mirrors live terminal output into a detached terminal instance, enabling real-time updates as commands execute.

Key changes:

  • Introduced DetachedTerminalCommandMirror class to stream terminal output from a source terminal to a detached display instance
  • Added getRangeAsVT() method to IXtermTerminal for extracting terminal content as VT sequences between markers
  • Refactored ChatTerminalToolOutputSection to use the new streaming mirror instead of HTML-based output rendering
  • Removed terminalCommandOutput storage from IChatTerminalToolInvocationData as output is now rendered dynamically

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/vs/workbench/contrib/terminal/browser/chatTerminalCommandMirror.ts New file implementing DetachedTerminalCommandMirror for streaming terminal output with dirty region tracking and incremental updates
src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts Added getRangeAsVT() method to serialize terminal content between markers as VT sequences
src/vs/workbench/contrib/terminal/browser/terminal.ts Added getRangeAsVT() to IXtermTerminal interface and raw property to IDetachedXtermTerminal
src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts Refactored to use streaming mirror, removed HTML output rendering, updated layout calculation to use line counts
src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatTerminalToolProgressPart.css Updated styles for terminal output display, removed fixed height constraints
src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/terminalCommandArtifactCollector.ts Simplified to only capture command metadata, removed output serialization
src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts Updated calls to capture() method to remove fallback output parameter

private _lineCount = 0;
private _lastUpToDateCursorY: number | undefined;
private _lowestDirtyCursorY: number | undefined;
private _highestDirtyCursorY: number | undefined;
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

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

The _highestDirtyCursorY field is tracked but never read. It's set in _handleCursorEvent() and cleared in _doFlushDirtyRange(), but its value is not used anywhere. Consider either removing it if it's not needed, or documenting why it's being tracked for future use.

Copilot uses AI. Check for mistakes.

interface IDetachedTerminalCommandMirror {
attach(container: HTMLElement): Promise<void>;
renderCommand(): Promise<{ lineCount?: number } | undefined>;
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

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

[nitpick] The IDetachedTerminalCommandMirror interface is incomplete - it's missing the onDidUpdate event which is a public member of DetachedTerminalCommandMirror. Since the interface is not exported and only used internally, consider either:

  1. Adding onDidUpdate to the interface for completeness
  2. Removing the interface entirely if it's not providing value

This ensures the interface accurately represents the public API of the class.

Suggested change
renderCommand(): Promise<{ lineCount?: number } | undefined>;
renderCommand(): Promise<{ lineCount?: number } | undefined>;
onDidUpdate: Event<number>;

Copilot uses AI. Check for mistakes.
private _layoutOutput(): void {
if (!this._outputScrollbar || !this.isExpanded) {
private _layoutOutput(lineCount?: number): void {
if (!this._outputScrollbar || !this.isExpanded || !lineCount) {
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

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

The _layoutOutput() method now requires a lineCount parameter to calculate height, but the guard !lineCount at line 918 prevents layout when lineCount is 0 or undefined. This causes issues:

  1. When a command produces no output (lineCount === 0), layout is skipped even though the empty message should be displayed
  2. The ResizeObserver (line 980) and other callers (lines 747, 757, 912) call _layoutOutput() without parameters, causing early return

Consider either:

  • Tracking the last known line count as a class field to reuse in parameterless calls
  • Changing the guard to lineCount === undefined to allow layout for zero lines
  • Adding a separate code path for parameterless calls that measures the actual DOM

The old implementation measured the DOM directly and didn't have this issue.

Suggested change
if (!this._outputScrollbar || !this.isExpanded || !lineCount) {
if (!this._outputScrollbar || !this.isExpanded || lineCount === undefined) {

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +77
} catch {

Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

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

Empty catch block silently swallows errors. Consider logging the error or at least adding a comment explaining why it's safe to ignore errors here.

Suggested change
} catch {
} catch (error) {
console.error('Error in renderCommand _getCommandOutputAsVT:', error);

Copilot uses AI. Check for mistakes.
private readonly _terminalInstance: ITerminalInstance,
private readonly _command: ITerminalCommand,
@ITerminalService private readonly _terminalService: ITerminalService,
@IInstantiationService private readonly _instantationService: IInstantiationService
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

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

Typo in parameter name: _instantationService should be _instantiationService (missing 'i').

Suggested change
@IInstantiationService private readonly _instantationService: IInstantiationService
@IInstantiationService private readonly _instantiationService: IInstantiationService

Copilot uses AI. Check for mistakes.
return this._detachedTerminal;
}
const targetRef = this._terminalInstance?.targetRef ?? new ImmortalReference<TerminalLocation | undefined>(undefined);
const colorProvider = this._instantationService.createInstance(TerminalInstanceColorProvider, targetRef);
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

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

Typo in variable name: _instantationService should be _instantiationService (missing 'i').

Copilot uses AI. Check for mistakes.
});
}


Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

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

Extra blank line should be removed to maintain consistent spacing between methods.

Suggested change

Copilot uses AI. Check for mistakes.
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.

2 participants