| ... | @@ -166,6 +166,27 @@ function sameColor(left: GhosttyColor, right: GhosttyColor): boolean { | ... | @@ -166,6 +166,27 @@ function sameColor(left: GhosttyColor, right: GhosttyColor): boolean { |
| 166 | return left.r === right.r && left.g === right.g && left.b === right.b; | 166 | return left.r === right.r && left.g === right.g && left.b === right.b; |
| 167 | } | 167 | } |
| 168 | | 168 | |
| | 169 | /** |
| | 170 | * A terminal program can print one base character followed by a huge run of |
| | 171 | * combining marks, packing hundreds of thousands of codepoints into a single |
| | 172 | * cell that still fits the scrollback buffer. Engines cap spread-call |
| | 173 | * arguments far below that, so convert in bounded chunks instead of spreading |
| | 174 | * every codepoint into String.fromCodePoint at once. |
| | 175 | */ |
| | 176 | export function ghosttyCellText(codepointView: DataView, graphemeLength: number): string { |
| | 177 | const CHUNK_SIZE = 4_096; |
| | 178 | let text = ""; |
| | 179 | for (let start = 0; start < graphemeLength; start += CHUNK_SIZE) { |
| | 180 | const count = Math.min(CHUNK_SIZE, graphemeLength - start); |
| | 181 | const codes = new Array<number>(count); |
| | 182 | for (let index = 0; index < count; index += 1) { |
| | 183 | codes[index] = codepointView.getUint32((start + index) * 4, true); |
| | 184 | } |
| | 185 | text += String.fromCodePoint(...codes); |
| | 186 | } |
| | 187 | return text; |
| | 188 | } |
| | 189 | |
| 169 | export class GhosttyTerminalCore { | 190 | export class GhosttyTerminalCore { |
| 170 | private readonly runtime: GhosttyRuntime; | 191 | private readonly runtime: GhosttyRuntime; |
| 171 | private terminalSlot = 0; | 192 | private terminalSlot = 0; |
| ... | @@ -960,11 +981,7 @@ export class GhosttyTerminalCore { | ... | @@ -960,11 +981,7 @@ export class GhosttyTerminalCore { |
| 960 | // Read through a DataView: the byte-array allocator guarantees no | 981 | // Read through a DataView: the byte-array allocator guarantees no |
| 961 | // 4-byte alignment, which a Uint32Array view would require. | 982 | // 4-byte alignment, which a Uint32Array view would require. |
| 962 | const codepointView = this.runtime.view(codepoints, bufferSize); | 983 | const codepointView = this.runtime.view(codepoints, bufferSize); |
| 963 | const codes: number[] = []; | 984 | text = ghosttyCellText(codepointView, graphemeLength); |
| 964 | for (let index = 0; index < graphemeLength; index += 1) { | | |
| 965 | codes.push(codepointView.getUint32(index * 4, true)); | | |
| 966 | } | | |
| 967 | text = String.fromCodePoint(...codes); | | |
| 968 | } | 985 | } |
| 969 | this.runtime.free(codepoints, bufferSize); | 986 | this.runtime.free(codepoints, bufferSize); |
| 970 | } | 987 | } |