@@ -129,10 +129,10 @@ def __console__(
129129
130130
131131"""A type that may be rendered by Console."""
132- RenderableType = Union [ConsoleRenderable , RichCast , Control , str ]
132+ RenderableType = Union [ConsoleRenderable , RichCast , str ]
133133
134134"""The result of calling a __console__ method."""
135- RenderResult = Iterable [Union [RenderableType , Segment , Control ]]
135+ RenderResult = Iterable [Union [RenderableType , Segment ]]
136136
137137
138138_null_highlighter = NullHighlighter ()
@@ -207,15 +207,14 @@ class ConsoleThreadLocals(threading.local):
207207
208208 buffer : List [Segment ] = field (default_factory = list )
209209 buffer_index : int = 0
210- control : List [str ] = field (default_factory = list )
211210
212211
213212def detect_legacy_windows () -> bool :
214213 """Detect legacy Windows."""
215214 return "WINDIR" in os .environ and "WT_SESSION" not in os .environ
216215
217216
218- if detect_legacy_windows ():
217+ if detect_legacy_windows (): # pragma: no cover
219218 from colorama import init
220219
221220 init ()
@@ -274,18 +273,16 @@ def __init__(
274273
275274 self ._color_system : Optional [ColorSystem ]
276275 self ._force_terminal = force_terminal
277- if self .legacy_windows :
278- self .file = file or sys .stdout
279- self ._color_system = COLOR_SYSTEMS ["windows" ]
276+ self .file = file or sys .stdout
277+
278+ if color_system is None :
279+ self ._color_system = None
280+ elif color_system == "auto" :
281+ self ._color_system = self ._detect_color_system ()
280282 else :
281- self .file = file or sys .stdout
282- if color_system is None :
283- self ._color_system = None
284- elif color_system == "auto" :
285- self ._color_system = self ._detect_color_system ()
286- else :
287- self ._color_system = COLOR_SYSTEMS [color_system ]
283+ self ._color_system = COLOR_SYSTEMS [color_system ]
288284
285+ self ._lock = threading .RLock ()
289286 self ._log_render = LogRender (
290287 show_time = log_time , show_path = log_path , time_format = log_time_format
291288 )
@@ -312,15 +309,12 @@ def _buffer_index(self) -> int:
312309 def _buffer_index (self , value : int ) -> None :
313310 self ._thread_locals .buffer_index = value
314311
315- @property
316- def _control (self ) -> List [str ]:
317- """Get control codes buffer."""
318- return self ._thread_locals .control
319-
320312 def _detect_color_system (self ) -> Optional [ColorSystem ]:
321313 """Detect color system from env vars."""
322314 if not self .is_terminal :
323315 return None
316+ if self .legacy_windows : # pragma: no cover
317+ return ColorSystem .WINDOWS
324318 color_term = os .environ .get ("COLORTERM" , "" ).strip ().lower ()
325319 return (
326320 ColorSystem .TRUECOLOR
@@ -402,10 +396,8 @@ def size(self) -> ConsoleDimensions:
402396 return ConsoleDimensions (self ._width , self ._height )
403397
404398 width , height = shutil .get_terminal_size ()
405- if self .legacy_windows :
406- width -= 1
407399 return ConsoleDimensions (
408- width if self ._width is None else self ._width ,
400+ ( width - self . legacy_windows ) if self ._width is None else self ._width ,
409401 height if self ._height is None else self ._height ,
410402 )
411403
@@ -441,9 +433,7 @@ def show_cursor(self, show: bool = True) -> None:
441433 self .file .write ("\033 [?25h" if show else "\033 [?25l" )
442434
443435 def _render (
444- self ,
445- renderable : Union [RenderableType , Control ],
446- options : Optional [ConsoleOptions ],
436+ self , renderable : RenderableType , options : Optional [ConsoleOptions ],
447437 ) -> Iterable [Segment ]:
448438 """Render an object in to an iterable of `Segment` instances.
449439
@@ -459,9 +449,6 @@ def _render(
459449 Iterable[Segment]: An iterable of segments that may be rendered.
460450 """
461451 render_iterable : RenderResult
462- if isinstance (renderable , Control ):
463- self ._control .append (renderable .codes )
464- return
465452 render_options = options or self .options
466453 if isinstance (renderable , ConsoleRenderable ):
467454 render_iterable = renderable .__console__ (self , render_options )
@@ -487,7 +474,7 @@ def _render(
487474 yield from self .render (render_output , render_options )
488475
489476 def render (
490- self , renderable : RenderableType , options : Optional [ConsoleOptions ]
477+ self , renderable : RenderableType , options : Optional [ConsoleOptions ] = None
491478 ) -> Iterable [Segment ]:
492479 """Render an object in to an iterable of `Segment` instances.
493480
@@ -504,7 +491,7 @@ def render(
504491 Iterable[Segment]: An iterable of segments that may be rendered.
505492 """
506493
507- yield from self ._render (renderable , options )
494+ yield from self ._render (renderable , options or self . options )
508495
509496 def render_lines (
510497 self ,
@@ -685,6 +672,16 @@ def rule(
685672 rule = Rule (title = title , character = character , style = style )
686673 self .print (rule )
687674
675+ def control (self , control_codes : Union ["Control" , str ]) -> None :
676+ """Insert non-printing control codes.
677+
678+ Args:
679+ control_codes (str): Control codes, such as those that may move the cursor.
680+ """
681+
682+ self ._buffer .append (Segment .control (str (control_codes )))
683+ self ._check_buffer ()
684+
688685 def print (
689686 self ,
690687 * objects : Any ,
@@ -802,10 +799,11 @@ def log(
802799
803800 def _check_buffer (self ) -> None :
804801 """Check if the buffer may be rendered."""
805- if self ._buffer_index == 0 :
806- text = self ._render_buffer ()
807- self .file .write (text )
808- self .file .flush ()
802+ with self ._lock :
803+ if self ._buffer_index == 0 :
804+ text = self ._render_buffer ()
805+ self .file .write (text )
806+ self .file .flush ()
809807
810808 def _render_buffer (self ) -> str :
811809 """Render buffered output, and clear buffer."""
@@ -818,14 +816,13 @@ def _render_buffer(self) -> str:
818816 self ._record_buffer .extend (buffer )
819817 del self ._buffer [:]
820818 for line in Segment .split_and_crop_lines (buffer , self .width , pad = False ):
821- for text , style in line :
822- if style :
819+ for text , style , is_control in line :
820+ if style and not is_control :
823821 append (style .render (text , color_system = color_system ))
824822 else :
825823 append (text )
826824
827- rendered = "" .join (self ._control ) + "" .join (output )
828- del self ._control [:]
825+ rendered = "" .join (output )
829826 return rendered
830827
831828 def export_text (self , clear : bool = True , styles : bool = False ) -> str :
@@ -848,10 +845,10 @@ def export_text(self, clear: bool = True, styles: bool = False) -> str:
848845 if styles :
849846 text = "" .join (
850847 (style .render (text ) if style else text )
851- for text , style in self ._record_buffer
848+ for text , style , _ in self ._record_buffer
852849 )
853850 else :
854- text = "" .join (text for text , _ in self ._record_buffer )
851+ text = "" .join (text for text , _ , _ in self ._record_buffer )
855852 if clear :
856853 del self ._record_buffer [:]
857854 return text
@@ -907,7 +904,9 @@ def escape(text: str) -> str:
907904
908905 with self ._record_buffer_lock :
909906 if inline_styles :
910- for text , style in Segment .simplify (self ._record_buffer ):
907+ for text , style , _ in Segment .filter_control (
908+ Segment .simplify (self ._record_buffer )
909+ ):
911910 text = escape (text )
912911 if style :
913912 rule = style .get_html_style (_theme )
@@ -916,7 +915,9 @@ def escape(text: str) -> str:
916915 append (text )
917916 else :
918917 styles : Dict [str , int ] = {}
919- for text , style in Segment .simplify (self ._record_buffer ):
918+ for text , style , _ in Segment .filter_control (
919+ Segment .simplify (self ._record_buffer )
920+ ):
920921 text = escape (text )
921922 if style :
922923 rule = style .get_html_style (_theme )
0 commit comments