@@ -129,10 +129,10 @@ def __console__(
129
129
130
130
131
131
"""A type that may be rendered by Console."""
132
- RenderableType = Union [ConsoleRenderable , RichCast , Control , str ]
132
+ RenderableType = Union [ConsoleRenderable , RichCast , str ]
133
133
134
134
"""The result of calling a __console__ method."""
135
- RenderResult = Iterable [Union [RenderableType , Segment , Control ]]
135
+ RenderResult = Iterable [Union [RenderableType , Segment ]]
136
136
137
137
138
138
_null_highlighter = NullHighlighter ()
@@ -207,15 +207,14 @@ class ConsoleThreadLocals(threading.local):
207
207
208
208
buffer : List [Segment ] = field (default_factory = list )
209
209
buffer_index : int = 0
210
- control : List [str ] = field (default_factory = list )
211
210
212
211
213
212
def detect_legacy_windows () -> bool :
214
213
"""Detect legacy Windows."""
215
214
return "WINDIR" in os .environ and "WT_SESSION" not in os .environ
216
215
217
216
218
- if detect_legacy_windows ():
217
+ if detect_legacy_windows (): # pragma: no cover
219
218
from colorama import init
220
219
221
220
init ()
@@ -274,18 +273,16 @@ def __init__(
274
273
275
274
self ._color_system : Optional [ColorSystem ]
276
275
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 ()
280
282
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 ]
288
284
285
+ self ._lock = threading .RLock ()
289
286
self ._log_render = LogRender (
290
287
show_time = log_time , show_path = log_path , time_format = log_time_format
291
288
)
@@ -312,15 +309,12 @@ def _buffer_index(self) -> int:
312
309
def _buffer_index (self , value : int ) -> None :
313
310
self ._thread_locals .buffer_index = value
314
311
315
- @property
316
- def _control (self ) -> List [str ]:
317
- """Get control codes buffer."""
318
- return self ._thread_locals .control
319
-
320
312
def _detect_color_system (self ) -> Optional [ColorSystem ]:
321
313
"""Detect color system from env vars."""
322
314
if not self .is_terminal :
323
315
return None
316
+ if self .legacy_windows : # pragma: no cover
317
+ return ColorSystem .WINDOWS
324
318
color_term = os .environ .get ("COLORTERM" , "" ).strip ().lower ()
325
319
return (
326
320
ColorSystem .TRUECOLOR
@@ -402,10 +396,8 @@ def size(self) -> ConsoleDimensions:
402
396
return ConsoleDimensions (self ._width , self ._height )
403
397
404
398
width , height = shutil .get_terminal_size ()
405
- if self .legacy_windows :
406
- width -= 1
407
399
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 ,
409
401
height if self ._height is None else self ._height ,
410
402
)
411
403
@@ -441,9 +433,7 @@ def show_cursor(self, show: bool = True) -> None:
441
433
self .file .write ("\033 [?25h" if show else "\033 [?25l" )
442
434
443
435
def _render (
444
- self ,
445
- renderable : Union [RenderableType , Control ],
446
- options : Optional [ConsoleOptions ],
436
+ self , renderable : RenderableType , options : Optional [ConsoleOptions ],
447
437
) -> Iterable [Segment ]:
448
438
"""Render an object in to an iterable of `Segment` instances.
449
439
@@ -459,9 +449,6 @@ def _render(
459
449
Iterable[Segment]: An iterable of segments that may be rendered.
460
450
"""
461
451
render_iterable : RenderResult
462
- if isinstance (renderable , Control ):
463
- self ._control .append (renderable .codes )
464
- return
465
452
render_options = options or self .options
466
453
if isinstance (renderable , ConsoleRenderable ):
467
454
render_iterable = renderable .__console__ (self , render_options )
@@ -487,7 +474,7 @@ def _render(
487
474
yield from self .render (render_output , render_options )
488
475
489
476
def render (
490
- self , renderable : RenderableType , options : Optional [ConsoleOptions ]
477
+ self , renderable : RenderableType , options : Optional [ConsoleOptions ] = None
491
478
) -> Iterable [Segment ]:
492
479
"""Render an object in to an iterable of `Segment` instances.
493
480
@@ -504,7 +491,7 @@ def render(
504
491
Iterable[Segment]: An iterable of segments that may be rendered.
505
492
"""
506
493
507
- yield from self ._render (renderable , options )
494
+ yield from self ._render (renderable , options or self . options )
508
495
509
496
def render_lines (
510
497
self ,
@@ -685,6 +672,16 @@ def rule(
685
672
rule = Rule (title = title , character = character , style = style )
686
673
self .print (rule )
687
674
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
+
688
685
def print (
689
686
self ,
690
687
* objects : Any ,
@@ -802,10 +799,11 @@ def log(
802
799
803
800
def _check_buffer (self ) -> None :
804
801
"""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 ()
809
807
810
808
def _render_buffer (self ) -> str :
811
809
"""Render buffered output, and clear buffer."""
@@ -818,14 +816,13 @@ def _render_buffer(self) -> str:
818
816
self ._record_buffer .extend (buffer )
819
817
del self ._buffer [:]
820
818
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 :
823
821
append (style .render (text , color_system = color_system ))
824
822
else :
825
823
append (text )
826
824
827
- rendered = "" .join (self ._control ) + "" .join (output )
828
- del self ._control [:]
825
+ rendered = "" .join (output )
829
826
return rendered
830
827
831
828
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:
848
845
if styles :
849
846
text = "" .join (
850
847
(style .render (text ) if style else text )
851
- for text , style in self ._record_buffer
848
+ for text , style , _ in self ._record_buffer
852
849
)
853
850
else :
854
- text = "" .join (text for text , _ in self ._record_buffer )
851
+ text = "" .join (text for text , _ , _ in self ._record_buffer )
855
852
if clear :
856
853
del self ._record_buffer [:]
857
854
return text
@@ -907,7 +904,9 @@ def escape(text: str) -> str:
907
904
908
905
with self ._record_buffer_lock :
909
906
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
+ ):
911
910
text = escape (text )
912
911
if style :
913
912
rule = style .get_html_style (_theme )
@@ -916,7 +915,9 @@ def escape(text: str) -> str:
916
915
append (text )
917
916
else :
918
917
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
+ ):
920
921
text = escape (text )
921
922
if style :
922
923
rule = style .get_html_style (_theme )
0 commit comments