r"""Decompose the input buffer into a printable variant with applied colors. Returns a tuple of two lists: - the first list is the input buffer, character by character, with color escape codes added (while those codes contain multiple ASCII characters, each code is considered at
(
buffer: str,
colors: list[ColorSpan] | None = None,
start_index: int = 0,
force_color: bool = False,
)
| 287 | |
| 288 | |
| 289 | def disp_str( |
| 290 | buffer: str, |
| 291 | colors: list[ColorSpan] | None = None, |
| 292 | start_index: int = 0, |
| 293 | force_color: bool = False, |
| 294 | ) -> tuple[CharBuffer, CharWidths]: |
| 295 | r"""Decompose the input buffer into a printable variant with applied colors. |
| 296 | |
| 297 | Returns a tuple of two lists: |
| 298 | - the first list is the input buffer, character by character, with color |
| 299 | escape codes added (while those codes contain multiple ASCII characters, |
| 300 | each code is considered atomic *and is attached for the corresponding |
| 301 | visible character*); |
| 302 | - the second list is the visible width of each character in the input |
| 303 | buffer. |
| 304 | |
| 305 | Note on colors: |
| 306 | - The `colors` list, if provided, is partially consumed within. We're using |
| 307 | a list and not a generator since we need to hold onto the current |
| 308 | unfinished span between calls to disp_str in case of multiline strings. |
| 309 | - The `colors` list is computed from the start of the input block. `buffer` |
| 310 | is only a subset of that input block, a single line within. This is why |
| 311 | we need `start_index` to inform us which position is the start of `buffer` |
| 312 | actually within user input. This allows us to match color spans correctly. |
| 313 | |
| 314 | Examples: |
| 315 | >>> utils.disp_str("a = 9") |
| 316 | (['a', ' ', '=', ' ', '9'], [1, 1, 1, 1, 1]) |
| 317 | |
| 318 | >>> line = "while 1:" |
| 319 | >>> colors = list(utils.gen_colors(line)) |
| 320 | >>> utils.disp_str(line, colors=colors) |
| 321 | (['\x1b[1;34mw', 'h', 'i', 'l', 'e\x1b[0m', ' ', '1', ':'], [1, 1, 1, 1, 1, 1, 1, 1]) |
| 322 | |
| 323 | """ |
| 324 | chars: CharBuffer = [] |
| 325 | char_widths: CharWidths = [] |
| 326 | |
| 327 | if not buffer: |
| 328 | return chars, char_widths |
| 329 | |
| 330 | while colors and colors[0].span.end < start_index: |
| 331 | # move past irrelevant spans |
| 332 | colors.pop(0) |
| 333 | |
| 334 | theme = THEME(force_color=force_color) |
| 335 | pre_color = "" |
| 336 | post_color = "" |
| 337 | if colors and colors[0].span.start < start_index: |
| 338 | # looks like we're continuing a previous color (e.g. a multiline str) |
| 339 | pre_color = theme[colors[0].tag] |
| 340 | |
| 341 | for i, c in enumerate(buffer, start_index): |
| 342 | if colors and colors[0].span.start == i: # new color starts now |
| 343 | pre_color = theme[colors[0].tag] |
| 344 | |
| 345 | if c == "\x1a": # CTRL-Z on Windows |
| 346 | chars.append(c) |
no test coverage detected
searching dependent graphs…