]>
jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/pip/_vendor/rich/segment.py
1 from enum
import IntEnum
2 from functools
import lru_cache
3 from itertools
import filterfalse
4 from logging
import getLogger
5 from operator
import attrgetter
20 _is_single_cell_widths
,
23 get_character_cell_size
,
26 from .repr import Result
, rich_repr
27 from .style
import Style
30 from .console
import Console
, ConsoleOptions
, RenderResult
32 log
= getLogger("rich")
35 class ControlType(IntEnum
):
36 """Non-printable control codes which typically translate to ANSI codes."""
45 DISABLE_ALT_SCREEN
= 8
50 CURSOR_MOVE_TO_COLUMN
= 13
58 Tuple
[ControlType
, Union
[int, str]],
59 Tuple
[ControlType
, int, int],
64 class Segment(NamedTuple
):
65 """A piece of text with associated style. Segments are produced by the Console render process and
66 are ultimately converted in to strings to be written to the terminal.
69 text (str): A piece of text.
70 style (:class:`~rich.style.Style`, optional): An optional style to apply to the text.
71 control (Tuple[ControlCode], optional): Optional sequence of control codes.
74 cell_length (int): The cell length of this Segment.
78 style
: Optional
[Style
] = None
79 control
: Optional
[Sequence
[ControlCode
]] = None
82 def cell_length(self
) -> int:
83 """The number of terminal cells required to display self.text.
86 int: A number of cells.
88 text
, _style
, control
= self
89 return 0 if control
else cell_len(text
)
91 def __rich_repr__(self
) -> Result
:
93 if self
.control
is None:
94 if self
.style
is not None:
100 def __bool__(self
) -> bool:
101 """Check if the segment contains text."""
102 return bool(self
.text
)
105 def is_control(self
) -> bool:
106 """Check if the segment contains control codes."""
107 return self
.control
is not None
110 @lru_cache(1024 * 16)
111 def _split_cells(cls
, segment
: "Segment", cut
: int) -> Tuple
["Segment", "Segment"]:
113 text
, style
, control
= segment
116 cell_length
= segment
.cell_length
117 if cut
>= cell_length
:
118 return segment
, _Segment("", style
, control
)
120 cell_size
= get_character_cell_size
122 pos
= int((cut
/ cell_length
) * (len(text
) - 1))
125 cell_pos
= cell_len(before
)
128 _Segment(before
, style
, control
),
129 _Segment(text
[pos
:], style
, control
),
131 while pos
< len(text
):
134 cell_pos
+= cell_size(char
)
138 _Segment(before
, style
, control
),
139 _Segment(text
[pos
:], style
, control
),
143 _Segment(before
[: pos
- 1] + " ", style
, control
),
144 _Segment(" " + text
[pos
:], style
, control
),
147 raise AssertionError("Will never reach here")
149 def split_cells(self
, cut
: int) -> Tuple
["Segment", "Segment"]:
150 """Split segment in to two segments at the specified column.
152 If the cut point falls in the middle of a 2-cell wide character then it is replaced
153 by two spaces, to preserve the display width of the parent segment.
156 Tuple[Segment, Segment]: Two segments.
158 text
, style
, control
= self
160 if _is_single_cell_widths(text
):
161 # Fast path with all 1 cell characters
163 return self
, Segment("", style
, control
)
165 Segment(text
[:cut
], style
, control
),
166 Segment(text
[cut
:], style
, control
),
169 return self
._split
_cells
(self
, cut
)
172 def line(cls
) -> "Segment":
173 """Make a new line segment."""
179 segments
: Iterable
["Segment"],
180 style
: Optional
[Style
] = None,
181 post_style
: Optional
[Style
] = None,
182 ) -> Iterable
["Segment"]:
183 """Apply style(s) to an iterable of segments.
185 Returns an iterable of segments where the style is replaced by ``style + segment.style + post_style``.
188 segments (Iterable[Segment]): Segments to process.
189 style (Style, optional): Base style. Defaults to None.
190 post_style (Style, optional): Style to apply on top of segment style. Defaults to None.
193 Iterable[Segments]: A new iterable of segments (possibly the same iterable).
195 result_segments
= segments
197 apply = style
.__add
__
199 cls(text
, None if control
else apply(_style
), control
)
200 for text
, _style
, control
in result_segments
209 else (_style
+ post_style
if _style
else post_style
)
213 for text
, _style
, control
in result_segments
215 return result_segments
219 cls
, segments
: Iterable
["Segment"], is_control
: bool = False
220 ) -> Iterable
["Segment"]:
221 """Filter segments by ``is_control`` attribute.
224 segments (Iterable[Segment]): An iterable of Segment instances.
225 is_control (bool, optional): is_control flag to match in search.
228 Iterable[Segment]: And iterable of Segment instances.
232 return filter(attrgetter("control"), segments
)
234 return filterfalse(attrgetter("control"), segments
)
237 def split_lines(cls
, segments
: Iterable
["Segment"]) -> Iterable
[List
["Segment"]]:
238 """Split a sequence of segments in to a list of lines.
241 segments (Iterable[Segment]): Segments potentially containing line feeds.
244 Iterable[List[Segment]]: Iterable of segment lists, one per line.
246 line
: List
[Segment
] = []
249 for segment
in segments
:
250 if "\n" in segment
.text
and not segment
.control
:
251 text
, style
, _
= segment
253 _text
, new_line
, text
= text
.partition("\n")
255 append(cls(_text
, style
))
266 def split_and_crop_lines(
268 segments
: Iterable
["Segment"],
270 style
: Optional
[Style
] = None,
272 include_new_lines
: bool = True,
273 ) -> Iterable
[List
["Segment"]]:
274 """Split segments in to lines, and crop lines greater than a given length.
277 segments (Iterable[Segment]): An iterable of segments, probably
278 generated from console.render.
279 length (int): Desired line length.
280 style (Style, optional): Style to use for any padding.
281 pad (bool): Enable padding of lines that are less than `length`.
284 Iterable[List[Segment]]: An iterable of lines of segments.
286 line
: List
[Segment
] = []
289 adjust_line_length
= cls
.adjust_line_length
290 new_line_segment
= cls("\n")
292 for segment
in segments
:
293 if "\n" in segment
.text
and not segment
.control
:
294 text
, segment_style
, _
= segment
296 _text
, new_line
, text
= text
.partition("\n")
298 append(cls(_text
, segment_style
))
300 cropped_line
= adjust_line_length(
301 line
, length
, style
=style
, pad
=pad
303 if include_new_lines
:
304 cropped_line
.append(new_line_segment
)
310 yield adjust_line_length(line
, length
, style
=style
, pad
=pad
)
313 def adjust_line_length(
315 line
: List
["Segment"],
317 style
: Optional
[Style
] = None,
319 ) -> List
["Segment"]:
320 """Adjust a line to a given width (cropping or padding as required).
323 segments (Iterable[Segment]): A list of segments in a single line.
324 length (int): The desired width of the line.
325 style (Style, optional): The style of padding if used (space on the end). Defaults to None.
326 pad (bool, optional): Pad lines with spaces if they are shorter than `length`. Defaults to True.
329 List[Segment]: A line of segments with the desired length.
331 line_length
= sum(segment
.cell_length
for segment
in line
)
332 new_line
: List
[Segment
]
334 if line_length
< length
:
336 new_line
= line
+ [cls(" " * (length
- line_length
), style
)]
339 elif line_length
> length
:
341 append
= new_line
.append
344 segment_length
= segment
.cell_length
345 if line_length
+ segment_length
< length
or segment
.control
:
347 line_length
+= segment_length
349 text
, segment_style
, _
= segment
350 text
= set_cell_size(text
, length
- line_length
)
351 append(cls(text
, segment_style
))
358 def get_line_length(cls
, line
: List
["Segment"]) -> int:
359 """Get the length of list of segments.
362 line (List[Segment]): A line encoded as a list of Segments (assumes no '\\\\n' characters),
365 int: The length of the line.
368 return sum(_cell_len(text
) for text
, style
, control
in line
if not control
)
371 def get_shape(cls
, lines
: List
[List
["Segment"]]) -> Tuple
[int, int]:
372 """Get the shape (enclosing rectangle) of a list of lines.
375 lines (List[List[Segment]]): A list of lines (no '\\\\n' characters).
378 Tuple[int, int]: Width and height in characters.
380 get_line_length
= cls
.get_line_length
381 max_width
= max(get_line_length(line
) for line
in lines
) if lines
else 0
382 return (max_width
, len(lines
))
387 lines
: List
[List
["Segment"]],
389 height
: Optional
[int] = None,
390 style
: Optional
[Style
] = None,
391 new_lines
: bool = False,
392 ) -> List
[List
["Segment"]]:
393 """Set the shape of a list of lines (enclosing rectangle).
396 lines (List[List[Segment]]): A list of lines.
397 width (int): Desired width.
398 height (int, optional): Desired height or None for no change.
399 style (Style, optional): Style of any padding added.
400 new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
403 List[List[Segment]]: New list of lines.
405 _height
= height
or len(lines
)
408 [cls(" " * width
+ "\n", style
)] if new_lines
else [cls(" " * width
, style
)]
411 adjust_line_length
= cls
.adjust_line_length
412 shaped_lines
= lines
[:_height
]
414 adjust_line_length(line
, width
, style
=style
) for line
in lines
416 if len(shaped_lines
) < _height
:
417 shaped_lines
.extend([blank
] * (_height
- len(shaped_lines
)))
422 cls
: Type
["Segment"],
423 lines
: List
[List
["Segment"]],
427 new_lines
: bool = False,
428 ) -> List
[List
["Segment"]]:
429 """Aligns lines to top (adds extra lines to bottom as required).
432 lines (List[List[Segment]]): A list of lines.
433 width (int): Desired width.
434 height (int, optional): Desired height or None for no change.
435 style (Style): Style of any padding added.
436 new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
439 List[List[Segment]]: New list of lines.
441 extra_lines
= height
- len(lines
)
444 lines
= lines
[:height
]
445 blank
= cls(" " * width
+ "\n", style
) if new_lines
else cls(" " * width
, style
)
446 lines
= lines
+ [[blank
]] * extra_lines
451 cls
: Type
["Segment"],
452 lines
: List
[List
["Segment"]],
456 new_lines
: bool = False,
457 ) -> List
[List
["Segment"]]:
458 """Aligns render to bottom (adds extra lines above as required).
461 lines (List[List[Segment]]): A list of lines.
462 width (int): Desired width.
463 height (int, optional): Desired height or None for no change.
464 style (Style): Style of any padding added. Defaults to None.
465 new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
468 List[List[Segment]]: New list of lines.
470 extra_lines
= height
- len(lines
)
473 lines
= lines
[:height
]
474 blank
= cls(" " * width
+ "\n", style
) if new_lines
else cls(" " * width
, style
)
475 lines
= [[blank
]] * extra_lines
+ lines
480 cls
: Type
["Segment"],
481 lines
: List
[List
["Segment"]],
485 new_lines
: bool = False,
486 ) -> List
[List
["Segment"]]:
487 """Aligns lines to middle (adds extra lines to above and below as required).
490 lines (List[List[Segment]]): A list of lines.
491 width (int): Desired width.
492 height (int, optional): Desired height or None for no change.
493 style (Style): Style of any padding added.
494 new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
497 List[List[Segment]]: New list of lines.
499 extra_lines
= height
- len(lines
)
502 lines
= lines
[:height
]
503 blank
= cls(" " * width
+ "\n", style
) if new_lines
else cls(" " * width
, style
)
504 top_lines
= extra_lines
// 2
505 bottom_lines
= extra_lines
- top_lines
506 lines
= [[blank
]] * top_lines
+ lines
+ [[blank
]] * bottom_lines
510 def simplify(cls
, segments
: Iterable
["Segment"]) -> Iterable
["Segment"]:
511 """Simplify an iterable of segments by combining contiguous segments with the same style.
514 segments (Iterable[Segment]): An iterable of segments.
517 Iterable[Segment]: A possibly smaller iterable of segments that will render the same way.
519 iter_segments
= iter(segments
)
521 last_segment
= next(iter_segments
)
522 except StopIteration:
526 for segment
in iter_segments
:
527 if last_segment
.style
== segment
.style
and not segment
.control
:
528 last_segment
= _Segment(
529 last_segment
.text
+ segment
.text
, last_segment
.style
533 last_segment
= segment
537 def strip_links(cls
, segments
: Iterable
["Segment"]) -> Iterable
["Segment"]:
538 """Remove all links from an iterable of styles.
541 segments (Iterable[Segment]): An iterable segments.
544 Segment: Segments with link removed.
546 for segment
in segments
:
547 if segment
.control
or segment
.style
is None:
550 text
, style
, _control
= segment
551 yield cls(text
, style
.update_link(None) if style
else None)
554 def strip_styles(cls
, segments
: Iterable
["Segment"]) -> Iterable
["Segment"]:
555 """Remove all styles from an iterable of segments.
558 segments (Iterable[Segment]): An iterable segments.
561 Segment: Segments with styles replace with None
563 for text
, _style
, control
in segments
:
564 yield cls(text
, None, control
)
567 def remove_color(cls
, segments
: Iterable
["Segment"]) -> Iterable
["Segment"]:
568 """Remove all color from an iterable of segments.
571 segments (Iterable[Segment]): An iterable segments.
574 Segment: Segments with colorless style.
577 cache
: Dict
[Style
, Style
] = {}
578 for text
, style
, control
in segments
:
580 colorless_style
= cache
.get(style
)
581 if colorless_style
is None:
582 colorless_style
= style
.without_color
583 cache
[style
] = colorless_style
584 yield cls(text
, colorless_style
, control
)
586 yield cls(text
, None, control
)
590 cls
, segments
: Iterable
["Segment"], cuts
: Iterable
[int]
591 ) -> Iterable
[List
["Segment"]]:
592 """Divides an iterable of segments in to portions.
595 cuts (Iterable[int]): Cell positions where to divide.
598 [Iterable[List[Segment]]]: An iterable of Segments in List.
600 split_segments
: List
["Segment"] = []
601 add_segment
= split_segments
.append
603 iter_cuts
= iter(cuts
)
606 cut
= next(iter_cuts
, -1)
614 segments_clear
= split_segments
.clear
615 segments_copy
= split_segments
.copy
617 _cell_len
= cached_cell_len
618 for segment
in segments
:
619 text
, _style
, control
= segment
621 end_pos
= pos
if control
else pos
+ _cell_len(text
)
629 yield segments_copy()
633 cut
= next(iter_cuts
, -1)
636 yield segments_copy()
642 before
, segment
= segment
.split_cells(cut
- pos
)
643 text
, _style
, control
= segment
645 yield segments_copy()
649 cut
= next(iter_cuts
, -1)
652 yield segments_copy()
655 yield segments_copy()
659 """A simple renderable to render an iterable of segments. This class may be useful if
660 you want to print segments outside of a __rich_console__ method.
663 segments (Iterable[Segment]): An iterable of segments.
664 new_lines (bool, optional): Add new lines between segments. Defaults to False.
667 def __init__(self
, segments
: Iterable
[Segment
], new_lines
: bool = False) -> None:
668 self
.segments
= list(segments
)
669 self
.new_lines
= new_lines
671 def __rich_console__(
672 self
, console
: "Console", options
: "ConsoleOptions"
675 line
= Segment
.line()
676 for segment
in self
.segments
:
680 yield from self
.segments
684 def __init__(self
, lines
: Iterable
[List
[Segment
]], new_lines
: bool = False) -> None:
685 """A simple renderable containing a number of lines of segments. May be used as an intermediate
686 in rendering process.
689 lines (Iterable[List[Segment]]): Lists of segments forming lines.
690 new_lines (bool, optional): Insert new lines after each line. Defaults to False.
692 self
.lines
= list(lines
)
693 self
.new_lines
= new_lines
695 def __rich_console__(
696 self
, console
: "Console", options
: "ConsoleOptions"
699 new_line
= Segment
.line()
700 for line
in self
.lines
:
704 for line
in self
.lines
:
708 if __name__
== "__main__": # pragma: no cover
709 from pip
._vendor
.rich
.console
import Console
710 from pip
._vendor
.rich
.syntax
import Syntax
711 from pip
._vendor
.rich
.text
import Text
713 code
= """from rich.console import Console
715 text = Text.from_markup("Hello, [bold magenta]World[/]!")
716 console.print(text)"""
718 text
= Text
.from_markup("Hello, [bold magenta]World[/]!")
722 console
.rule("rich.Segment")
724 "A Segment is the last step in the Rich render process before generating text with ANSI codes."
726 console
.print("\nConsider the following code:\n")
727 console
.print(Syntax(code
, "python", line_numbers
=True))
730 "When you call [b]print()[/b], Rich [i]renders[/i] the object in to the following:\n"
732 fragments
= list(console
.render(text
))
733 console
.print(fragments
)
735 console
.print("The Segments are then processed to produce the following output:\n")
738 "\nYou will only need to know this if you are implementing your own Rich renderables."