1from __future__ import annotations
2
3import sys
4from typing import TYPE_CHECKING, TextIO, cast
5
6from prompt_toolkit.utils import (
7 get_bell_environment_variable,
8 get_term_environment_variable,
9 is_conemu_ansi,
10)
11
12from .base import DummyOutput, Output
13from .color_depth import ColorDepth
14from .plain_text import PlainTextOutput
15
16if TYPE_CHECKING:
17 from prompt_toolkit.patch_stdout import StdoutProxy
18
19
20__all__ = [
21 "create_output",
22]
23
24
25def create_output(
26 stdout: TextIO | StdoutProxy | None = None, always_prefer_tty: bool = False
27) -> Output:
28 """
29 Return an :class:`~prompt_toolkit.output.Output` instance for the command
30 line.
31
32 :param stdout: The stdout object
33 :param always_prefer_tty: When set, look for `sys.stderr` if `sys.stdout`
34 is not a TTY. Useful if `sys.stdout` is redirected to a file, but we
35 still want user input and output on the terminal.
36
37 By default, this is `False`. If `sys.stdout` is not a terminal (maybe
38 it's redirected to a file), then a `PlainTextOutput` will be returned.
39 That way, tools like `print_formatted_text` will write plain text into
40 that file.
41 """
42 # Consider TERM, PROMPT_TOOLKIT_BELL, and PROMPT_TOOLKIT_COLOR_DEPTH
43 # environment variables. Notice that PROMPT_TOOLKIT_COLOR_DEPTH value is
44 # the default that's used if the Application doesn't override it.
45 term_from_env = get_term_environment_variable()
46 bell_from_env = get_bell_environment_variable()
47 color_depth_from_env = ColorDepth.from_env()
48
49 if stdout is None:
50 # By default, render to stdout. If the output is piped somewhere else,
51 # render to stderr.
52 stdout = sys.stdout
53
54 if always_prefer_tty:
55 for io in [sys.stdout, sys.stderr]:
56 if io is not None and io.isatty():
57 # (This is `None` when using `pythonw.exe` on Windows.)
58 stdout = io
59 break
60
61 # If the patch_stdout context manager has been used, then sys.stdout is
62 # replaced by this proxy. For prompt_toolkit applications, we want to use
63 # the real stdout.
64 from prompt_toolkit.patch_stdout import StdoutProxy
65
66 while isinstance(stdout, StdoutProxy):
67 stdout = stdout.original_stdout
68
69 # If the output is still `None`, use a DummyOutput.
70 # This happens for instance on Windows, when running the application under
71 # `pythonw.exe`. In that case, there won't be a terminal Window, and
72 # stdin/stdout/stderr are `None`.
73 if stdout is None:
74 return DummyOutput()
75
76 if sys.platform == "win32":
77 from .conemu import ConEmuOutput
78 from .win32 import Win32Output
79 from .windows10 import Windows10_Output, is_win_vt100_enabled
80
81 if is_win_vt100_enabled():
82 return cast(
83 Output,
84 Windows10_Output(stdout, default_color_depth=color_depth_from_env),
85 )
86 if is_conemu_ansi():
87 return cast(
88 Output, ConEmuOutput(stdout, default_color_depth=color_depth_from_env)
89 )
90 else:
91 return Win32Output(stdout, default_color_depth=color_depth_from_env)
92 else:
93 from .vt100 import Vt100_Output
94
95 # Stdout is not a TTY? Render as plain text.
96 # This is mostly useful if stdout is redirected to a file, and
97 # `print_formatted_text` is used.
98 if not stdout.isatty():
99 return PlainTextOutput(stdout)
100
101 return Vt100_Output.from_pty(
102 stdout,
103 term=term_from_env,
104 default_color_depth=color_depth_from_env,
105 enable_bell=bell_from_env,
106 )