1from __future__ import annotations
2
3from collections.abc import Iterable
4from typing import TYPE_CHECKING, TypeVar, cast, overload
5
6from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple
7
8if TYPE_CHECKING:
9 from typing_extensions import SupportsIndex
10
11__all__ = [
12 "explode_text_fragments",
13]
14
15_T = TypeVar("_T", bound=OneStyleAndTextTuple)
16
17
18class _ExplodedList(list[_T]):
19 """
20 Wrapper around a list, that marks it as 'exploded'.
21
22 As soon as items are added or the list is extended, the new items are
23 automatically exploded as well.
24 """
25
26 exploded = True
27
28 def append(self, item: _T) -> None:
29 self.extend([item])
30
31 def extend(self, lst: Iterable[_T]) -> None:
32 super().extend(explode_text_fragments(lst))
33
34 def insert(self, index: SupportsIndex, item: _T) -> None:
35 raise NotImplementedError # TODO
36
37 # TODO: When creating a copy() or [:], return also an _ExplodedList.
38
39 @overload
40 def __setitem__(self, index: SupportsIndex, value: _T) -> None: ...
41
42 @overload
43 def __setitem__(self, index: slice, value: Iterable[_T]) -> None: ...
44
45 def __setitem__(
46 self, index: SupportsIndex | slice, value: _T | Iterable[_T]
47 ) -> None:
48 """
49 Ensure that when `(style_str, 'long string')` is set, the string will be
50 exploded.
51 """
52 if not isinstance(index, slice):
53 int_index = index.__index__()
54 index = slice(int_index, int_index + 1)
55 if isinstance(value, tuple): # In case of `OneStyleAndTextTuple`.
56 value = cast("list[_T]", [value])
57
58 super().__setitem__(index, explode_text_fragments(value))
59
60
61def explode_text_fragments(fragments: Iterable[_T]) -> _ExplodedList[_T]:
62 """
63 Turn a list of (style_str, text) tuples into another list where each string is
64 exactly one character.
65
66 It should be fine to call this function several times. Calling this on a
67 list that is already exploded, is a null operation.
68
69 :param fragments: List of (style, text) tuples.
70 """
71 # When the fragments is already exploded, don't explode again.
72 if isinstance(fragments, _ExplodedList):
73 return fragments
74
75 result: list[_T] = []
76
77 for style, string, *rest in fragments:
78 for c in string:
79 result.append((style, c, *rest)) # type: ignore
80
81 return _ExplodedList(result)