( line: string, startCol: number, length: number, strict = false, )
| 1086 | |
| 1087 | /** Like sliceByColumn but also returns the actual visible width of the result. */ |
| 1088 | export function sliceWithWidth( |
| 1089 | line: string, |
| 1090 | startCol: number, |
| 1091 | length: number, |
| 1092 | strict = false, |
| 1093 | ): { text: string; width: number } { |
| 1094 | if (length <= 0) return { text: "", width: 0 }; |
| 1095 | const endCol = startCol + length; |
| 1096 | let result = "", |
| 1097 | resultWidth = 0, |
| 1098 | currentCol = 0, |
| 1099 | i = 0, |
| 1100 | pendingAnsi = ""; |
| 1101 | |
| 1102 | while (i < line.length) { |
| 1103 | const ansi = extractAnsiCode(line, i); |
| 1104 | if (ansi) { |
| 1105 | if (currentCol >= startCol && currentCol < endCol) result += ansi.code; |
| 1106 | else if (currentCol < startCol) pendingAnsi += ansi.code; |
| 1107 | i += ansi.length; |
| 1108 | continue; |
| 1109 | } |
| 1110 | |
| 1111 | let textEnd = i; |
| 1112 | while (textEnd < line.length && !extractAnsiCode(line, textEnd)) textEnd++; |
| 1113 | |
| 1114 | for (const { segment } of graphemeSegmenter.segment(line.slice(i, textEnd))) { |
| 1115 | const w = graphemeWidth(segment); |
| 1116 | const inRange = currentCol >= startCol && currentCol < endCol; |
| 1117 | const fits = !strict || currentCol + w <= endCol; |
| 1118 | if (inRange && fits) { |
| 1119 | if (pendingAnsi) { |
| 1120 | result += pendingAnsi; |
| 1121 | pendingAnsi = ""; |
| 1122 | } |
| 1123 | result += segment; |
| 1124 | resultWidth += w; |
| 1125 | } |
| 1126 | currentCol += w; |
| 1127 | if (currentCol >= endCol) break; |
| 1128 | } |
| 1129 | i = textEnd; |
| 1130 | if (currentCol >= endCol) break; |
| 1131 | } |
| 1132 | return { text: result, width: resultWidth }; |
| 1133 | } |
| 1134 | |
| 1135 | // Pooled tracker instance for extractSegments (avoids allocation per call) |
| 1136 | const pooledStyleTracker = new AnsiCodeTracker(); |
no test coverage detected