segment.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. from enum import IntEnum
  2. from functools import lru_cache
  3. from itertools import filterfalse
  4. from operator import attrgetter
  5. from typing import (
  6. TYPE_CHECKING,
  7. Dict,
  8. Iterable,
  9. List,
  10. NamedTuple,
  11. Optional,
  12. Sequence,
  13. Tuple,
  14. Type,
  15. Union,
  16. )
  17. from .cells import (
  18. _is_single_cell_widths,
  19. cached_cell_len,
  20. cell_len,
  21. get_character_cell_size,
  22. set_cell_size,
  23. )
  24. from .repr import Result, rich_repr
  25. from .style import Style
  26. if TYPE_CHECKING:
  27. from .console import Console, ConsoleOptions, RenderResult
  28. class ControlType(IntEnum):
  29. """Non-printable control codes which typically translate to ANSI codes."""
  30. BELL = 1
  31. CARRIAGE_RETURN = 2
  32. HOME = 3
  33. CLEAR = 4
  34. SHOW_CURSOR = 5
  35. HIDE_CURSOR = 6
  36. ENABLE_ALT_SCREEN = 7
  37. DISABLE_ALT_SCREEN = 8
  38. CURSOR_UP = 9
  39. CURSOR_DOWN = 10
  40. CURSOR_FORWARD = 11
  41. CURSOR_BACKWARD = 12
  42. CURSOR_MOVE_TO_COLUMN = 13
  43. CURSOR_MOVE_TO = 14
  44. ERASE_IN_LINE = 15
  45. SET_WINDOW_TITLE = 16
  46. ControlCode = Union[
  47. Tuple[ControlType],
  48. Tuple[ControlType, Union[int, str]],
  49. Tuple[ControlType, int, int],
  50. ]
  51. @rich_repr()
  52. class Segment(NamedTuple):
  53. """A piece of text with associated style. Segments are produced by the Console render process and
  54. are ultimately converted in to strings to be written to the terminal.
  55. Args:
  56. text (str): A piece of text.
  57. style (:class:`~rich.style.Style`, optional): An optional style to apply to the text.
  58. control (Tuple[ControlCode], optional): Optional sequence of control codes.
  59. Attributes:
  60. cell_length (int): The cell length of this Segment.
  61. """
  62. text: str
  63. style: Optional[Style] = None
  64. control: Optional[Sequence[ControlCode]] = None
  65. @property
  66. def cell_length(self) -> int:
  67. """The number of terminal cells required to display self.text.
  68. Returns:
  69. int: A number of cells.
  70. """
  71. text, _style, control = self
  72. return 0 if control else cell_len(text)
  73. def __rich_repr__(self) -> Result:
  74. yield self.text
  75. if self.control is None:
  76. if self.style is not None:
  77. yield self.style
  78. else:
  79. yield self.style
  80. yield self.control
  81. def __bool__(self) -> bool:
  82. """Check if the segment contains text."""
  83. return bool(self.text)
  84. @property
  85. def is_control(self) -> bool:
  86. """Check if the segment contains control codes."""
  87. return self.control is not None
  88. @classmethod
  89. @lru_cache(1024 * 16)
  90. def _split_cells(cls, segment: "Segment", cut: int) -> Tuple["Segment", "Segment"]:
  91. """Split a segment in to two at a given cell position.
  92. Note that splitting a double-width character, may result in that character turning
  93. into two spaces.
  94. Args:
  95. segment (Segment): A segment to split.
  96. cut (int): A cell position to cut on.
  97. Returns:
  98. A tuple of two segments.
  99. """
  100. text, style, control = segment
  101. _Segment = Segment
  102. cell_length = segment.cell_length
  103. if cut >= cell_length:
  104. return segment, _Segment("", style, control)
  105. cell_size = get_character_cell_size
  106. pos = int((cut / cell_length) * len(text))
  107. while True:
  108. before = text[:pos]
  109. cell_pos = cell_len(before)
  110. out_by = cell_pos - cut
  111. if not out_by:
  112. return (
  113. _Segment(before, style, control),
  114. _Segment(text[pos:], style, control),
  115. )
  116. if out_by == -1 and cell_size(text[pos]) == 2:
  117. return (
  118. _Segment(text[:pos] + " ", style, control),
  119. _Segment(" " + text[pos + 1 :], style, control),
  120. )
  121. if out_by == +1 and cell_size(text[pos - 1]) == 2:
  122. return (
  123. _Segment(text[: pos - 1] + " ", style, control),
  124. _Segment(" " + text[pos:], style, control),
  125. )
  126. if cell_pos < cut:
  127. pos += 1
  128. else:
  129. pos -= 1
  130. def split_cells(self, cut: int) -> Tuple["Segment", "Segment"]:
  131. """Split segment in to two segments at the specified column.
  132. If the cut point falls in the middle of a 2-cell wide character then it is replaced
  133. by two spaces, to preserve the display width of the parent segment.
  134. Args:
  135. cut (int): Offset within the segment to cut.
  136. Returns:
  137. Tuple[Segment, Segment]: Two segments.
  138. """
  139. text, style, control = self
  140. assert cut >= 0
  141. if _is_single_cell_widths(text):
  142. # Fast path with all 1 cell characters
  143. if cut >= len(text):
  144. return self, Segment("", style, control)
  145. return (
  146. Segment(text[:cut], style, control),
  147. Segment(text[cut:], style, control),
  148. )
  149. return self._split_cells(self, cut)
  150. @classmethod
  151. def line(cls) -> "Segment":
  152. """Make a new line segment."""
  153. return cls("\n")
  154. @classmethod
  155. def apply_style(
  156. cls,
  157. segments: Iterable["Segment"],
  158. style: Optional[Style] = None,
  159. post_style: Optional[Style] = None,
  160. ) -> Iterable["Segment"]:
  161. """Apply style(s) to an iterable of segments.
  162. Returns an iterable of segments where the style is replaced by ``style + segment.style + post_style``.
  163. Args:
  164. segments (Iterable[Segment]): Segments to process.
  165. style (Style, optional): Base style. Defaults to None.
  166. post_style (Style, optional): Style to apply on top of segment style. Defaults to None.
  167. Returns:
  168. Iterable[Segments]: A new iterable of segments (possibly the same iterable).
  169. """
  170. result_segments = segments
  171. if style:
  172. apply = style.__add__
  173. result_segments = (
  174. cls(text, None if control else apply(_style), control)
  175. for text, _style, control in result_segments
  176. )
  177. if post_style:
  178. result_segments = (
  179. cls(
  180. text,
  181. (
  182. None
  183. if control
  184. else (_style + post_style if _style else post_style)
  185. ),
  186. control,
  187. )
  188. for text, _style, control in result_segments
  189. )
  190. return result_segments
  191. @classmethod
  192. def filter_control(
  193. cls, segments: Iterable["Segment"], is_control: bool = False
  194. ) -> Iterable["Segment"]:
  195. """Filter segments by ``is_control`` attribute.
  196. Args:
  197. segments (Iterable[Segment]): An iterable of Segment instances.
  198. is_control (bool, optional): is_control flag to match in search.
  199. Returns:
  200. Iterable[Segment]: And iterable of Segment instances.
  201. """
  202. if is_control:
  203. return filter(attrgetter("control"), segments)
  204. else:
  205. return filterfalse(attrgetter("control"), segments)
  206. @classmethod
  207. def split_lines(cls, segments: Iterable["Segment"]) -> Iterable[List["Segment"]]:
  208. """Split a sequence of segments in to a list of lines.
  209. Args:
  210. segments (Iterable[Segment]): Segments potentially containing line feeds.
  211. Yields:
  212. Iterable[List[Segment]]: Iterable of segment lists, one per line.
  213. """
  214. line: List[Segment] = []
  215. append = line.append
  216. for segment in segments:
  217. if "\n" in segment.text and not segment.control:
  218. text, style, _ = segment
  219. while text:
  220. _text, new_line, text = text.partition("\n")
  221. if _text:
  222. append(cls(_text, style))
  223. if new_line:
  224. yield line
  225. line = []
  226. append = line.append
  227. else:
  228. append(segment)
  229. if line:
  230. yield line
  231. @classmethod
  232. def split_lines_terminator(
  233. cls, segments: Iterable["Segment"]
  234. ) -> Iterable[Tuple[List["Segment"], bool]]:
  235. """Split a sequence of segments in to a list of lines and a boolean to indicate if there was a new line.
  236. Args:
  237. segments (Iterable[Segment]): Segments potentially containing line feeds.
  238. Yields:
  239. Iterable[List[Segment]]: Iterable of segment lists, one per line.
  240. """
  241. line: List[Segment] = []
  242. append = line.append
  243. for segment in segments:
  244. if "\n" in segment.text and not segment.control:
  245. text, style, _ = segment
  246. while text:
  247. _text, new_line, text = text.partition("\n")
  248. if _text:
  249. append(cls(_text, style))
  250. if new_line:
  251. yield (line, True)
  252. line = []
  253. append = line.append
  254. else:
  255. append(segment)
  256. if line:
  257. yield (line, False)
  258. @classmethod
  259. def split_and_crop_lines(
  260. cls,
  261. segments: Iterable["Segment"],
  262. length: int,
  263. style: Optional[Style] = None,
  264. pad: bool = True,
  265. include_new_lines: bool = True,
  266. ) -> Iterable[List["Segment"]]:
  267. """Split segments in to lines, and crop lines greater than a given length.
  268. Args:
  269. segments (Iterable[Segment]): An iterable of segments, probably
  270. generated from console.render.
  271. length (int): Desired line length.
  272. style (Style, optional): Style to use for any padding.
  273. pad (bool): Enable padding of lines that are less than `length`.
  274. Returns:
  275. Iterable[List[Segment]]: An iterable of lines of segments.
  276. """
  277. line: List[Segment] = []
  278. append = line.append
  279. adjust_line_length = cls.adjust_line_length
  280. new_line_segment = cls("\n")
  281. for segment in segments:
  282. if "\n" in segment.text and not segment.control:
  283. text, segment_style, _ = segment
  284. while text:
  285. _text, new_line, text = text.partition("\n")
  286. if _text:
  287. append(cls(_text, segment_style))
  288. if new_line:
  289. cropped_line = adjust_line_length(
  290. line, length, style=style, pad=pad
  291. )
  292. if include_new_lines:
  293. cropped_line.append(new_line_segment)
  294. yield cropped_line
  295. line.clear()
  296. else:
  297. append(segment)
  298. if line:
  299. yield adjust_line_length(line, length, style=style, pad=pad)
  300. @classmethod
  301. def adjust_line_length(
  302. cls,
  303. line: List["Segment"],
  304. length: int,
  305. style: Optional[Style] = None,
  306. pad: bool = True,
  307. ) -> List["Segment"]:
  308. """Adjust a line to a given width (cropping or padding as required).
  309. Args:
  310. segments (Iterable[Segment]): A list of segments in a single line.
  311. length (int): The desired width of the line.
  312. style (Style, optional): The style of padding if used (space on the end). Defaults to None.
  313. pad (bool, optional): Pad lines with spaces if they are shorter than `length`. Defaults to True.
  314. Returns:
  315. List[Segment]: A line of segments with the desired length.
  316. """
  317. line_length = sum(segment.cell_length for segment in line)
  318. new_line: List[Segment]
  319. if line_length < length:
  320. if pad:
  321. new_line = line + [cls(" " * (length - line_length), style)]
  322. else:
  323. new_line = line[:]
  324. elif line_length > length:
  325. new_line = []
  326. append = new_line.append
  327. line_length = 0
  328. for segment in line:
  329. segment_length = segment.cell_length
  330. if line_length + segment_length < length or segment.control:
  331. append(segment)
  332. line_length += segment_length
  333. else:
  334. text, segment_style, _ = segment
  335. text = set_cell_size(text, length - line_length)
  336. append(cls(text, segment_style))
  337. break
  338. else:
  339. new_line = line[:]
  340. return new_line
  341. @classmethod
  342. def get_line_length(cls, line: List["Segment"]) -> int:
  343. """Get the length of list of segments.
  344. Args:
  345. line (List[Segment]): A line encoded as a list of Segments (assumes no '\\\\n' characters),
  346. Returns:
  347. int: The length of the line.
  348. """
  349. _cell_len = cell_len
  350. return sum(_cell_len(text) for text, style, control in line if not control)
  351. @classmethod
  352. def get_shape(cls, lines: List[List["Segment"]]) -> Tuple[int, int]:
  353. """Get the shape (enclosing rectangle) of a list of lines.
  354. Args:
  355. lines (List[List[Segment]]): A list of lines (no '\\\\n' characters).
  356. Returns:
  357. Tuple[int, int]: Width and height in characters.
  358. """
  359. get_line_length = cls.get_line_length
  360. max_width = max(get_line_length(line) for line in lines) if lines else 0
  361. return (max_width, len(lines))
  362. @classmethod
  363. def set_shape(
  364. cls,
  365. lines: List[List["Segment"]],
  366. width: int,
  367. height: Optional[int] = None,
  368. style: Optional[Style] = None,
  369. new_lines: bool = False,
  370. ) -> List[List["Segment"]]:
  371. """Set the shape of a list of lines (enclosing rectangle).
  372. Args:
  373. lines (List[List[Segment]]): A list of lines.
  374. width (int): Desired width.
  375. height (int, optional): Desired height or None for no change.
  376. style (Style, optional): Style of any padding added.
  377. new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
  378. Returns:
  379. List[List[Segment]]: New list of lines.
  380. """
  381. _height = height or len(lines)
  382. blank = (
  383. [cls(" " * width + "\n", style)] if new_lines else [cls(" " * width, style)]
  384. )
  385. adjust_line_length = cls.adjust_line_length
  386. shaped_lines = lines[:_height]
  387. shaped_lines[:] = [
  388. adjust_line_length(line, width, style=style) for line in lines
  389. ]
  390. if len(shaped_lines) < _height:
  391. shaped_lines.extend([blank] * (_height - len(shaped_lines)))
  392. return shaped_lines
  393. @classmethod
  394. def align_top(
  395. cls: Type["Segment"],
  396. lines: List[List["Segment"]],
  397. width: int,
  398. height: int,
  399. style: Style,
  400. new_lines: bool = False,
  401. ) -> List[List["Segment"]]:
  402. """Aligns lines to top (adds extra lines to bottom as required).
  403. Args:
  404. lines (List[List[Segment]]): A list of lines.
  405. width (int): Desired width.
  406. height (int, optional): Desired height or None for no change.
  407. style (Style): Style of any padding added.
  408. new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
  409. Returns:
  410. List[List[Segment]]: New list of lines.
  411. """
  412. extra_lines = height - len(lines)
  413. if not extra_lines:
  414. return lines[:]
  415. lines = lines[:height]
  416. blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style)
  417. lines = lines + [[blank]] * extra_lines
  418. return lines
  419. @classmethod
  420. def align_bottom(
  421. cls: Type["Segment"],
  422. lines: List[List["Segment"]],
  423. width: int,
  424. height: int,
  425. style: Style,
  426. new_lines: bool = False,
  427. ) -> List[List["Segment"]]:
  428. """Aligns render to bottom (adds extra lines above as required).
  429. Args:
  430. lines (List[List[Segment]]): A list of lines.
  431. width (int): Desired width.
  432. height (int, optional): Desired height or None for no change.
  433. style (Style): Style of any padding added. Defaults to None.
  434. new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
  435. Returns:
  436. List[List[Segment]]: New list of lines.
  437. """
  438. extra_lines = height - len(lines)
  439. if not extra_lines:
  440. return lines[:]
  441. lines = lines[:height]
  442. blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style)
  443. lines = [[blank]] * extra_lines + lines
  444. return lines
  445. @classmethod
  446. def align_middle(
  447. cls: Type["Segment"],
  448. lines: List[List["Segment"]],
  449. width: int,
  450. height: int,
  451. style: Style,
  452. new_lines: bool = False,
  453. ) -> List[List["Segment"]]:
  454. """Aligns lines to middle (adds extra lines to above and below as required).
  455. Args:
  456. lines (List[List[Segment]]): A list of lines.
  457. width (int): Desired width.
  458. height (int, optional): Desired height or None for no change.
  459. style (Style): Style of any padding added.
  460. new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
  461. Returns:
  462. List[List[Segment]]: New list of lines.
  463. """
  464. extra_lines = height - len(lines)
  465. if not extra_lines:
  466. return lines[:]
  467. lines = lines[:height]
  468. blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style)
  469. top_lines = extra_lines // 2
  470. bottom_lines = extra_lines - top_lines
  471. lines = [[blank]] * top_lines + lines + [[blank]] * bottom_lines
  472. return lines
  473. @classmethod
  474. def simplify(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
  475. """Simplify an iterable of segments by combining contiguous segments with the same style.
  476. Args:
  477. segments (Iterable[Segment]): An iterable of segments.
  478. Returns:
  479. Iterable[Segment]: A possibly smaller iterable of segments that will render the same way.
  480. """
  481. iter_segments = iter(segments)
  482. try:
  483. last_segment = next(iter_segments)
  484. except StopIteration:
  485. return
  486. _Segment = Segment
  487. for segment in iter_segments:
  488. if last_segment.style == segment.style and not segment.control:
  489. last_segment = _Segment(
  490. last_segment.text + segment.text, last_segment.style
  491. )
  492. else:
  493. yield last_segment
  494. last_segment = segment
  495. yield last_segment
  496. @classmethod
  497. def strip_links(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
  498. """Remove all links from an iterable of styles.
  499. Args:
  500. segments (Iterable[Segment]): An iterable segments.
  501. Yields:
  502. Segment: Segments with link removed.
  503. """
  504. for segment in segments:
  505. if segment.control or segment.style is None:
  506. yield segment
  507. else:
  508. text, style, _control = segment
  509. yield cls(text, style.update_link(None) if style else None)
  510. @classmethod
  511. def strip_styles(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
  512. """Remove all styles from an iterable of segments.
  513. Args:
  514. segments (Iterable[Segment]): An iterable segments.
  515. Yields:
  516. Segment: Segments with styles replace with None
  517. """
  518. for text, _style, control in segments:
  519. yield cls(text, None, control)
  520. @classmethod
  521. def remove_color(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
  522. """Remove all color from an iterable of segments.
  523. Args:
  524. segments (Iterable[Segment]): An iterable segments.
  525. Yields:
  526. Segment: Segments with colorless style.
  527. """
  528. cache: Dict[Style, Style] = {}
  529. for text, style, control in segments:
  530. if style:
  531. colorless_style = cache.get(style)
  532. if colorless_style is None:
  533. colorless_style = style.without_color
  534. cache[style] = colorless_style
  535. yield cls(text, colorless_style, control)
  536. else:
  537. yield cls(text, None, control)
  538. @classmethod
  539. def divide(
  540. cls, segments: Iterable["Segment"], cuts: Iterable[int]
  541. ) -> Iterable[List["Segment"]]:
  542. """Divides an iterable of segments in to portions.
  543. Args:
  544. cuts (Iterable[int]): Cell positions where to divide.
  545. Yields:
  546. [Iterable[List[Segment]]]: An iterable of Segments in List.
  547. """
  548. split_segments: List["Segment"] = []
  549. add_segment = split_segments.append
  550. iter_cuts = iter(cuts)
  551. while True:
  552. cut = next(iter_cuts, -1)
  553. if cut == -1:
  554. return
  555. if cut != 0:
  556. break
  557. yield []
  558. pos = 0
  559. segments_clear = split_segments.clear
  560. segments_copy = split_segments.copy
  561. _cell_len = cached_cell_len
  562. for segment in segments:
  563. text, _style, control = segment
  564. while text:
  565. end_pos = pos if control else pos + _cell_len(text)
  566. if end_pos < cut:
  567. add_segment(segment)
  568. pos = end_pos
  569. break
  570. if end_pos == cut:
  571. add_segment(segment)
  572. yield segments_copy()
  573. segments_clear()
  574. pos = end_pos
  575. cut = next(iter_cuts, -1)
  576. if cut == -1:
  577. if split_segments:
  578. yield segments_copy()
  579. return
  580. break
  581. else:
  582. before, segment = segment.split_cells(cut - pos)
  583. text, _style, control = segment
  584. add_segment(before)
  585. yield segments_copy()
  586. segments_clear()
  587. pos = cut
  588. cut = next(iter_cuts, -1)
  589. if cut == -1:
  590. if split_segments:
  591. yield segments_copy()
  592. return
  593. yield segments_copy()
  594. class Segments:
  595. """A simple renderable to render an iterable of segments. This class may be useful if
  596. you want to print segments outside of a __rich_console__ method.
  597. Args:
  598. segments (Iterable[Segment]): An iterable of segments.
  599. new_lines (bool, optional): Add new lines between segments. Defaults to False.
  600. """
  601. def __init__(self, segments: Iterable[Segment], new_lines: bool = False) -> None:
  602. self.segments = list(segments)
  603. self.new_lines = new_lines
  604. def __rich_console__(
  605. self, console: "Console", options: "ConsoleOptions"
  606. ) -> "RenderResult":
  607. if self.new_lines:
  608. line = Segment.line()
  609. for segment in self.segments:
  610. yield segment
  611. yield line
  612. else:
  613. yield from self.segments
  614. class SegmentLines:
  615. def __init__(self, lines: Iterable[List[Segment]], new_lines: bool = False) -> None:
  616. """A simple renderable containing a number of lines of segments. May be used as an intermediate
  617. in rendering process.
  618. Args:
  619. lines (Iterable[List[Segment]]): Lists of segments forming lines.
  620. new_lines (bool, optional): Insert new lines after each line. Defaults to False.
  621. """
  622. self.lines = list(lines)
  623. self.new_lines = new_lines
  624. def __rich_console__(
  625. self, console: "Console", options: "ConsoleOptions"
  626. ) -> "RenderResult":
  627. if self.new_lines:
  628. new_line = Segment.line()
  629. for line in self.lines:
  630. yield from line
  631. yield new_line
  632. else:
  633. for line in self.lines:
  634. yield from line
  635. if __name__ == "__main__": # pragma: no cover
  636. from rich.console import Console
  637. from rich.syntax import Syntax
  638. from rich.text import Text
  639. code = """from rich.console import Console
  640. console = Console()
  641. text = Text.from_markup("Hello, [bold magenta]World[/]!")
  642. console.print(text)"""
  643. text = Text.from_markup("Hello, [bold magenta]World[/]!")
  644. console = Console()
  645. console.rule("rich.Segment")
  646. console.print(
  647. "A Segment is the last step in the Rich render process before generating text with ANSI codes."
  648. )
  649. console.print("\nConsider the following code:\n")
  650. console.print(Syntax(code, "python", line_numbers=True))
  651. console.print()
  652. console.print(
  653. "When you call [b]print()[/b], Rich [i]renders[/i] the object in to the following:\n"
  654. )
  655. fragments = list(console.render(text))
  656. console.print(fragments)
  657. console.print()
  658. console.print("The Segments are then processed to produce the following output:\n")
  659. console.print(text)
  660. console.print(
  661. "\nYou will only need to know this if you are implementing your own Rich renderables."
  662. )