From 42facac2d98fd4354632f7f4f3e7758174679b47 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 27 May 2026 18:16:51 +0000 Subject: [PATCH 1/2] Use StringBuilder in BufferLine.translateToString MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace O(n²) string concatenation in the translate loop with StringBuilder.append, matching the pattern used elsewhere in the project. Fixes #5939 Co-authored-by: Daniel Imms --- src/common/buffer/BufferLine.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index be200f112..69cde7e8f 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -8,6 +8,7 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { CellData } from 'common/buffer/CellData'; import { Attributes, BgFlags, CHAR_DATA_ATTR_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, Content, NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, WHITESPACE_CELL_CHAR } from 'common/buffer/Constants'; import { stringFromCodePoint } from 'common/input/TextDecoder'; +import { StringBuilder } from 'common/StringBuilder'; // Buffer memory layout: // @@ -570,12 +571,12 @@ export class BufferLine implements IBufferLine { if (outColumns) { outColumns.length = 0; } - let result = ''; + const resultBuilder = new StringBuilder(); while (startCol < endCol) { const content = this._data[startCol * Constants.CELL_INDICIES + Cell.CONTENT]; const cp = content & Content.CODEPOINT_MASK; const chars = (content & Content.IS_COMBINED_MASK) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR; - result += chars; + resultBuilder.append(chars); if (outColumns) { for (let i = 0; i < chars.length; ++i) { outColumns.push(startCol); @@ -586,6 +587,7 @@ export class BufferLine implements IBufferLine { if (outColumns) { outColumns.push(startCol); } + const result = resultBuilder.toString(); if (isCanonicalRequest) { const cacheEntry = this._getStringCacheEntry(true)!; cacheEntry.value = result; From e59a6c1f7b549bb4ab775a359d5dba4f748b0e57 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 27 May 2026 19:31:00 +0000 Subject: [PATCH 2/2] Reuse shared StringBuilder in BufferLine.translateToString Avoid per-call allocation when translating many lines in sequence by resetting a module-level work StringBuilder before and after each use. Co-authored-by: Daniel Imms --- src/common/buffer/BufferLine.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index 69cde7e8f..e24a000c9 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -42,6 +42,7 @@ export const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData()); // Work variables to avoid garbage collection let $startIndex = 0; const $workCell = new CellData(); +const $translateToStringBuilder = new StringBuilder(); export interface IBufferLineStringCacheEntry { value: string | undefined; @@ -571,12 +572,12 @@ export class BufferLine implements IBufferLine { if (outColumns) { outColumns.length = 0; } - const resultBuilder = new StringBuilder(); + $translateToStringBuilder.reset(); while (startCol < endCol) { const content = this._data[startCol * Constants.CELL_INDICIES + Cell.CONTENT]; const cp = content & Content.CODEPOINT_MASK; const chars = (content & Content.IS_COMBINED_MASK) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR; - resultBuilder.append(chars); + $translateToStringBuilder.append(chars); if (outColumns) { for (let i = 0; i < chars.length; ++i) { outColumns.push(startCol); @@ -587,7 +588,8 @@ export class BufferLine implements IBufferLine { if (outColumns) { outColumns.push(startCol); } - const result = resultBuilder.toString(); + const result = $translateToStringBuilder.toString(); + $translateToStringBuilder.reset(); if (isCanonicalRequest) { const cacheEntry = this._getStringCacheEntry(true)!; cacheEntry.value = result;