* Apply styles to wrapped text by mapping each character back to its original segment. * This preserves per-segment styles even when text wraps across lines. * * @param trimEnabled - Whether whitespace trimming is enabled (wrap-trim mode). * When true, we skip whitespace in the original that w
( wrappedPlain: string, segments: StyledSegment[], charToSegment: number[], originalPlain: string, trimEnabled: boolean = false, )
| 209 | * When false (wrap mode), all whitespace is preserved so no skipping is needed. |
| 210 | */ |
| 211 | function applyStylesToWrappedText( |
| 212 | wrappedPlain: string, |
| 213 | segments: StyledSegment[], |
| 214 | charToSegment: number[], |
| 215 | originalPlain: string, |
| 216 | trimEnabled: boolean = false, |
| 217 | ): string { |
| 218 | const lines = wrappedPlain.split('\n') |
| 219 | const resultLines: string[] = [] |
| 220 | |
| 221 | let charIndex = 0 |
| 222 | for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) { |
| 223 | const line = lines[lineIdx]! |
| 224 | |
| 225 | // In trim mode, skip leading whitespace that was trimmed from this line. |
| 226 | // Only skip if the original has whitespace but the output line doesn't start |
| 227 | // with whitespace (meaning it was trimmed). If both have whitespace, the |
| 228 | // whitespace was preserved and we shouldn't skip. |
| 229 | if (trimEnabled && line.length > 0) { |
| 230 | const lineStartsWithWhitespace = /\s/.test(line[0]!) |
| 231 | const originalHasWhitespace = |
| 232 | charIndex < originalPlain.length && /\s/.test(originalPlain[charIndex]!) |
| 233 | |
| 234 | // Only skip if original has whitespace but line doesn't |
| 235 | if (originalHasWhitespace && !lineStartsWithWhitespace) { |
| 236 | while ( |
| 237 | charIndex < originalPlain.length && |
| 238 | /\s/.test(originalPlain[charIndex]!) |
| 239 | ) { |
| 240 | charIndex++ |
| 241 | } |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | let styledLine = '' |
| 246 | let runStart = 0 |
| 247 | let runSegmentIndex = charToSegment[charIndex] ?? 0 |
| 248 | |
| 249 | for (let i = 0; i < line.length; i++) { |
| 250 | const currentSegmentIndex = charToSegment[charIndex] ?? runSegmentIndex |
| 251 | |
| 252 | if (currentSegmentIndex !== runSegmentIndex) { |
| 253 | // Flush the current run |
| 254 | const runText = line.slice(runStart, i) |
| 255 | const segment = segments[runSegmentIndex] |
| 256 | if (segment) { |
| 257 | let styled = applyTextStyles(runText, segment.styles) |
| 258 | if (segment.hyperlink) { |
| 259 | styled = wrapWithOsc8Link(styled, segment.hyperlink) |
| 260 | } |
| 261 | styledLine += styled |
| 262 | } else { |
| 263 | styledLine += runText |
| 264 | } |
| 265 | runStart = i |
| 266 | runSegmentIndex = currentSegmentIndex |
| 267 | } |
| 268 |
no test coverage detected