utils.py 2.3 KB

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