perf(ui): optimize text buffer and highlighting for large inputs (#16782)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
N. Taylor Mullen
2026-01-16 09:33:13 -08:00
committed by GitHub
parent fcd860e1b0
commit be37c26c88
10 changed files with 469 additions and 76 deletions
+21 -12
View File
@@ -8,6 +8,8 @@ import stripAnsi from 'strip-ansi';
import ansiRegex from 'ansi-regex';
import { stripVTControlCharacters } from 'node:util';
import stringWidth from 'string-width';
import { LruCache } from '@google/gemini-cli-core';
import { LRU_BUFFER_PERF_CACHE_LIMIT } from '../constants.js';
/**
* Calculates the maximum width of a multi-line ASCII art string.
@@ -28,9 +30,11 @@ export const getAsciiArtWidth = (asciiArt: string): number => {
* code units so that surrogatepair emoji count as one "column".)
* ---------------------------------------------------------------------- */
// Cache for code points to reduce GC pressure
const codePointsCache = new Map<string, string[]>();
// Cache for code points
const MAX_STRING_LENGTH_TO_CACHE = 1000;
const codePointsCache = new LruCache<string, string[]>(
LRU_BUFFER_PERF_CACHE_LIMIT,
);
export function toCodePoints(str: string): string[] {
// ASCII fast path - check if all chars are ASCII (0-127)
@@ -48,14 +52,14 @@ export function toCodePoints(str: string): string[] {
// Cache short strings
if (str.length <= MAX_STRING_LENGTH_TO_CACHE) {
const cached = codePointsCache.get(str);
if (cached) {
if (cached !== undefined) {
return cached;
}
}
const result = Array.from(str);
// Cache result (unlimited like Ink)
// Cache result
if (str.length <= MAX_STRING_LENGTH_TO_CACHE) {
codePointsCache.set(str, result);
}
@@ -119,21 +123,26 @@ export function stripUnsafeCharacters(str: string): string {
.join('');
}
// String width caching for performance optimization
const stringWidthCache = new Map<string, number>();
const stringWidthCache = new LruCache<string, number>(
LRU_BUFFER_PERF_CACHE_LIMIT,
);
/**
* Cached version of stringWidth function for better performance
* Follows Ink's approach with unlimited cache (no eviction)
*/
export const getCachedStringWidth = (str: string): number => {
// ASCII printable chars have width 1
if (/^[\x20-\x7E]*$/.test(str)) {
return str.length;
// ASCII printable chars (32-126) have width 1.
// This is a very frequent path, so we use a fast numeric check.
if (str.length === 1) {
const code = str.charCodeAt(0);
if (code >= 0x20 && code <= 0x7e) {
return 1;
}
}
if (stringWidthCache.has(str)) {
return stringWidthCache.get(str)!;
const cached = stringWidthCache.get(str);
if (cached !== undefined) {
return cached;
}
let width: number;