import type { TerminalLine } from '../types';
export class LineRenderer {
private container: HTMLElement;
constructor(container: HTMLElement) {
this.container = container;
}
render(line: TerminalLine): HTMLElement {
const lineElement = document.createElement('div');
lineElement.className = `terminal-line terminal-line-${line.type}`;
lineElement.setAttribute('data-id', line.id);
switch (line.type) {
case 'input':
lineElement.innerHTML = `${this.escapeHtml(this.getPromptFromLine(line.content))}${this.escapeHtml(this.getInputFromLine(line.content))}`;
break;
case 'output':
lineElement.innerHTML = this.formatOutput(line.content);
break;
case 'error':
lineElement.innerHTML = `${this.escapeHtml(line.content)}`;
break;
case 'prompt':
lineElement.innerHTML = `${this.escapeHtml(line.content)}`;
break;
}
return lineElement;
}
private getPromptFromLine(content: string): string {
// Extract prompt if it's in the format "user:path$ command"
const match = content.match(/^(.+?\$)\s/);
return match ? match[1] + ' ' : '';
}
private getInputFromLine(content: string): string {
// Extract input after prompt
const match = content.match(/^.+?\$\s(.*)$/);
return match ? match[1] : content;
}
private formatOutput(content: string): string {
// Handle ANSI escape codes (basic support)
let formatted = this.escapeHtml(content);
// Clear screen codes
if (formatted.includes('\x1b[2J\x1b[H')) {
return '';
}
// Color codes (basic support)
formatted = formatted
.replace(/\x1b\[31m/g, '')
.replace(/\x1b\[32m/g, '')
.replace(/\x1b\[33m/g, '')
.replace(/\x1b\[34m/g, '')
.replace(/\x1b\[35m/g, '')
.replace(/\x1b\[36m/g, '')
.replace(/\x1b\[0m/g, '');
return `${formatted}`;
}
private escapeHtml(text: string): string {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
appendLine(line: TerminalLine): void {
const lineElement = this.render(line);
// Check if this is a clear screen marker
if (line.type === 'output' && line.content.includes('\x1b[2J\x1b[H')) {
this.clear();
return;
}
this.container.appendChild(lineElement);
this.scrollToBottom();
}
clear(): void {
this.container.innerHTML = '';
}
private scrollToBottom(): void {
this.container.scrollTop = this.container.scrollHeight;
}
}