终端 API
概述
Trae 终端 API 提供了强大的终端集成功能,允许开发者创建、管理和控制终端实例。通过终端 API,可以执行命令、处理输入输出、自定义终端行为等。
核心概念
终端接口
typescript
interface Terminal {
readonly name: string;
readonly processId: Thenable<number | undefined>;
readonly creationOptions: Readonly<TerminalOptions | ExtensionTerminalOptions>;
readonly exitStatus: TerminalExitStatus | undefined;
readonly state: TerminalState;
sendText(text: string, addNewLine?: boolean): void;
show(preserveFocus?: boolean): void;
hide(): void;
dispose(): void;
}
interface TerminalOptions {
name?: string;
shellPath?: string;
shellArgs?: string[] | string;
cwd?: string | Uri;
env?: { [key: string]: string | null | undefined };
strictEnv?: boolean;
hideFromUser?: boolean;
message?: string;
iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon;
color?: ThemeColor;
location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions;
isTransient?: boolean;
}
interface ExtensionTerminalOptions {
name: string;
pty: Pseudoterminal;
iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon;
color?: ThemeColor;
location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions;
isTransient?: boolean;
}
interface TerminalExitStatus {
readonly code: number | undefined;
readonly reason: TerminalExitReason;
}
enum TerminalExitReason {
Unknown = 0,
Shutdown = 1,
Process = 2,
User = 3,
Extension = 4
}
interface TerminalState {
readonly isInteractedWith: boolean;
}伪终端接口
typescript
interface Pseudoterminal {
onDidWrite: Event<string>;
onDidOverrideDimensions?: Event<TerminalDimensions | undefined>;
onDidClose?: Event<number | void>;
onDidChangeName?: Event<string>;
open(initialDimensions: TerminalDimensions | undefined): void;
close(): void;
handleInput?(data: string): void;
setDimensions?(dimensions: TerminalDimensions): void;
}
interface TerminalDimensions {
readonly columns: number;
readonly rows: number;
}API 参考
终端管理
基础终端操作
typescript
import { window, Terminal, TerminalOptions } from '@trae/api';
// 创建终端
const createTerminal = (options?: TerminalOptions): Terminal => {
return window.createTerminal(options);
};
// 创建命名终端
const createNamedTerminal = (name: string, shellPath?: string): Terminal => {
return window.createTerminal({
name,
shellPath,
iconPath: new ThemeIcon('terminal'),
color: new ThemeColor('terminal.ansiBlue')
});
};
// 获取活动终端
const getActiveTerminal = (): Terminal | undefined => {
return window.activeTerminal;
};
// 获取所有终端
const getAllTerminals = (): readonly Terminal[] => {
return window.terminals;
};
// 显示终端
const showTerminal = (terminal: Terminal, preserveFocus = false): void => {
terminal.show(preserveFocus);
};
// 发送文本到终端
const sendTextToTerminal = (terminal: Terminal, text: string, addNewLine = true): void => {
terminal.sendText(text, addNewLine);
};
// 关闭终端
const closeTerminal = (terminal: Terminal): void => {
terminal.dispose();
};
// 监听终端事件
const onDidOpenTerminal = (listener: (terminal: Terminal) => void): Disposable => {
return window.onDidOpenTerminal(listener);
};
const onDidCloseTerminal = (listener: (terminal: Terminal) => void): Disposable => {
return window.onDidCloseTerminal(listener);
};
const onDidChangeActiveTerminal = (
listener: (terminal: Terminal | undefined) => void
): Disposable => {
return window.onDidChangeActiveTerminal(listener);
};
const onDidChangeTerminalState = (
listener: (terminal: Terminal) => void
): Disposable => {
return window.onDidChangeTerminalState(listener);
};高级终端配置
typescript
// 创建具有自定义环境的终端
const createTerminalWithEnv = (env: Record<string, string>): Terminal => {
return window.createTerminal({
name: 'Custom Environment',
env: {
...process.env,
...env,
CUSTOM_VAR: 'custom_value'
},
cwd: workspace.rootPath,
shellPath: '/bin/bash',
shellArgs: ['--login']
});
};
// 创建开发环境终端
const createDevTerminal = (): Terminal => {
return window.createTerminal({
name: 'Development',
env: {
NODE_ENV: 'development',
DEBUG: '*',
PATH: `${workspace.rootPath}/node_modules/.bin:${process.env.PATH}`
},
cwd: workspace.rootPath,
iconPath: new ThemeIcon('gear'),
color: new ThemeColor('terminal.ansiGreen')
});
};
// 创建生产环境终端
const createProdTerminal = (): Terminal => {
return window.createTerminal({
name: 'Production',
env: {
NODE_ENV: 'production'
},
iconPath: new ThemeIcon('server'),
color: new ThemeColor('terminal.ansiRed')
});
};
// 创建分割终端
const createSplitTerminal = (parentTerminal: Terminal): Terminal => {
return window.createTerminal({
name: 'Split Terminal',
location: {
parentTerminal,
splitDirection: TerminalSplitDirection.Right
}
});
};
// 创建编辑器终端
const createEditorTerminal = (): Terminal => {
return window.createTerminal({
name: 'Editor Terminal',
location: TerminalLocation.Editor
});
};自定义伪终端
基础伪终端实现
typescript
class CustomPseudoterminal implements Pseudoterminal {
private writeEmitter = new EventEmitter<string>();
private closeEmitter = new EventEmitter<number | void>();
private nameEmitter = new EventEmitter<string>();
onDidWrite: Event<string> = this.writeEmitter.event;
onDidClose: Event<number | void> = this.closeEmitter.event;
onDidChangeName: Event<string> = this.nameEmitter.event;
private currentDirectory = process.cwd();
private commandHistory: string[] = [];
private historyIndex = -1;
private currentLine = '';
private cursorPosition = 0;
open(initialDimensions: TerminalDimensions | undefined): void {
this.writeEmitter.fire('Welcome to Custom Terminal!\r\n');
this.showPrompt();
}
close(): void {
this.writeEmitter.fire('\r\nTerminal closed.\r\n');
this.closeEmitter.fire(0);
}
handleInput(data: string): void {
switch (data) {
case '\r': // Enter
this.executeCommand();
break;
case '\x7f': // Backspace
this.handleBackspace();
break;
case '\x1b[A': // Up arrow
this.navigateHistory(-1);
break;
case '\x1b[B': // Down arrow
this.navigateHistory(1);
break;
case '\x1b[C': // Right arrow
this.moveCursor(1);
break;
case '\x1b[D': // Left arrow
this.moveCursor(-1);
break;
case '\x03': // Ctrl+C
this.handleInterrupt();
break;
default:
if (data >= ' ' && data <= '~') {
this.insertCharacter(data);
}
}
}
private showPrompt(): void {
const prompt = `\r\n${this.currentDirectory}$ `;
this.writeEmitter.fire(prompt);
this.currentLine = '';
this.cursorPosition = 0;
}
private executeCommand(): void {
this.writeEmitter.fire('\r\n');
const command = this.currentLine.trim();
if (command) {
this.commandHistory.push(command);
this.historyIndex = this.commandHistory.length;
this.processCommand(command);
} else {
this.showPrompt();
}
}
private processCommand(command: string): void {
const [cmd, ...args] = command.split(' ');
switch (cmd) {
case 'echo':
this.writeEmitter.fire(args.join(' ') + '\r\n');
break;
case 'pwd':
this.writeEmitter.fire(this.currentDirectory + '\r\n');
break;
case 'cd':
this.changeDirectory(args[0] || process.env.HOME || '/');
break;
case 'ls':
this.listDirectory();
break;
case 'clear':
this.writeEmitter.fire('\x1b[2J\x1b[H');
break;
case 'help':
this.showHelp();
break;
case 'exit':
this.close();
return;
default:
this.writeEmitter.fire(`Command not found: ${cmd}\r\n`);
}
this.showPrompt();
}
private changeDirectory(path: string): void {
try {
const newPath = path.startsWith('/') ? path : `${this.currentDirectory}/${path}`;
// 这里应该验证路径是否存在
this.currentDirectory = newPath;
this.nameEmitter.fire(`Terminal - ${this.currentDirectory}`);
} catch (error) {
this.writeEmitter.fire(`cd: ${path}: No such file or directory\r\n`);
}
}
private listDirectory(): void {
// 模拟 ls 命令
const files = ['file1.txt', 'file2.js', 'directory1/', 'directory2/'];
this.writeEmitter.fire(files.join(' ') + '\r\n');
}
private showHelp(): void {
const helpText = [
'Available commands:',
' echo <text> - Print text',
' pwd - Print working directory',
' cd <path> - Change directory',
' ls - List directory contents',
' clear - Clear screen',
' help - Show this help',
' exit - Exit terminal',
''
].join('\r\n');
this.writeEmitter.fire(helpText);
}
private handleBackspace(): void {
if (this.cursorPosition > 0) {
this.currentLine =
this.currentLine.slice(0, this.cursorPosition - 1) +
this.currentLine.slice(this.cursorPosition);
this.cursorPosition--;
this.redrawLine();
}
}
private insertCharacter(char: string): void {
this.currentLine =
this.currentLine.slice(0, this.cursorPosition) +
char +
this.currentLine.slice(this.cursorPosition);
this.cursorPosition++;
this.redrawLine();
}
private moveCursor(direction: number): void {
const newPosition = this.cursorPosition + direction;
if (newPosition >= 0 && newPosition <= this.currentLine.length) {
this.cursorPosition = newPosition;
this.writeEmitter.fire(direction > 0 ? '\x1b[C' : '\x1b[D');
}
}
private navigateHistory(direction: number): void {
const newIndex = this.historyIndex + direction;
if (newIndex >= 0 && newIndex < this.commandHistory.length) {
this.historyIndex = newIndex;
this.currentLine = this.commandHistory[this.historyIndex];
this.cursorPosition = this.currentLine.length;
this.redrawLine();
} else if (newIndex === this.commandHistory.length) {
this.historyIndex = newIndex;
this.currentLine = '';
this.cursorPosition = 0;
this.redrawLine();
}
}
private redrawLine(): void {
// 清除当前行并重绘
this.writeEmitter.fire('\r\x1b[K');
this.writeEmitter.fire(`${this.currentDirectory}$ ${this.currentLine}`);
// 移动光标到正确位置
const promptLength = this.currentDirectory.length + 2;
const targetPosition = promptLength + this.cursorPosition;
const currentPosition = promptLength + this.currentLine.length;
if (targetPosition < currentPosition) {
const moves = currentPosition - targetPosition;
this.writeEmitter.fire('\x1b[D'.repeat(moves));
}
}
private handleInterrupt(): void {
this.writeEmitter.fire('^C');
this.showPrompt();
}
}
// 使用自定义伪终端
const createCustomTerminal = (): Terminal => {
const pty = new CustomPseudoterminal();
return window.createTerminal({
name: 'Custom Terminal',
pty,
iconPath: new ThemeIcon('terminal-bash'),
color: new ThemeColor('terminal.ansiCyan')
});
};专用伪终端实现
typescript
// 日志查看器终端
class LogViewerPseudoterminal implements Pseudoterminal {
private writeEmitter = new EventEmitter<string>();
private closeEmitter = new EventEmitter<number | void>();
onDidWrite: Event<string> = this.writeEmitter.event;
onDidClose: Event<number | void> = this.closeEmitter.event;
private logFile: string;
private watcher?: FileSystemWatcher;
private isFollowing = true;
constructor(logFile: string) {
this.logFile = logFile;
}
open(initialDimensions: TerminalDimensions | undefined): void {
this.writeEmitter.fire(`Watching log file: ${this.logFile}\r\n`);
this.writeEmitter.fire('Press Ctrl+C to stop following, Ctrl+D to exit\r\n\r\n');
this.startWatching();
this.loadExistingContent();
}
close(): void {
this.stopWatching();
this.closeEmitter.fire(0);
}
handleInput(data: string): void {
switch (data) {
case '\x03': // Ctrl+C
this.toggleFollowing();
break;
case '\x04': // Ctrl+D
this.close();
break;
case 'r':
this.refresh();
break;
}
}
private async loadExistingContent(): Promise<void> {
try {
const content = await workspace.fs.readFile(Uri.file(this.logFile));
const lines = content.toString().split('\n');
// 显示最后 100 行
const recentLines = lines.slice(-100);
for (const line of recentLines) {
if (line.trim()) {
this.writeEmitter.fire(this.formatLogLine(line) + '\r\n');
}
}
} catch (error) {
this.writeEmitter.fire(`Error reading log file: ${error.message}\r\n`);
}
}
private startWatching(): void {
this.watcher = workspace.createFileSystemWatcher(this.logFile);
this.watcher.onDidChange(() => {
if (this.isFollowing) {
this.loadNewContent();
}
});
}
private stopWatching(): void {
if (this.watcher) {
this.watcher.dispose();
this.watcher = undefined;
}
}
private async loadNewContent(): Promise<void> {
// 实现增量读取新内容的逻辑
// 这里简化为重新读取文件
try {
const content = await workspace.fs.readFile(Uri.file(this.logFile));
const lines = content.toString().split('\n');
const newLines = lines.slice(-10); // 获取最新的 10 行
for (const line of newLines) {
if (line.trim()) {
this.writeEmitter.fire(this.formatLogLine(line) + '\r\n');
}
}
} catch (error) {
// 忽略读取错误
}
}
private formatLogLine(line: string): string {
// 添加颜色和格式化
if (line.includes('ERROR')) {
return `\x1b[31m${line}\x1b[0m`; // 红色
} else if (line.includes('WARN')) {
return `\x1b[33m${line}\x1b[0m`; // 黄色
} else if (line.includes('INFO')) {
return `\x1b[32m${line}\x1b[0m`; // 绿色
} else if (line.includes('DEBUG')) {
return `\x1b[36m${line}\x1b[0m`; // 青色
}
return line;
}
private toggleFollowing(): void {
this.isFollowing = !this.isFollowing;
const status = this.isFollowing ? 'Following' : 'Paused';
this.writeEmitter.fire(`\r\n[${status}] Press Ctrl+C to ${this.isFollowing ? 'pause' : 'resume'}\r\n`);
}
private refresh(): void {
this.writeEmitter.fire('\x1b[2J\x1b[H'); // 清屏
this.writeEmitter.fire(`Refreshing log file: ${this.logFile}\r\n\r\n`);
this.loadExistingContent();
}
}
// 进程监控终端
class ProcessMonitorPseudoterminal implements Pseudoterminal {
private writeEmitter = new EventEmitter<string>();
private closeEmitter = new EventEmitter<number | void>();
onDidWrite: Event<string> = this.writeEmitter.event;
onDidClose: Event<number | void> = this.closeEmitter.event;
private process?: ChildProcess;
private command: string;
private args: string[];
private options: SpawnOptions;
constructor(command: string, args: string[] = [], options: SpawnOptions = {}) {
this.command = command;
this.args = args;
this.options = options;
}
open(initialDimensions: TerminalDimensions | undefined): void {
this.writeEmitter.fire(`Starting process: ${this.command} ${this.args.join(' ')}\r\n`);
this.startProcess();
}
close(): void {
this.stopProcess();
this.closeEmitter.fire(0);
}
handleInput(data: string): void {
if (this.process && this.process.stdin) {
this.process.stdin.write(data);
}
}
private startProcess(): void {
try {
this.process = spawn(this.command, this.args, {
...this.options,
stdio: ['pipe', 'pipe', 'pipe']
});
this.process.stdout?.on('data', (data) => {
this.writeEmitter.fire(data.toString());
});
this.process.stderr?.on('data', (data) => {
this.writeEmitter.fire(`\x1b[31m${data.toString()}\x1b[0m`); // 红色错误输出
});
this.process.on('close', (code) => {
this.writeEmitter.fire(`\r\nProcess exited with code ${code}\r\n`);
this.closeEmitter.fire(code || 0);
});
this.process.on('error', (error) => {
this.writeEmitter.fire(`\r\nProcess error: ${error.message}\r\n`);
this.closeEmitter.fire(1);
});
} catch (error) {
this.writeEmitter.fire(`Failed to start process: ${error.message}\r\n`);
this.closeEmitter.fire(1);
}
}
private stopProcess(): void {
if (this.process) {
this.process.kill();
this.process = undefined;
}
}
}终端管理器
终端会话管理
typescript
class TerminalManager {
private terminals: Map<string, Terminal> = new Map();
private disposables: Disposable[] = [];
constructor() {
this.setupEventListeners();
}
private setupEventListeners(): void {
this.disposables.push(
window.onDidOpenTerminal(terminal => {
this.onTerminalOpened(terminal);
}),
window.onDidCloseTerminal(terminal => {
this.onTerminalClosed(terminal);
}),
window.onDidChangeActiveTerminal(terminal => {
this.onActiveTerminalChanged(terminal);
})
);
}
private onTerminalOpened(terminal: Terminal): void {
const id = this.generateTerminalId(terminal);
this.terminals.set(id, terminal);
console.log(`Terminal opened: ${terminal.name} (${id})`);
}
private onTerminalClosed(terminal: Terminal): void {
const id = this.findTerminalId(terminal);
if (id) {
this.terminals.delete(id);
console.log(`Terminal closed: ${terminal.name} (${id})`);
}
}
private onActiveTerminalChanged(terminal: Terminal | undefined): void {
if (terminal) {
console.log(`Active terminal changed to: ${terminal.name}`);
} else {
console.log('No active terminal');
}
}
private generateTerminalId(terminal: Terminal): string {
return `${terminal.name}-${Date.now()}`;
}
private findTerminalId(terminal: Terminal): string | undefined {
for (const [id, t] of this.terminals) {
if (t === terminal) {
return id;
}
}
return undefined;
}
// 创建预定义终端
createDevelopmentTerminal(): Terminal {
const terminal = window.createTerminal({
name: 'Development',
env: {
NODE_ENV: 'development',
DEBUG: '*'
},
iconPath: new ThemeIcon('gear'),
color: new ThemeColor('terminal.ansiGreen')
});
// 发送初始化命令
terminal.sendText('echo "Development environment ready"');
return terminal;
}
createTestTerminal(): Terminal {
const terminal = window.createTerminal({
name: 'Test Runner',
iconPath: new ThemeIcon('beaker'),
color: new ThemeColor('terminal.ansiYellow')
});
return terminal;
}
createBuildTerminal(): Terminal {
const terminal = window.createTerminal({
name: 'Build',
iconPath: new ThemeIcon('tools'),
color: new ThemeColor('terminal.ansiBlue')
});
return terminal;
}
// 终端查找和管理
findTerminalByName(name: string): Terminal | undefined {
return Array.from(this.terminals.values()).find(t => t.name === name);
}
getOrCreateTerminal(name: string, factory: () => Terminal): Terminal {
let terminal = this.findTerminalByName(name);
if (!terminal || terminal.exitStatus) {
terminal = factory();
}
return terminal;
}
closeAllTerminals(): void {
for (const terminal of this.terminals.values()) {
terminal.dispose();
}
this.terminals.clear();
}
getTerminalCount(): number {
return this.terminals.size;
}
getActiveTerminals(): Terminal[] {
return Array.from(this.terminals.values()).filter(t => !t.exitStatus);
}
dispose(): void {
this.disposables.forEach(d => d.dispose());
this.closeAllTerminals();
}
}终端命令执行器
typescript
class TerminalCommandExecutor {
private terminal: Terminal;
private commandQueue: QueuedCommand[] = [];
private isExecuting = false;
constructor(terminal: Terminal) {
this.terminal = terminal;
}
async executeCommand(
command: string,
options: CommandExecutionOptions = {}
): Promise<CommandResult> {
return new Promise((resolve, reject) => {
const queuedCommand: QueuedCommand = {
command,
options,
resolve,
reject,
timestamp: Date.now()
};
this.commandQueue.push(queuedCommand);
this.processQueue();
});
}
private async processQueue(): Promise<void> {
if (this.isExecuting || this.commandQueue.length === 0) {
return;
}
this.isExecuting = true;
while (this.commandQueue.length > 0) {
const queuedCommand = this.commandQueue.shift()!;
await this.executeQueuedCommand(queuedCommand);
}
this.isExecuting = false;
}
private async executeQueuedCommand(queuedCommand: QueuedCommand): Promise<void> {
const { command, options, resolve, reject } = queuedCommand;
try {
// 确保终端可见
if (options.showTerminal !== false) {
this.terminal.show(options.preserveFocus);
}
// 发送命令
this.terminal.sendText(command, true);
// 如果需要等待完成
if (options.waitForCompletion) {
const result = await this.waitForCommandCompletion(command, options.timeout);
resolve(result);
} else {
resolve({ success: true, output: '', exitCode: 0 });
}
} catch (error) {
reject(error);
}
}
private async waitForCommandCompletion(
command: string,
timeout = 30000
): Promise<CommandResult> {
// 这里需要实现命令完成检测逻辑
// 由于 Terminal API 不直接提供输出捕获,这是一个简化的实现
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`Command timeout: ${command}`));
}, timeout);
// 模拟命令执行完成
setTimeout(() => {
clearTimeout(timer);
resolve({
success: true,
output: 'Command executed successfully',
exitCode: 0
});
}, 1000);
});
}
async executeBatch(commands: string[], options: BatchExecutionOptions = {}): Promise<BatchResult> {
const results: CommandResult[] = [];
for (const command of commands) {
try {
const result = await this.executeCommand(command, {
...options,
waitForCompletion: true
});
results.push(result);
// 如果命令失败且设置了停止执行
if (!result.success && options.stopOnError) {
break;
}
} catch (error) {
results.push({
success: false,
output: error.message,
exitCode: 1
});
if (options.stopOnError) {
break;
}
}
}
return {
results,
allSuccessful: results.every(r => r.success),
failedCount: results.filter(r => !r.success).length
};
}
clearQueue(): void {
this.commandQueue.forEach(cmd => {
cmd.reject(new Error('Command cancelled'));
});
this.commandQueue = [];
}
getQueueLength(): number {
return this.commandQueue.length;
}
}
interface QueuedCommand {
command: string;
options: CommandExecutionOptions;
resolve: (result: CommandResult) => void;
reject: (error: Error) => void;
timestamp: number;
}
interface CommandExecutionOptions {
showTerminal?: boolean;
preserveFocus?: boolean;
waitForCompletion?: boolean;
timeout?: number;
}
interface CommandResult {
success: boolean;
output: string;
exitCode: number;
}
interface BatchExecutionOptions extends CommandExecutionOptions {
stopOnError?: boolean;
delay?: number;
}
interface BatchResult {
results: CommandResult[];
allSuccessful: boolean;
failedCount: number;
}终端主题和样式
终端外观定制
typescript
class TerminalThemeManager {
private static readonly THEME_COLORS = {
// 标准 ANSI 颜色
black: '\x1b[30m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
white: '\x1b[37m',
// 亮色
brightBlack: '\x1b[90m',
brightRed: '\x1b[91m',
brightGreen: '\x1b[92m',
brightYellow: '\x1b[93m',
brightBlue: '\x1b[94m',
brightMagenta: '\x1b[95m',
brightCyan: '\x1b[96m',
brightWhite: '\x1b[97m',
// 背景色
bgBlack: '\x1b[40m',
bgRed: '\x1b[41m',
bgGreen: '\x1b[42m',
bgYellow: '\x1b[43m',
bgBlue: '\x1b[44m',
bgMagenta: '\x1b[45m',
bgCyan: '\x1b[46m',
bgWhite: '\x1b[47m',
// 重置
reset: '\x1b[0m',
bold: '\x1b[1m',
dim: '\x1b[2m',
italic: '\x1b[3m',
underline: '\x1b[4m',
blink: '\x1b[5m',
reverse: '\x1b[7m',
strikethrough: '\x1b[9m'
};
static colorize(text: string, color: keyof typeof TerminalThemeManager.THEME_COLORS): string {
return `${this.THEME_COLORS[color]}${text}${this.THEME_COLORS.reset}`;
}
static createColoredText(text: string, options: ColorOptions): string {
let result = '';
if (options.foreground) {
result += this.THEME_COLORS[options.foreground];
}
if (options.background) {
result += this.THEME_COLORS[options.background];
}
if (options.bold) {
result += this.THEME_COLORS.bold;
}
if (options.italic) {
result += this.THEME_COLORS.italic;
}
if (options.underline) {
result += this.THEME_COLORS.underline;
}
result += text + this.THEME_COLORS.reset;
return result;
}
static createProgressBar(progress: number, width = 20): string {
const filled = Math.floor((progress / 100) * width);
const empty = width - filled;
const bar = '█'.repeat(filled) + '░'.repeat(empty);
const percentage = `${progress.toFixed(1)}%`;
return this.colorize(`[${bar}] ${percentage}`, 'cyan');
}
static createTable(data: any[], headers: string[]): string {
const rows = [headers, ...data.map(row => headers.map(h => String(row[h] || '')))];
const columnWidths = headers.map((_, i) =>
Math.max(...rows.map(row => row[i].length))
);
const separator = '+' + columnWidths.map(w => '-'.repeat(w + 2)).join('+') + '+';
let result = separator + '\r\n';
// 表头
result += '|' + headers.map((header, i) =>
` ${this.colorize(header.padEnd(columnWidths[i]), 'bold')} `
).join('|') + '|\r\n';
result += separator + '\r\n';
// 数据行
for (let i = 1; i < rows.length; i++) {
result += '|' + rows[i].map((cell, j) =>
` ${cell.padEnd(columnWidths[j])} `
).join('|') + '|\r\n';
}
result += separator;
return result;
}
static createBanner(text: string, style: 'simple' | 'double' | 'rounded' = 'simple'): string {
const styles = {
simple: { h: '-', v: '|', tl: '+', tr: '+', bl: '+', br: '+' },
double: { h: '═', v: '║', tl: '╔', tr: '╗', bl: '╚', br: '╝' },
rounded: { h: '─', v: '│', tl: '╭', tr: '╮', bl: '╰', br: '╯' }
};
const chars = styles[style];
const width = text.length + 4;
let result = '';
result += chars.tl + chars.h.repeat(width - 2) + chars.tr + '\r\n';
result += chars.v + ` ${text} ` + chars.v + '\r\n';
result += chars.bl + chars.h.repeat(width - 2) + chars.br;
return this.colorize(result, 'cyan');
}
}
interface ColorOptions {
foreground?: keyof typeof TerminalThemeManager['THEME_COLORS'];
background?: keyof typeof TerminalThemeManager['THEME_COLORS'];
bold?: boolean;
italic?: boolean;
underline?: boolean;
}最佳实践
终端生命周期管理
typescript
// 1. 终端资源管理
class TerminalResourceManager {
private terminals: Set<Terminal> = new Set();
private disposables: Disposable[] = [];
createManagedTerminal(options: TerminalOptions): Terminal {
const terminal = window.createTerminal(options);
this.terminals.add(terminal);
// 监听终端关闭事件
const disposable = window.onDidCloseTerminal(closedTerminal => {
if (closedTerminal === terminal) {
this.terminals.delete(terminal);
disposable.dispose();
}
});
this.disposables.push(disposable);
return terminal;
}
dispose(): void {
// 关闭所有管理的终端
this.terminals.forEach(terminal => terminal.dispose());
this.terminals.clear();
// 清理事件监听器
this.disposables.forEach(d => d.dispose());
this.disposables = [];
}
}
// 2. 终端状态监控
class TerminalStatusMonitor {
private statusBar: StatusBarItem;
constructor() {
this.statusBar = window.createStatusBarItem(StatusBarAlignment.Right, 100);
this.statusBar.command = 'terminal.showAll';
this.updateStatus();
window.onDidOpenTerminal(() => this.updateStatus());
window.onDidCloseTerminal(() => this.updateStatus());
window.onDidChangeActiveTerminal(() => this.updateStatus());
}
private updateStatus(): void {
const terminalCount = window.terminals.length;
const activeTerminal = window.activeTerminal;
if (terminalCount === 0) {
this.statusBar.text = '$(terminal) No terminals';
this.statusBar.tooltip = 'Click to create a new terminal';
} else {
this.statusBar.text = `$(terminal) ${terminalCount} terminal${terminalCount > 1 ? 's' : ''}`;
this.statusBar.tooltip = activeTerminal
? `Active: ${activeTerminal.name}\nClick to show all terminals`
: 'Click to show all terminals';
}
this.statusBar.show();
}
dispose(): void {
this.statusBar.dispose();
}
}错误处理和调试
typescript
// 终端错误处理
class TerminalErrorHandler {
static handleTerminalError(error: Error, context: string): void {
console.error(`Terminal error in ${context}:`, error);
window.showErrorMessage(
`Terminal error: ${error.message}`,
'Show Details',
'Retry'
).then(action => {
if (action === 'Show Details') {
this.showErrorDetails(error, context);
} else if (action === 'Retry') {
// 实现重试逻辑
}
});
}
private static showErrorDetails(error: Error, context: string): void {
const channel = window.createOutputChannel('Terminal Errors');
channel.appendLine(`Error in ${context}:`);
channel.appendLine(`Message: ${error.message}`);
channel.appendLine(`Stack: ${error.stack}`);
channel.appendLine(`Timestamp: ${new Date().toISOString()}`);
channel.show();
}
static wrapTerminalOperation<T>(
operation: () => T,
context: string
): T | undefined {
try {
return operation();
} catch (error) {
this.handleTerminalError(error as Error, context);
return undefined;
}
}
static async wrapAsyncTerminalOperation<T>(
operation: () => Promise<T>,
context: string
): Promise<T | undefined> {
try {
return await operation();
} catch (error) {
this.handleTerminalError(error as Error, context);
return undefined;
}
}
}相关 API
示例项目
查看 终端 API 示例 了解完整的实现示例。