| name | terminal-expert |
| description | This skill provides unified expert-level guidance for terminal implementation in VS Code extensions. Covers xterm.js API and addons, VS Code terminal architecture, PTY integration, session persistence, input handling (keyboard/IME/mouse), shell integration with OSC sequences, and performance optimization. Use when implementing any terminal-related features. Use when this capability is needed. |
| metadata | {"author":"s-hiraoku"} |
Terminal Expert
Overview
This skill provides comprehensive knowledge for implementing terminal features in VS Code extensions. It unifies xterm.js implementation details with VS Code's terminal architecture patterns, ensuring consistency with the official VS Code terminal implementation.
When to Use This Skill
- Creating and configuring xterm.js Terminal instances
- Implementing terminal addons (fit, webgl, serialize, search, etc.)
- Integrating with VS Code WebViews
- Implementing PTY (pseudo-terminal) process management
- Creating terminal session persistence and restoration
- Handling keyboard input, IME composition, and mouse events
- Implementing shell integration with OSC 633 sequences
- Optimizing terminal performance (GPU acceleration, buffering)
- Managing terminal lifecycle and resource cleanup
Quick Reference
Essential Addons
| Addon | Package | Purpose | Loading |
|---|
| FitAddon | @xterm/addon-fit | Auto-resize to container | Always |
| WebglAddon | @xterm/addon-webgl | GPU acceleration (30%+ perf) | Lazy |
| SerializeAddon | @xterm/addon-serialize | Session persistence | Lazy |
| SearchAddon | @xterm/addon-search | Find in terminal | Lazy |
| Unicode11Addon | @xterm/addon-unicode11 | Extended Unicode | Config |
| WebLinksAddon | @xterm/addon-web-links | Clickable URLs | Config |
| ImageAddon | @xterm/addon-image | Inline images | Config |
Performance Targets
| Operation | Target | Notes |
|---|
| Terminal creation | <500ms | Including PTY spawn |
| Session restore | <3s | 1000 lines scrollback |
| Terminal disposal | <100ms | Full cleanup |
| Resize handling | 100ms debounce | Prevents excessive calls |
| Output buffering | 16ms flush | ~60fps rendering |
xterm.js Implementation
Basic Terminal Setup
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import { WebglAddon } from '@xterm/addon-webgl';
class TerminalManager {
private terminal: Terminal;
private fitAddon: FitAddon;
private container: HTMLElement;
constructor(container: HTMLElement) {
this.container = container;
this.terminal = this.createTerminal();
this.fitAddon = new FitAddon();
}
private createTerminal(): Terminal {
return new Terminal({
fontFamily: '"Cascadia Code", "Fira Code", monospace',
fontSize: 14,
lineHeight: 1.2,
cursorStyle: 'block',
cursorBlink: true,
scrollback: 5000,
altClickMovesCursor: true,
fastScrollModifier: 'alt',
smoothScrollDuration: 0,
theme: {
background: '#1e1e1e',
foreground: '#d4d4d4',
cursor: '#d4d4d4',
selectionBackground: '#264f78'
}
});
}
initialize(): void {
this.terminal.loadAddon(this.fitAddon);
this.terminal.open(this.container);
this.fitAddon.fit();
this.setupResizeObserver();
}
private setupResizeObserver(): void {
const resizeObserver = new ResizeObserver(() => {
requestAnimationFrame(() => this.fitAddon.fit());
});
resizeObserver.observe(this.container);
}
}
WebGL Renderer with Fallback
class RenderingManager {
private webglAddon: WebglAddon | undefined;
private terminal: Terminal;
enableWebGL(): boolean {
try {
this.webglAddon = new WebglAddon();
this.webglAddon.onContextLoss(() => {
console.warn('WebGL context lost, falling back to canvas');
this.disableWebGL();
});
this.terminal.loadAddon(this.webglAddon);
return true;
} catch (error) {
console.warn('WebGL not available:', error);
return false;
}
}
disableWebGL(): void {
this.webglAddon?.dispose();
this.webglAddon = undefined;
}
}
Session Persistence with SerializeAddon
import { SerializeAddon } from '@xterm/addon-serialize';
class ScrollbackManager {
private serializeAddon: SerializeAddon;
private terminal: Terminal;
constructor(terminal: Terminal) {
this.terminal = terminal;
this.serializeAddon = new SerializeAddon();
this.terminal.loadAddon(this.serializeAddon);
}
saveScrollback(options?: { scrollback?: number }): string {
return this.serializeAddon.serialize({
scrollback: options?.scrollback ?? 1000,
excludeModes: true,
excludeAltBuffer: true
});
}
restoreScrollback(content: string): void {
this.terminal.write(content);
}
}
Output Buffering for High-Frequency Output
class OutputBuffer {
private terminal: Terminal;
private buffer: string = '';
private flushScheduled = false;
private flushInterval: number;
constructor(terminal: Terminal, flushInterval: number = 16) {
this.terminal = terminal;
this.flushInterval = flushInterval;
}
write(data: string): void {
this.buffer += data;
if (this.buffer.length > 10000) {
this.flushInterval = 4;
}
this.scheduleFlush();
}
private scheduleFlush(): void {
if (!this.flushScheduled) {
this.flushScheduled = true;
setTimeout(() => this.flush(), this.flushInterval);
}
}
private flush(): void {
if (this.buffer.length > 0) {
this.terminal.write(this.buffer);
this.buffer = '';
}
this.flushScheduled = false;
if (this.buffer.length === 0) {
this.flushInterval = 16;
}
}
}
VS Code Integration
Multi-Process Architecture
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Main Process (Electron) ā
āāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāā¼āāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāā
ā¼ ā¼ ā¼ ā¼
āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā
ā Renderer ā ā Extension ā ā PTY Host ā ā Shared ā
ā Process ā ā Host Process ā ā Process ā ā Process ā
āāāāāāāāāāāāāāā⤠āāāāāāāāāāāāāāā⤠āāāāāāāāāāāāāāā⤠āāāāāāāāāāāāāāāā¤
ā - Workbench ā ā - Extension ā ā - PtyService ā ā - Extension ā
ā - XtermTerm ā ā execution ā ā - node-pty ā ā mgmt ā
ā - UI ā ā - ExtHost ā ā - Shell ā ā ā
āāāāāāāāāāāāāāāā ā Terminal ā ā processes ā āāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā
PTY Integration Pattern
import * as pty from 'node-pty';
class TerminalProcess {
private _ptyProcess: pty.IPty;
constructor(
shellPath: string,
args: string[],
cwd: string,
cols: number,
rows: number,
env: { [key: string]: string }
) {
this._ptyProcess = pty.spawn(shellPath, args, {
name: 'xterm-256color',
cols,
rows,
cwd,
env
});
this._ptyProcess.onData(data => this._onData.fire(data));
this._ptyProcess.onExit(({ exitCode }) => this._onExit.fire(exitCode));
}
write(data: string): void {
this._ptyProcess.write(data);
}
resize(cols: number, rows: number): void {
if (cols < 1 || rows < 1) return;
this._ptyProcess.resize(cols, rows);
}
shutdown(): void {
this._ptyProcess.kill();
}
}
Flow Control (Critical for Performance)
class FlowControlledProcess {
private _unacknowledgedCharCount = 0;
private _isPaused = false;
private readonly HIGH_WATERMARK = 100000;
private readonly LOW_WATERMARK = 5000;
handleData(data: string): void {
this._unacknowledgedCharCount += data.length;
if (!this._isPaused && this._unacknowledgedCharCount > this.HIGH_WATERMARK) {
this._ptyProcess.pause();
this._isPaused = true;
}
this._onData.fire(data);
}
acknowledge(charCount: number): void {
this._unacknowledgedCharCount -= charCount;
if (this._isPaused && this._unacknowledgedCharCount <= this.LOW_WATERMARK) {
this._ptyProcess.resume();
this._isPaused = false;
}
}
}
Input Handling
Keyboard Input Flow
User Keypress ā Browser KeyboardEvent ā Custom key event handler
ā Check VS Code keybindings
āāā [Match] ā Execute command, preventDefault
āāā [No Match] ā Pass to xterm.js ā Emits onData ā Write to PTY
Custom Key Handler
terminal.attachCustomKeyEventHandler((event: KeyboardEvent) => {
if (event.ctrlKey && event.key === 'c' && terminal.hasSelection()) {
navigator.clipboard.writeText(terminal.getSelection());
return false;
}
if (event.ctrlKey && event.key === 'v') {
navigator.clipboard.readText().then(text => terminal.paste(text));
return false;
}
if (event.ctrlKey && event.key === 'l') {
terminal.clear();
return false;
}
return true;
});
IME Composition (CJK Input)
class IMEHandler {
private _composing = false;
handleCompositionStart(): void {
this._composing = true;
}
handleCompositionEnd(text: string): void {
this._composing = false;
this._pty.write(text);
}
handleKeyDown(event: KeyboardEvent): boolean {
if (this._composing) return false;
return true;
}
}
Shell Integration (OSC 633)
Protocol
OSC 633 ; A ST ā Prompt start
OSC 633 ; B ST ā Command line start
OSC 633 ; C ST ā Command execution start
OSC 633 ; D [; <ExitCode>] ST ā Command finished
OSC 633 ; P ; <Property>=<Value> ST ā Set property
Shell Integration Addon
class ShellIntegrationAddon implements ITerminalAddon {
activate(terminal: Terminal): void {
terminal.parser.registerOscHandler(633, data => {
const [code, ...params] = data.split(';');
switch (code) {
case 'A': this._handlePromptStart(); return true;
case 'B': this._handleCommandStart(); return true;
case 'C': this._handleExecutionStart(); return true;
case 'D':
const exitCode = params[0] ? parseInt(params[0]) : undefined;
this._handleCommandFinished(exitCode);
return true;
}
return false;
});
}
}
Lifecycle Management
DisposableStore Pattern (from VS Code)
abstract class Disposable implements IDisposable {
private readonly _store = new DisposableStore();
private _isDisposed = false;
protected _register<T extends IDisposable>(disposable: T): T {
if (this._isDisposed) {
disposable.dispose();
return disposable;
}
return this._store.add(disposable);
}
dispose(): void {
if (this._isDisposed) return;
this._isDisposed = true;
this._store.dispose();
}
}
class DisposableStore implements IDisposable {
private readonly _toDispose = new Set<IDisposable>();
add<T extends IDisposable>(disposable: T): T {
this._toDispose.add(disposable);
return disposable;
}
dispose(): void {
const items = Array.from(this._toDispose).reverse();
this._toDispose.clear();
items.forEach(item => item.dispose());
}
}
VS Code WebView Integration
Complete Integration Example
class XtermVSCodeIntegration {
private terminal: Terminal;
private vscode: any;
private fitAddon: FitAddon;
constructor(container: HTMLElement) {
this.vscode = acquireVsCodeApi();
this.terminal = new Terminal({
fontFamily: 'var(--vscode-editor-font-family)',
fontSize: 14,
theme: this.getVSCodeTheme()
});
this.fitAddon = new FitAddon();
this.terminal.loadAddon(this.fitAddon);
this.terminal.open(container);
this.fitAddon.fit();
this.setupCommunication();
}
private getVSCodeTheme(): ITheme {
const style = getComputedStyle(document.body);
return {
background: style.getPropertyValue('--vscode-terminal-background').trim() ||
style.getPropertyValue('--vscode-editor-background').trim(),
foreground: style.getPropertyValue('--vscode-terminal-foreground').trim(),
cursor: style.getPropertyValue('--vscode-terminalCursor-foreground').trim(),
selectionBackground: style.getPropertyValue('--vscode-terminal-selectionBackground').trim()
};
}
private setupCommunication(): void {
this.terminal.onData(data => {
this.vscode.postMessage({ type: 'input', data });
});
window.addEventListener('message', event => {
const message = event.data;
switch (message.type) {
case 'output': this.terminal.write(message.data); break;
case 'resize': this.terminal.resize(message.cols, message.rows); break;
case 'clear': this.terminal.clear(); break;
case 'focus': this.terminal.focus(); break;
}
});
this.vscode.postMessage({ type: 'ready' });
}
}
ANSI Escape Sequences Reference
const ANSI = {
cursorUp: (n: number) => `\x1b[${n}A`,
cursorDown: (n: number) => `\x1b[${n}B`,
cursorPosition: (row: number, col: number) => `\x1b[${row};${col}H`,
eraseLine: '\x1b[2K',
eraseScreen: '\x1b[2J',
reset: '\x1b[0m',
bold: '\x1b[1m',
italic: '\x1b[3m',
underline: '\x1b[4m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
fg256: (n: number) => `\x1b[38;5;${n}m`,
bg256: (n: number) => `\x1b[48;5;${n}m`,
fgRGB: (r: number, g: number, b: number) => `\x1b[38;2;${r};${g};${b}m`,
bgRGB: (r: number, g: number, b: number) => `\x1b[48;2;${r};${g};${b}m`,
alternateScreen: '\x1b[?1049h',
normalScreen: '\x1b[?1049l',
hideCursor: '\x1b[?25l',
showCursor: '\x1b[?25h'
};
References
For detailed reference documentation:
references/xterm-api.md - Complete xterm.js API reference
references/vscode-terminal.md - VS Code terminal source reference
references/escape-sequences.md - ANSI escape sequence reference
For implementation reference:
Converted and distributed by TomeVault ā claim your Tome and manage your conversions.