1717import rich .repr
1818from rich ._wrap import divide_line
1919from rich .cells import set_cell_size
20- from rich .console import Console , OverflowMethod
20+ from rich .console import Console
2121from rich .segment import Segment , Segments
2222from rich .style import Style as RichStyle
2323from rich .terminal_theme import TerminalTheme
2828from textual ._context import active_app
2929from textual ._loop import loop_last
3030from textual .color import Color
31- from textual .css .styles import RulesMap
32- from textual .css .types import TextAlign
31+ from textual .css .types import TextAlign , TextOverflow
3332from textual .selection import Selection
3433from textual .strip import Strip
3534from textual .style import Style
36- from textual .visual import Visual
35+ from textual .visual import Rules , Visual
3736
3837if TYPE_CHECKING :
3938 pass
@@ -132,9 +131,6 @@ def __init__(
132131 text: text content.
133132 spans: Optional list of spans.
134133 cell_length: Cell length of text if known, otherwise `None`.
135- align: Align method.
136- no_wrap: Disable wrapping.
137- ellipsis: Add ellipsis when wrapping is disabled and text is cropped.
138134 """
139135 self ._text : str = _strip_control_codes (text )
140136 self ._spans : list [Span ] = [] if spans is None else spans
@@ -174,19 +170,23 @@ def markup(self) -> str:
174170 return markup
175171
176172 @classmethod
177- def from_markup (cls , markup : str ) -> Content :
173+ def from_markup (cls , markup : str | Content ) -> Content :
178174 """Create content from Textual markup.
179175
176+ If `markup` is already Content, return it unmodified.
177+
180178 !!! note
181179 Textual markup is not the same as Rich markup. Use [Text.parse] to parse Rich Console markup.
182180
183181
184182 Args:
185- markup: Textual Markup
183+ markup: Textual markup, or Content.
186184
187185 Returns:
188186 New Content instance.
189187 """
188+ if isinstance (markup , Content ):
189+ return markup
190190 from textual .markup import to_content
191191
192192 content = to_content (markup )
@@ -286,7 +286,7 @@ def __lt__(self, other: object) -> bool:
286286
287287 def get_optimal_width (
288288 self ,
289- rules : RulesMap ,
289+ rules : Rules ,
290290 container_width : int ,
291291 ) -> int :
292292 """Get optimal width of the visual to display its content. Part of the Textual Visual protocol.
@@ -302,7 +302,7 @@ def get_optimal_width(
302302 lines = self .without_spans .split ("\n " )
303303 return max (line .cell_length for line in lines )
304304
305- def get_height (self , rules : RulesMap , width : int ) -> int :
305+ def get_height (self , rules : Rules , width : int ) -> int :
306306 """Get the height of the visual if rendered with the given width. Part of the Textual Visual protocol.
307307
308308 Args:
@@ -312,14 +312,16 @@ def get_height(self, rules: RulesMap, width: int) -> int:
312312 Returns:
313313 A height in lines.
314314 """
315- lines = self .without_spans ._wrap_and_format (width )
315+ lines = self .without_spans ._wrap_and_format (
316+ width , no_wrap = rules .get ("text_wrap" ) == "nowrap"
317+ )
316318 return len (lines )
317319
318320 def _wrap_and_format (
319321 self ,
320322 width : int ,
321323 align : TextAlign = "left" ,
322- overflow : OverflowMethod = "fold" ,
324+ overflow : TextOverflow = "fold" ,
323325 no_wrap : bool = False ,
324326 tab_size : int = 8 ,
325327 selection : Selection | None = None ,
@@ -355,13 +357,15 @@ def get_span(y: int) -> tuple[int, int] | None:
355357 end = len (line .plain )
356358 line = line .stylize (selection_style , start , end )
357359
358- content_line = FormattedLine (
359- line .expand_tabs (tab_size ), width , y = y , align = align
360- )
360+ line = line .expand_tabs (tab_size )
361361
362362 if no_wrap :
363+ if overflow == "ellipsis" and no_wrap :
364+ line = line .truncate (width , ellipsis = True )
365+ content_line = FormattedLine (line , width , y = y , align = align )
363366 new_lines = [content_line ]
364367 else :
368+ content_line = FormattedLine (line , width , y = y , align = align )
365369 offsets = divide_line (line .plain , width , fold = overflow == "fold" )
366370 divided_lines = content_line .content .divide (offsets )
367371 new_lines = [
@@ -378,7 +382,7 @@ def get_span(y: int) -> tuple[int, int] | None:
378382
379383 def render_strips (
380384 self ,
381- rules : RulesMap ,
385+ rules : Rules ,
382386 width : int ,
383387 height : int | None ,
384388 style : Style ,
@@ -391,8 +395,8 @@ def render_strips(
391395 lines = self ._wrap_and_format (
392396 width ,
393397 align = rules .get ("text_align" , "left" ),
394- overflow = " fold" ,
395- no_wrap = False ,
398+ overflow = rules . get ( "text_overflow" , " fold") ,
399+ no_wrap = rules . get ( "text_wrap" , "wrap" ) == "nowrap" ,
396400 tab_size = 8 ,
397401 selection = selection ,
398402 selection_style = selection_style ,
@@ -414,8 +418,11 @@ def __hash__(self) -> int:
414418 return hash (self ._text )
415419
416420 def __rich_repr__ (self ) -> rich .repr .Result :
417- yield self ._text
418- yield "spans" , self ._spans , []
421+ try :
422+ yield self ._text
423+ yield "spans" , self ._spans , []
424+ except AttributeError :
425+ pass
419426
420427 @property
421428 def spans (self ) -> Sequence [Span ]:
0 commit comments