utils.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. """
  2. Utilities for manipulating formatted text.
  3. When ``to_formatted_text`` has been called, we get a list of ``(style, text)``
  4. tuples. This file contains functions for manipulating such a list.
  5. """
  6. from __future__ import annotations
  7. from typing import Iterable, cast
  8. from prompt_toolkit.utils import get_cwidth
  9. from .base import (
  10. AnyFormattedText,
  11. OneStyleAndTextTuple,
  12. StyleAndTextTuples,
  13. to_formatted_text,
  14. )
  15. __all__ = [
  16. "to_plain_text",
  17. "fragment_list_len",
  18. "fragment_list_width",
  19. "fragment_list_to_text",
  20. "split_lines",
  21. ]
  22. def to_plain_text(value: AnyFormattedText) -> str:
  23. """
  24. Turn any kind of formatted text back into plain text.
  25. """
  26. return fragment_list_to_text(to_formatted_text(value))
  27. def fragment_list_len(fragments: StyleAndTextTuples) -> int:
  28. """
  29. Return the amount of characters in this text fragment list.
  30. :param fragments: List of ``(style_str, text)`` or
  31. ``(style_str, text, mouse_handler)`` tuples.
  32. """
  33. ZeroWidthEscape = "[ZeroWidthEscape]"
  34. return sum(len(item[1]) for item in fragments if ZeroWidthEscape not in item[0])
  35. def fragment_list_width(fragments: StyleAndTextTuples) -> int:
  36. """
  37. Return the character width of this text fragment list.
  38. (Take double width characters into account.)
  39. :param fragments: List of ``(style_str, text)`` or
  40. ``(style_str, text, mouse_handler)`` tuples.
  41. """
  42. ZeroWidthEscape = "[ZeroWidthEscape]"
  43. return sum(
  44. get_cwidth(c)
  45. for item in fragments
  46. for c in item[1]
  47. if ZeroWidthEscape not in item[0]
  48. )
  49. def fragment_list_to_text(fragments: StyleAndTextTuples) -> str:
  50. """
  51. Concatenate all the text parts again.
  52. :param fragments: List of ``(style_str, text)`` or
  53. ``(style_str, text, mouse_handler)`` tuples.
  54. """
  55. ZeroWidthEscape = "[ZeroWidthEscape]"
  56. return "".join(item[1] for item in fragments if ZeroWidthEscape not in item[0])
  57. def split_lines(
  58. fragments: Iterable[OneStyleAndTextTuple],
  59. ) -> Iterable[StyleAndTextTuples]:
  60. """
  61. Take a single list of (style_str, text) tuples and yield one such list for each
  62. line. Just like str.split, this will yield at least one item.
  63. :param fragments: Iterable of ``(style_str, text)`` or
  64. ``(style_str, text, mouse_handler)`` tuples.
  65. """
  66. line: StyleAndTextTuples = []
  67. for style, string, *mouse_handler in fragments:
  68. parts = string.split("\n")
  69. for part in parts[:-1]:
  70. if part:
  71. line.append(cast(OneStyleAndTextTuple, (style, part, *mouse_handler)))
  72. yield line
  73. line = []
  74. line.append(cast(OneStyleAndTextTuple, (style, parts[-1], *mouse_handler)))
  75. # Always yield the last line, even when this is an empty line. This ensures
  76. # that when `fragments` ends with a newline character, an additional empty
  77. # line is yielded. (Otherwise, there's no way to differentiate between the
  78. # cases where `fragments` does and doesn't end with a newline.)
  79. yield line