Fix tab rendering in TUI components

Replace tabs with 3 spaces for consistent rendering and width calculation:
- Updated visibleWidth() to normalize tabs before measuring
- Updated Text and Markdown components to replace tabs when rendering
- Updated tool-execution display for read/write tools to replace tabs

This fixes background color rendering issues when displaying files with tab indentation.
This commit is contained in:
Mario Zechner 2025-11-11 23:24:48 +01:00
parent 7beb354337
commit 001beff394
5 changed files with 49 additions and 5 deletions

View file

@ -13,6 +13,13 @@ function shortenPath(path: string): string {
return path;
}
/**
* Replace tabs with spaces for consistent rendering
*/
function replaceTabs(text: string): string {
return text.replace(/\t/g, " ");
}
/**
* Generate a unified diff between old and new strings with line numbers
*/
@ -120,7 +127,7 @@ export class ToolExecutionComponent extends Container {
const displayLines = lines.slice(0, maxLines);
const remaining = lines.length - maxLines;
text += "\n\n" + displayLines.map((line: string) => chalk.dim(line)).join("\n");
text += "\n\n" + displayLines.map((line: string) => chalk.dim(replaceTabs(line))).join("\n");
if (remaining > 0) {
text += chalk.dim(`\n... (${remaining} more lines)`);
}
@ -142,7 +149,7 @@ export class ToolExecutionComponent extends Container {
const displayLines = lines.slice(0, maxLines);
const remaining = lines.length - maxLines;
text += "\n\n" + displayLines.map((line: string) => chalk.dim(line)).join("\n");
text += "\n\n" + displayLines.map((line: string) => chalk.dim(replaceTabs(line))).join("\n");
if (remaining > 0) {
text += chalk.dim(`\n... (${remaining} more lines)`);
}

View file

@ -0,0 +1,28 @@
{
"name": "test-file",
"version": "1.0.0",
"description": "A test JSON file with tab indentation",
"author": "coding-agent",
"data": {
"items": [
{
"id": 1,
"name": "First item",
"active": true
},
{
"id": 2,
"name": "Second item",
"active": false
}
],
"metadata": {
"created": "2024-11-11",
"tags": [
"test",
"example",
"json"
]
}
}
}

View file

@ -103,8 +103,11 @@ export class Markdown implements Component {
return result;
}
// Replace tabs with 3 spaces for consistent rendering
const normalizedText = this.text.replace(/\t/g, " ");
// Parse markdown to HTML-like tokens
const tokens = marked.lexer(this.text);
const tokens = marked.lexer(normalizedText);
// Convert tokens to styled terminal output
const renderedLines: string[] = [];

View file

@ -63,8 +63,11 @@ export class Text implements Component {
return result;
}
// Replace tabs with 3 spaces for consistent rendering
const normalizedText = this.text.replace(/\t/g, " ");
const lines: string[] = [];
const textLines = this.text.split("\n");
const textLines = normalizedText.split("\n");
for (const line of textLines) {
// Measure visible length (strip ANSI codes)

View file

@ -6,7 +6,10 @@ import stringWidth from "string-width";
* - ANSI escape codes (ignored)
* - Emojis and wide characters (counted as 2 columns)
* - Combining characters (counted correctly)
* - Tabs (replaced with 3 spaces for consistent width)
*/
export function visibleWidth(str: string): number {
return stringWidth(str);
// Replace tabs with 3 spaces before measuring
const normalized = str.replace(/\t/g, " ");
return stringWidth(normalized);
}