summaryrefslogtreecommitdiffstats
path: root/src/prompt_toolkit/layout/utils.py
blob: 0f78f3713c497cbc5cb16333ba0fbbd3e04b1270 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
from __future__ import annotations

from typing import TYPE_CHECKING, Iterable, List, TypeVar, cast, overload

from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple

if TYPE_CHECKING:
    from typing_extensions import SupportsIndex

__all__ = [
    "explode_text_fragments",
]

_T = TypeVar("_T", bound=OneStyleAndTextTuple)


class _ExplodedList(List[_T]):
    """
    Wrapper around a list, that marks it as 'exploded'.

    As soon as items are added or the list is extended, the new items are
    automatically exploded as well.
    """

    exploded = True

    def append(self, item: _T) -> None:
        self.extend([item])

    def extend(self, lst: Iterable[_T]) -> None:
        super().extend(explode_text_fragments(lst))

    def insert(self, index: SupportsIndex, item: _T) -> None:
        raise NotImplementedError  # TODO

    # TODO: When creating a copy() or [:], return also an _ExplodedList.

    @overload
    def __setitem__(self, index: SupportsIndex, value: _T) -> None:
        ...

    @overload
    def __setitem__(self, index: slice, value: Iterable[_T]) -> None:
        ...

    def __setitem__(
        self, index: SupportsIndex | slice, value: _T | Iterable[_T]
    ) -> None:
        """
        Ensure that when `(style_str, 'long string')` is set, the string will be
        exploded.
        """
        if not isinstance(index, slice):
            int_index = index.__index__()
            index = slice(int_index, int_index + 1)
        if isinstance(value, tuple):  # In case of `OneStyleAndTextTuple`.
            value = cast("List[_T]", [value])

        super().__setitem__(index, explode_text_fragments(value))


def explode_text_fragments(fragments: Iterable[_T]) -> _ExplodedList[_T]:
    """
    Turn a list of (style_str, text) tuples into another list where each string is
    exactly one character.

    It should be fine to call this function several times. Calling this on a
    list that is already exploded, is a null operation.

    :param fragments: List of (style, text) tuples.
    """
    # When the fragments is already exploded, don't explode again.
    if isinstance(fragments, _ExplodedList):
        return fragments

    result: list[_T] = []

    for style, string, *rest in fragments:
        for c in string:
            result.append((style, c, *rest))  # type: ignore

    return _ExplodedList(result)