(
cursorChar: string,
mask: string,
invert: (text: string) => string,
ghostText?: { text: string; dim: (text: string) => string },
maxVisibleLines?: number,
)
| 201 | } |
| 202 | |
| 203 | render( |
| 204 | cursorChar: string, |
| 205 | mask: string, |
| 206 | invert: (text: string) => string, |
| 207 | ghostText?: { text: string; dim: (text: string) => string }, |
| 208 | maxVisibleLines?: number, |
| 209 | ) { |
| 210 | const { line, column } = this.getPosition() |
| 211 | const allLines = this.measuredText.getWrappedText() |
| 212 | |
| 213 | const startLine = this.getViewportStartLine(maxVisibleLines) |
| 214 | const endLine = |
| 215 | maxVisibleLines !== undefined && maxVisibleLines > 0 |
| 216 | ? Math.min(allLines.length, startLine + maxVisibleLines) |
| 217 | : allLines.length |
| 218 | |
| 219 | return allLines |
| 220 | .slice(startLine, endLine) |
| 221 | .map((text, i) => { |
| 222 | const currentLine = i + startLine |
| 223 | let displayText = text |
| 224 | if (mask) { |
| 225 | const graphemes = Array.from(getGraphemeSegmenter().segment(text)) |
| 226 | if (currentLine === allLines.length - 1) { |
| 227 | // Last line: mask all but the trailing 6 chars so the user can |
| 228 | // confirm they pasted the right thing without exposing the full token |
| 229 | const visibleCount = Math.min(6, graphemes.length) |
| 230 | const maskCount = graphemes.length - visibleCount |
| 231 | const splitOffset = |
| 232 | graphemes.length > visibleCount ? graphemes[maskCount]!.index : 0 |
| 233 | displayText = mask.repeat(maskCount) + text.slice(splitOffset) |
| 234 | } else { |
| 235 | // Earlier wrapped lines: fully mask. Previously only the last line |
| 236 | // was masked, leaking the start of the token on narrow terminals |
| 237 | // where the pasted OAuth code wraps across multiple lines. |
| 238 | displayText = mask.repeat(graphemes.length) |
| 239 | } |
| 240 | } |
| 241 | // looking for the line with the cursor |
| 242 | if (line !== currentLine) return displayText.trimEnd() |
| 243 | |
| 244 | // Split the line into before/at/after cursor in a single pass over the |
| 245 | // graphemes, accumulating display width until we reach the cursor column. |
| 246 | // This replaces a two-pass approach (displayWidthToStringIndex + a second |
| 247 | // segmenter pass) — the intermediate stringIndex from that approach is |
| 248 | // always a grapheme boundary, so the "cursor in the middle of a |
| 249 | // multi-codepoint character" branch was unreachable. |
| 250 | let beforeCursor = '' |
| 251 | let atCursor = cursorChar |
| 252 | let afterCursor = '' |
| 253 | let currentWidth = 0 |
| 254 | let cursorFound = false |
| 255 | |
| 256 | for (const { segment } of getGraphemeSegmenter().segment(displayText)) { |
| 257 | if (cursorFound) { |
| 258 | afterCursor += segment |
| 259 | continue |
| 260 | } |
no test coverage detected