(targetVisiblePos: number)
| 94 | } |
| 95 | |
| 96 | private segmentTo(targetVisiblePos: number): TextSegment | null { |
| 97 | if ( |
| 98 | this.tokenIdx >= this.tokens.length || |
| 99 | targetVisiblePos <= this.visiblePos |
| 100 | ) { |
| 101 | return null |
| 102 | } |
| 103 | |
| 104 | const visibleStart = this.visiblePos |
| 105 | |
| 106 | // Consume leading ANSI codes before first visible char |
| 107 | while (this.tokenIdx < this.tokens.length) { |
| 108 | const token = this.tokens[this.tokenIdx]! |
| 109 | if (token.type !== 'ansi') break |
| 110 | this.codes.push(token) |
| 111 | this.stringPos += token.code.length |
| 112 | this.tokenIdx++ |
| 113 | } |
| 114 | |
| 115 | const stringStart = this.stringPos |
| 116 | const codesStart = [...this.codes] |
| 117 | |
| 118 | // Advance through tokens until we reach target |
| 119 | while ( |
| 120 | this.visiblePos < targetVisiblePos && |
| 121 | this.tokenIdx < this.tokens.length |
| 122 | ) { |
| 123 | const token = this.tokens[this.tokenIdx]! |
| 124 | |
| 125 | if (token.type === 'ansi') { |
| 126 | this.codes.push(token) |
| 127 | this.stringPos += token.code.length |
| 128 | this.tokenIdx++ |
| 129 | } else { |
| 130 | const charsNeeded = targetVisiblePos - this.visiblePos |
| 131 | const charsAvailable = token.value.length - this.charIdx |
| 132 | const charsToTake = Math.min(charsNeeded, charsAvailable) |
| 133 | |
| 134 | this.stringPos += charsToTake |
| 135 | this.visiblePos += charsToTake |
| 136 | this.charIdx += charsToTake |
| 137 | |
| 138 | if (this.charIdx >= token.value.length) { |
| 139 | this.tokenIdx++ |
| 140 | this.charIdx = 0 |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | // Empty segment (can occur when only trailing ANSI codes remain) |
| 146 | if (this.stringPos === stringStart) { |
| 147 | return null |
| 148 | } |
| 149 | |
| 150 | const prefixCodes = reduceCodes(codesStart) |
| 151 | const suffixCodes = reduceCodes(this.codes) |
| 152 | this.codes = suffixCodes |
| 153 |
no test coverage detected