@@ -508,6 +508,19 @@ def __init__(
508508 # Commands that will run at the beginning of the command loop
509509 self ._startup_commands : list [str ] = []
510510
511+ # Store initial termios settings to restore after each command.
512+ # This is a faster way of accomplishing what "stty sane" does.
513+ self ._initial_termios_settings = None
514+ if not sys .platform .startswith ('win' ) and self .stdin .isatty ():
515+ try :
516+ import io
517+ import termios
518+
519+ self ._initial_termios_settings = termios .tcgetattr (self .stdin .fileno ())
520+ except (ImportError , io .UnsupportedOperation , termios .error ):
521+ # This can happen if termios isn't available or stdin is a pseudo-TTY
522+ self ._initial_termios_settings = None
523+
511524 # If a startup script is provided and exists, then execute it in the startup commands
512525 if startup_script :
513526 startup_script = os .path .abspath (os .path .expanduser (startup_script ))
@@ -1198,22 +1211,39 @@ def print_to(
11981211 end : str = "\n " ,
11991212 style : StyleType | None = None ,
12001213 soft_wrap : bool = True ,
1214+ emoji : bool = False ,
1215+ markup : bool = False ,
1216+ highlight : bool = False ,
12011217 rich_print_kwargs : RichPrintKwargs | None = None ,
12021218 ** kwargs : Any , # noqa: ARG002
12031219 ) -> None :
12041220 """Print objects to a given file stream.
12051221
1222+ This method is configured for general-purpose printing. By default, it enables
1223+ soft wrap and disables Rich's automatic detection for markup, emoji, and highlighting.
1224+ These defaults can be overridden by passing explicit keyword arguments.
1225+
12061226 :param file: file stream being written to
12071227 :param objects: objects to print
12081228 :param sep: string to write between printed text. Defaults to " ".
12091229 :param end: string to write at end of printed text. Defaults to a newline.
12101230 :param style: optional style to apply to output
1211- :param soft_wrap: Enable soft wrap mode. If True, lines of text will not be word-wrapped or cropped to
1212- fit the terminal width. Defaults to True.
1231+ :param soft_wrap: Enable soft wrap mode. If True, lines of text will not be
1232+ word-wrapped or cropped to fit the terminal width. Defaults to True.
1233+ :param emoji: If True, Rich will replace emoji codes (e.g., :smiley:) with their
1234+ corresponding Unicode characters. Defaults to False.
1235+ :param markup: If True, Rich will interpret strings with tags (e.g., [bold]hello[/bold])
1236+ as styled output. Defaults to False.
1237+ :param highlight: If True, Rich will automatically apply highlighting to elements within
1238+ strings, such as common Python data types like numbers, booleans, or None.
1239+ This is particularly useful when pretty printing objects like lists and
1240+ dictionaries to display them in color. Defaults to False.
12131241 :param rich_print_kwargs: optional additional keyword arguments to pass to Rich's Console.print().
12141242 :param kwargs: Arbitrary keyword arguments. This allows subclasses to extend the signature of this
12151243 method and still call `super()` without encountering unexpected keyword argument errors.
12161244 These arguments are not passed to Rich's Console.print().
1245+
1246+ See the Rich documentation for more details on emoji codes, markup tags, and highlighting.
12171247 """
12181248 prepared_objects = ru .prepare_objects_for_rendering (* objects )
12191249
@@ -1224,6 +1254,9 @@ def print_to(
12241254 end = end ,
12251255 style = style ,
12261256 soft_wrap = soft_wrap ,
1257+ emoji = emoji ,
1258+ markup = markup ,
1259+ highlight = highlight ,
12271260 ** (rich_print_kwargs if rich_print_kwargs is not None else {}),
12281261 )
12291262 except BrokenPipeError :
@@ -1242,6 +1275,9 @@ def poutput(
12421275 end : str = "\n " ,
12431276 style : StyleType | None = None ,
12441277 soft_wrap : bool = True ,
1278+ emoji : bool = False ,
1279+ markup : bool = False ,
1280+ highlight : bool = False ,
12451281 rich_print_kwargs : RichPrintKwargs | None = None ,
12461282 ** kwargs : Any , # noqa: ARG002
12471283 ) -> None :
@@ -1256,6 +1292,9 @@ def poutput(
12561292 end = end ,
12571293 style = style ,
12581294 soft_wrap = soft_wrap ,
1295+ emoji = emoji ,
1296+ markup = markup ,
1297+ highlight = highlight ,
12591298 rich_print_kwargs = rich_print_kwargs ,
12601299 )
12611300
@@ -1266,6 +1305,9 @@ def perror(
12661305 end : str = "\n " ,
12671306 style : StyleType | None = Cmd2Style .ERROR ,
12681307 soft_wrap : bool = True ,
1308+ emoji : bool = False ,
1309+ markup : bool = False ,
1310+ highlight : bool = False ,
12691311 rich_print_kwargs : RichPrintKwargs | None = None ,
12701312 ** kwargs : Any , # noqa: ARG002
12711313 ) -> None :
@@ -1282,6 +1324,9 @@ def perror(
12821324 end = end ,
12831325 style = style ,
12841326 soft_wrap = soft_wrap ,
1327+ emoji = emoji ,
1328+ markup = markup ,
1329+ highlight = highlight ,
12851330 rich_print_kwargs = rich_print_kwargs ,
12861331 )
12871332
@@ -1291,6 +1336,9 @@ def psuccess(
12911336 sep : str = " " ,
12921337 end : str = "\n " ,
12931338 soft_wrap : bool = True ,
1339+ emoji : bool = False ,
1340+ markup : bool = False ,
1341+ highlight : bool = False ,
12941342 rich_print_kwargs : RichPrintKwargs | None = None ,
12951343 ** kwargs : Any , # noqa: ARG002
12961344 ) -> None :
@@ -1304,6 +1352,9 @@ def psuccess(
13041352 end = end ,
13051353 style = Cmd2Style .SUCCESS ,
13061354 soft_wrap = soft_wrap ,
1355+ emoji = emoji ,
1356+ markup = markup ,
1357+ highlight = highlight ,
13071358 rich_print_kwargs = rich_print_kwargs ,
13081359 )
13091360
@@ -1313,6 +1364,9 @@ def pwarning(
13131364 sep : str = " " ,
13141365 end : str = "\n " ,
13151366 soft_wrap : bool = True ,
1367+ emoji : bool = False ,
1368+ markup : bool = False ,
1369+ highlight : bool = False ,
13161370 rich_print_kwargs : RichPrintKwargs | None = None ,
13171371 ** kwargs : Any , # noqa: ARG002
13181372 ) -> None :
@@ -1326,6 +1380,9 @@ def pwarning(
13261380 end = end ,
13271381 style = Cmd2Style .WARNING ,
13281382 soft_wrap = soft_wrap ,
1383+ emoji = emoji ,
1384+ markup = markup ,
1385+ highlight = highlight ,
13291386 rich_print_kwargs = rich_print_kwargs ,
13301387 )
13311388
@@ -1390,6 +1447,9 @@ def pfeedback(
13901447 end : str = "\n " ,
13911448 style : StyleType | None = None ,
13921449 soft_wrap : bool = True ,
1450+ emoji : bool = False ,
1451+ markup : bool = False ,
1452+ highlight : bool = False ,
13931453 rich_print_kwargs : RichPrintKwargs | None = None ,
13941454 ** kwargs : Any , # noqa: ARG002
13951455 ) -> None :
@@ -1408,6 +1468,9 @@ def pfeedback(
14081468 end = end ,
14091469 style = style ,
14101470 soft_wrap = soft_wrap ,
1471+ emoji = emoji ,
1472+ markup = markup ,
1473+ highlight = highlight ,
14111474 rich_print_kwargs = rich_print_kwargs ,
14121475 )
14131476 else :
@@ -1417,6 +1480,9 @@ def pfeedback(
14171480 end = end ,
14181481 style = style ,
14191482 soft_wrap = soft_wrap ,
1483+ emoji = emoji ,
1484+ markup = markup ,
1485+ highlight = highlight ,
14201486 rich_print_kwargs = rich_print_kwargs ,
14211487 )
14221488
@@ -1428,6 +1494,9 @@ def ppaged(
14281494 style : StyleType | None = None ,
14291495 chop : bool = False ,
14301496 soft_wrap : bool = True ,
1497+ emoji : bool = False ,
1498+ markup : bool = False ,
1499+ highlight : bool = False ,
14311500 rich_print_kwargs : RichPrintKwargs | None = None ,
14321501 ** kwargs : Any , # noqa: ARG002
14331502 ) -> None :
@@ -1479,6 +1548,9 @@ def ppaged(
14791548 end = end ,
14801549 style = style ,
14811550 soft_wrap = soft_wrap ,
1551+ emoji = emoji ,
1552+ markup = markup ,
1553+ highlight = highlight ,
14821554 ** (rich_print_kwargs if rich_print_kwargs is not None else {}),
14831555 )
14841556 output_bytes = capture .get ().encode ('utf-8' , 'replace' )
@@ -1503,6 +1575,9 @@ def ppaged(
15031575 end = end ,
15041576 style = style ,
15051577 soft_wrap = soft_wrap ,
1578+ emoji = emoji ,
1579+ markup = markup ,
1580+ highlight = highlight ,
15061581 rich_print_kwargs = rich_print_kwargs ,
15071582 )
15081583
@@ -2760,14 +2835,15 @@ def onecmd_plus_hooks(
27602835
27612836 def _run_cmdfinalization_hooks (self , stop : bool , statement : Statement | None ) -> bool :
27622837 """Run the command finalization hooks."""
2763- with self .sigint_protection :
2764- if not sys .platform .startswith ('win' ) and self .stdin .isatty ():
2765- # Before the next command runs, fix any terminal problems like those
2766- # caused by certain binary characters having been printed to it.
2767- import subprocess
2768-
2769- proc = subprocess .Popen (['stty' , 'sane' ]) # noqa: S607
2770- proc .communicate ()
2838+ if self ._initial_termios_settings is not None and self .stdin .isatty ():
2839+ import io
2840+ import termios
2841+
2842+ # Before the next command runs, fix any terminal problems like those
2843+ # caused by certain binary characters having been printed to it.
2844+ with self .sigint_protection , contextlib .suppress (io .UnsupportedOperation , termios .error ):
2845+ # This can fail if stdin is a pseudo-TTY, in which case we just ignore it
2846+ termios .tcsetattr (self .stdin .fileno (), termios .TCSANOW , self ._initial_termios_settings )
27712847
27722848 data = plugin .CommandFinalizationData (stop , statement )
27732849 for func in self ._cmdfinalization_hooks :
0 commit comments