Uploaded Test files
This commit is contained in:
parent
f584ad9d97
commit
2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions
13
venv/Lib/site-packages/prompt_toolkit/output/__init__.py
Normal file
13
venv/Lib/site-packages/prompt_toolkit/output/__init__.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
from .base import DummyOutput, Output
|
||||
from .color_depth import ColorDepth
|
||||
from .defaults import create_output
|
||||
|
||||
__all__ = [
|
||||
# Base.
|
||||
"Output",
|
||||
"DummyOutput",
|
||||
# Color depth.
|
||||
"ColorDepth",
|
||||
# Defaults.
|
||||
"create_output",
|
||||
]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
303
venv/Lib/site-packages/prompt_toolkit/output/base.py
Normal file
303
venv/Lib/site-packages/prompt_toolkit/output/base.py
Normal file
|
@ -0,0 +1,303 @@
|
|||
"""
|
||||
Interface for an output.
|
||||
"""
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from prompt_toolkit.data_structures import Size
|
||||
from prompt_toolkit.styles import Attrs
|
||||
|
||||
from .color_depth import ColorDepth
|
||||
|
||||
__all__ = [
|
||||
"Output",
|
||||
"DummyOutput",
|
||||
]
|
||||
|
||||
|
||||
class Output(metaclass=ABCMeta):
|
||||
"""
|
||||
Base class defining the output interface for a
|
||||
:class:`~prompt_toolkit.renderer.Renderer`.
|
||||
|
||||
Actual implementations are
|
||||
:class:`~prompt_toolkit.output.vt100.Vt100_Output` and
|
||||
:class:`~prompt_toolkit.output.win32.Win32Output`.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def fileno(self) -> int:
|
||||
" Return the file descriptor to which we can write for the output. "
|
||||
|
||||
@abstractmethod
|
||||
def encoding(self) -> str:
|
||||
"""
|
||||
Return the encoding for this output, e.g. 'utf-8'.
|
||||
(This is used mainly to know which characters are supported by the
|
||||
output the data, so that the UI can provide alternatives, when
|
||||
required.)
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def write(self, data: str) -> None:
|
||||
" Write text (Terminal escape sequences will be removed/escaped.) "
|
||||
|
||||
@abstractmethod
|
||||
def write_raw(self, data: str) -> None:
|
||||
" Write text. "
|
||||
|
||||
@abstractmethod
|
||||
def set_title(self, title: str) -> None:
|
||||
" Set terminal title. "
|
||||
|
||||
@abstractmethod
|
||||
def clear_title(self) -> None:
|
||||
" Clear title again. (or restore previous title.) "
|
||||
|
||||
@abstractmethod
|
||||
def flush(self) -> None:
|
||||
" Write to output stream and flush. "
|
||||
|
||||
@abstractmethod
|
||||
def erase_screen(self) -> None:
|
||||
"""
|
||||
Erases the screen with the background colour and moves the cursor to
|
||||
home.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def enter_alternate_screen(self) -> None:
|
||||
" Go to the alternate screen buffer. (For full screen applications). "
|
||||
|
||||
@abstractmethod
|
||||
def quit_alternate_screen(self) -> None:
|
||||
" Leave the alternate screen buffer. "
|
||||
|
||||
@abstractmethod
|
||||
def enable_mouse_support(self) -> None:
|
||||
" Enable mouse. "
|
||||
|
||||
@abstractmethod
|
||||
def disable_mouse_support(self) -> None:
|
||||
" Disable mouse. "
|
||||
|
||||
@abstractmethod
|
||||
def erase_end_of_line(self) -> None:
|
||||
"""
|
||||
Erases from the current cursor position to the end of the current line.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def erase_down(self) -> None:
|
||||
"""
|
||||
Erases the screen from the current line down to the bottom of the
|
||||
screen.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def reset_attributes(self) -> None:
|
||||
" Reset color and styling attributes. "
|
||||
|
||||
@abstractmethod
|
||||
def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None:
|
||||
" Set new color and styling attributes. "
|
||||
|
||||
@abstractmethod
|
||||
def disable_autowrap(self) -> None:
|
||||
" Disable auto line wrapping. "
|
||||
|
||||
@abstractmethod
|
||||
def enable_autowrap(self) -> None:
|
||||
" Enable auto line wrapping. "
|
||||
|
||||
@abstractmethod
|
||||
def cursor_goto(self, row: int = 0, column: int = 0) -> None:
|
||||
" Move cursor position. "
|
||||
|
||||
@abstractmethod
|
||||
def cursor_up(self, amount: int) -> None:
|
||||
" Move cursor `amount` place up. "
|
||||
|
||||
@abstractmethod
|
||||
def cursor_down(self, amount: int) -> None:
|
||||
" Move cursor `amount` place down. "
|
||||
|
||||
@abstractmethod
|
||||
def cursor_forward(self, amount: int) -> None:
|
||||
" Move cursor `amount` place forward. "
|
||||
|
||||
@abstractmethod
|
||||
def cursor_backward(self, amount: int) -> None:
|
||||
" Move cursor `amount` place backward. "
|
||||
|
||||
@abstractmethod
|
||||
def hide_cursor(self) -> None:
|
||||
" Hide cursor. "
|
||||
|
||||
@abstractmethod
|
||||
def show_cursor(self) -> None:
|
||||
" Show cursor. "
|
||||
|
||||
def ask_for_cpr(self) -> None:
|
||||
"""
|
||||
Asks for a cursor position report (CPR).
|
||||
(VT100 only.)
|
||||
"""
|
||||
|
||||
@property
|
||||
def responds_to_cpr(self) -> bool:
|
||||
"""
|
||||
`True` if the `Application` can expect to receive a CPR response after
|
||||
calling `ask_for_cpr` (this will come back through the corresponding
|
||||
`Input`).
|
||||
|
||||
This is used to determine the amount of available rows we have below
|
||||
the cursor position. In the first place, we have this so that the drop
|
||||
down autocompletion menus are sized according to the available space.
|
||||
|
||||
On Windows, we don't need this, there we have
|
||||
`get_rows_below_cursor_position`.
|
||||
"""
|
||||
return False
|
||||
|
||||
@abstractmethod
|
||||
def get_size(self) -> Size:
|
||||
" Return the size of the output window. "
|
||||
|
||||
def bell(self) -> None:
|
||||
" Sound bell. "
|
||||
|
||||
def enable_bracketed_paste(self) -> None:
|
||||
" For vt100 only. "
|
||||
|
||||
def disable_bracketed_paste(self) -> None:
|
||||
" For vt100 only. "
|
||||
|
||||
def scroll_buffer_to_prompt(self) -> None:
|
||||
" For Win32 only. "
|
||||
|
||||
def get_rows_below_cursor_position(self) -> int:
|
||||
" For Windows only. "
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def get_default_color_depth(self) -> ColorDepth:
|
||||
"""
|
||||
Get default color depth for this output.
|
||||
|
||||
This value will be used if no color depth was explicitely passed to the
|
||||
`Application`.
|
||||
|
||||
.. note::
|
||||
|
||||
If the `$PROMPT_TOOLKIT_COLOR_DEPTH` environment variable has been
|
||||
set, then `outputs.defaults.create_output` will pass this value to
|
||||
the implementation as the default_color_depth, which is returned
|
||||
here. (This is not used when the output corresponds to a
|
||||
prompt_toolkit SSH/Telnet session.)
|
||||
"""
|
||||
|
||||
|
||||
class DummyOutput(Output):
|
||||
"""
|
||||
For testing. An output class that doesn't render anything.
|
||||
"""
|
||||
|
||||
def fileno(self) -> int:
|
||||
" There is no sensible default for fileno(). "
|
||||
raise NotImplementedError
|
||||
|
||||
def encoding(self) -> str:
|
||||
return "utf-8"
|
||||
|
||||
def write(self, data: str) -> None:
|
||||
pass
|
||||
|
||||
def write_raw(self, data: str) -> None:
|
||||
pass
|
||||
|
||||
def set_title(self, title: str) -> None:
|
||||
pass
|
||||
|
||||
def clear_title(self) -> None:
|
||||
pass
|
||||
|
||||
def flush(self) -> None:
|
||||
pass
|
||||
|
||||
def erase_screen(self) -> None:
|
||||
pass
|
||||
|
||||
def enter_alternate_screen(self) -> None:
|
||||
pass
|
||||
|
||||
def quit_alternate_screen(self) -> None:
|
||||
pass
|
||||
|
||||
def enable_mouse_support(self) -> None:
|
||||
pass
|
||||
|
||||
def disable_mouse_support(self) -> None:
|
||||
pass
|
||||
|
||||
def erase_end_of_line(self) -> None:
|
||||
pass
|
||||
|
||||
def erase_down(self) -> None:
|
||||
pass
|
||||
|
||||
def reset_attributes(self) -> None:
|
||||
pass
|
||||
|
||||
def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None:
|
||||
pass
|
||||
|
||||
def disable_autowrap(self) -> None:
|
||||
pass
|
||||
|
||||
def enable_autowrap(self) -> None:
|
||||
pass
|
||||
|
||||
def cursor_goto(self, row: int = 0, column: int = 0) -> None:
|
||||
pass
|
||||
|
||||
def cursor_up(self, amount: int) -> None:
|
||||
pass
|
||||
|
||||
def cursor_down(self, amount: int) -> None:
|
||||
pass
|
||||
|
||||
def cursor_forward(self, amount: int) -> None:
|
||||
pass
|
||||
|
||||
def cursor_backward(self, amount: int) -> None:
|
||||
pass
|
||||
|
||||
def hide_cursor(self) -> None:
|
||||
pass
|
||||
|
||||
def show_cursor(self) -> None:
|
||||
pass
|
||||
|
||||
def ask_for_cpr(self) -> None:
|
||||
pass
|
||||
|
||||
def bell(self) -> None:
|
||||
pass
|
||||
|
||||
def enable_bracketed_paste(self) -> None:
|
||||
pass
|
||||
|
||||
def disable_bracketed_paste(self) -> None:
|
||||
pass
|
||||
|
||||
def scroll_buffer_to_prompt(self) -> None:
|
||||
pass
|
||||
|
||||
def get_size(self) -> Size:
|
||||
return Size(rows=40, columns=80)
|
||||
|
||||
def get_rows_below_cursor_position(self) -> int:
|
||||
return 40
|
||||
|
||||
def get_default_color_depth(self) -> ColorDepth:
|
||||
return ColorDepth.DEPTH_1_BIT
|
58
venv/Lib/site-packages/prompt_toolkit/output/color_depth.py
Normal file
58
venv/Lib/site-packages/prompt_toolkit/output/color_depth.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
import os
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
__all__ = [
|
||||
"ColorDepth",
|
||||
]
|
||||
|
||||
|
||||
class ColorDepth(str, Enum):
|
||||
"""
|
||||
Possible color depth values for the output.
|
||||
"""
|
||||
|
||||
value: str
|
||||
|
||||
#: One color only.
|
||||
DEPTH_1_BIT = "DEPTH_1_BIT"
|
||||
|
||||
#: ANSI Colors.
|
||||
DEPTH_4_BIT = "DEPTH_4_BIT"
|
||||
|
||||
#: The default.
|
||||
DEPTH_8_BIT = "DEPTH_8_BIT"
|
||||
|
||||
#: 24 bit True color.
|
||||
DEPTH_24_BIT = "DEPTH_24_BIT"
|
||||
|
||||
# Aliases.
|
||||
MONOCHROME = DEPTH_1_BIT
|
||||
ANSI_COLORS_ONLY = DEPTH_4_BIT
|
||||
DEFAULT = DEPTH_8_BIT
|
||||
TRUE_COLOR = DEPTH_24_BIT
|
||||
|
||||
@classmethod
|
||||
def from_env(cls) -> Optional["ColorDepth"]:
|
||||
"""
|
||||
Return the color depth if the $PROMPT_TOOLKIT_COLOR_DEPTH environment
|
||||
variable has been set.
|
||||
|
||||
This is a way to enforce a certain color depth in all prompt_toolkit
|
||||
applications.
|
||||
"""
|
||||
# Check the `PROMPT_TOOLKIT_COLOR_DEPTH` environment variable.
|
||||
all_values = [i.value for i in ColorDepth]
|
||||
if os.environ.get("PROMPT_TOOLKIT_COLOR_DEPTH") in all_values:
|
||||
return cls(os.environ["PROMPT_TOOLKIT_COLOR_DEPTH"])
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def default(cls) -> "ColorDepth":
|
||||
"""
|
||||
Return the default color depth for the default output.
|
||||
"""
|
||||
from .defaults import create_output
|
||||
|
||||
return create_output().get_default_color_depth()
|
59
venv/Lib/site-packages/prompt_toolkit/output/conemu.py
Normal file
59
venv/Lib/site-packages/prompt_toolkit/output/conemu.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
from typing import Any, Optional, TextIO
|
||||
|
||||
from prompt_toolkit.data_structures import Size
|
||||
from prompt_toolkit.renderer import Output
|
||||
|
||||
from .color_depth import ColorDepth
|
||||
from .vt100 import Vt100_Output
|
||||
from .win32 import Win32Output
|
||||
|
||||
__all__ = [
|
||||
"ConEmuOutput",
|
||||
]
|
||||
|
||||
|
||||
class ConEmuOutput:
|
||||
"""
|
||||
ConEmu (Windows) output abstraction.
|
||||
|
||||
ConEmu is a Windows console application, but it also supports ANSI escape
|
||||
sequences. This output class is actually a proxy to both `Win32Output` and
|
||||
`Vt100_Output`. It uses `Win32Output` for console sizing and scrolling, but
|
||||
all cursor movements and scrolling happens through the `Vt100_Output`.
|
||||
|
||||
This way, we can have 256 colors in ConEmu and Cmder. Rendering will be
|
||||
even a little faster as well.
|
||||
|
||||
http://conemu.github.io/
|
||||
http://gooseberrycreative.com/cmder/
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, stdout: TextIO, default_color_depth: Optional[ColorDepth] = None
|
||||
) -> None:
|
||||
self.win32_output = Win32Output(stdout, default_color_depth=default_color_depth)
|
||||
self.vt100_output = Vt100_Output(
|
||||
stdout, lambda: Size(0, 0), default_color_depth=default_color_depth
|
||||
)
|
||||
|
||||
@property
|
||||
def responds_to_cpr(self) -> bool:
|
||||
return False # We don't need this on Windows.
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
if name in (
|
||||
"get_size",
|
||||
"get_rows_below_cursor_position",
|
||||
"enable_mouse_support",
|
||||
"disable_mouse_support",
|
||||
"scroll_buffer_to_prompt",
|
||||
"get_win32_screen_buffer_info",
|
||||
"enable_bracketed_paste",
|
||||
"disable_bracketed_paste",
|
||||
):
|
||||
return getattr(self.win32_output, name)
|
||||
else:
|
||||
return getattr(self.vt100_output, name)
|
||||
|
||||
|
||||
Output.register(ConEmuOutput)
|
76
venv/Lib/site-packages/prompt_toolkit/output/defaults.py
Normal file
76
venv/Lib/site-packages/prompt_toolkit/output/defaults.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
import sys
|
||||
from typing import Optional, TextIO, cast
|
||||
|
||||
from prompt_toolkit.patch_stdout import StdoutProxy
|
||||
from prompt_toolkit.utils import (
|
||||
get_term_environment_variable,
|
||||
is_conemu_ansi,
|
||||
is_windows,
|
||||
)
|
||||
|
||||
from .base import Output
|
||||
from .color_depth import ColorDepth
|
||||
|
||||
__all__ = [
|
||||
"create_output",
|
||||
]
|
||||
|
||||
|
||||
def create_output(
|
||||
stdout: Optional[TextIO] = None, always_prefer_tty: bool = True
|
||||
) -> Output:
|
||||
"""
|
||||
Return an :class:`~prompt_toolkit.output.Output` instance for the command
|
||||
line.
|
||||
|
||||
:param stdout: The stdout object
|
||||
:param always_prefer_tty: When set, look for `sys.stderr` if `sys.stdout`
|
||||
is not a TTY. (The prompt_toolkit render output is not meant to be
|
||||
consumed by something other then a terminal, so this is a reasonable
|
||||
default.)
|
||||
"""
|
||||
# Consider TERM and PROMPT_TOOLKIT_COLOR_DEPTH environment variables.
|
||||
# Notice that PROMPT_TOOLKIT_COLOR_DEPTH value is the default that's used
|
||||
# if the Application doesn't override it.
|
||||
term_from_env = get_term_environment_variable()
|
||||
color_depth_from_env = ColorDepth.from_env()
|
||||
|
||||
if stdout is None:
|
||||
# By default, render to stdout. If the output is piped somewhere else,
|
||||
# render to stderr.
|
||||
stdout = sys.stdout
|
||||
|
||||
if always_prefer_tty:
|
||||
for io in [sys.stdout, sys.stderr]:
|
||||
if io.isatty():
|
||||
stdout = io
|
||||
break
|
||||
|
||||
# If the patch_stdout context manager has been used, then sys.stdout is
|
||||
# replaced by this proxy. For prompt_toolkit applications, we want to use
|
||||
# the real stdout.
|
||||
while isinstance(stdout, StdoutProxy):
|
||||
stdout = stdout.original_stdout
|
||||
|
||||
if is_windows():
|
||||
from .conemu import ConEmuOutput
|
||||
from .win32 import Win32Output
|
||||
from .windows10 import Windows10_Output, is_win_vt100_enabled
|
||||
|
||||
if is_win_vt100_enabled():
|
||||
return cast(
|
||||
Output,
|
||||
Windows10_Output(stdout, default_color_depth=color_depth_from_env),
|
||||
)
|
||||
if is_conemu_ansi():
|
||||
return cast(
|
||||
Output, ConEmuOutput(stdout, default_color_depth=color_depth_from_env)
|
||||
)
|
||||
else:
|
||||
return Win32Output(stdout, default_color_depth=color_depth_from_env)
|
||||
else:
|
||||
from .vt100 import Vt100_Output
|
||||
|
||||
return Vt100_Output.from_pty(
|
||||
stdout, term=term_from_env, default_color_depth=color_depth_from_env
|
||||
)
|
738
venv/Lib/site-packages/prompt_toolkit/output/vt100.py
Normal file
738
venv/Lib/site-packages/prompt_toolkit/output/vt100.py
Normal file
|
@ -0,0 +1,738 @@
|
|||
"""
|
||||
Output for vt100 terminals.
|
||||
|
||||
A lot of thanks, regarding outputting of colors, goes to the Pygments project:
|
||||
(We don't rely on Pygments anymore, because many things are very custom, and
|
||||
everything has been highly optimized.)
|
||||
http://pygments.org/
|
||||
"""
|
||||
import array
|
||||
import errno
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from typing import (
|
||||
IO,
|
||||
Callable,
|
||||
Dict,
|
||||
Hashable,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Set,
|
||||
TextIO,
|
||||
Tuple,
|
||||
)
|
||||
|
||||
from prompt_toolkit.data_structures import Size
|
||||
from prompt_toolkit.output import Output
|
||||
from prompt_toolkit.styles import ANSI_COLOR_NAMES, Attrs
|
||||
from prompt_toolkit.utils import is_dumb_terminal
|
||||
|
||||
from .color_depth import ColorDepth
|
||||
|
||||
__all__ = [
|
||||
"Vt100_Output",
|
||||
]
|
||||
|
||||
|
||||
FG_ANSI_COLORS = {
|
||||
"ansidefault": 39,
|
||||
# Low intensity.
|
||||
"ansiblack": 30,
|
||||
"ansired": 31,
|
||||
"ansigreen": 32,
|
||||
"ansiyellow": 33,
|
||||
"ansiblue": 34,
|
||||
"ansimagenta": 35,
|
||||
"ansicyan": 36,
|
||||
"ansigray": 37,
|
||||
# High intensity.
|
||||
"ansibrightblack": 90,
|
||||
"ansibrightred": 91,
|
||||
"ansibrightgreen": 92,
|
||||
"ansibrightyellow": 93,
|
||||
"ansibrightblue": 94,
|
||||
"ansibrightmagenta": 95,
|
||||
"ansibrightcyan": 96,
|
||||
"ansiwhite": 97,
|
||||
}
|
||||
|
||||
BG_ANSI_COLORS = {
|
||||
"ansidefault": 49,
|
||||
# Low intensity.
|
||||
"ansiblack": 40,
|
||||
"ansired": 41,
|
||||
"ansigreen": 42,
|
||||
"ansiyellow": 43,
|
||||
"ansiblue": 44,
|
||||
"ansimagenta": 45,
|
||||
"ansicyan": 46,
|
||||
"ansigray": 47,
|
||||
# High intensity.
|
||||
"ansibrightblack": 100,
|
||||
"ansibrightred": 101,
|
||||
"ansibrightgreen": 102,
|
||||
"ansibrightyellow": 103,
|
||||
"ansibrightblue": 104,
|
||||
"ansibrightmagenta": 105,
|
||||
"ansibrightcyan": 106,
|
||||
"ansiwhite": 107,
|
||||
}
|
||||
|
||||
|
||||
ANSI_COLORS_TO_RGB = {
|
||||
"ansidefault": (
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
), # Don't use, 'default' doesn't really have a value.
|
||||
"ansiblack": (0x00, 0x00, 0x00),
|
||||
"ansigray": (0xE5, 0xE5, 0xE5),
|
||||
"ansibrightblack": (0x7F, 0x7F, 0x7F),
|
||||
"ansiwhite": (0xFF, 0xFF, 0xFF),
|
||||
# Low intensity.
|
||||
"ansired": (0xCD, 0x00, 0x00),
|
||||
"ansigreen": (0x00, 0xCD, 0x00),
|
||||
"ansiyellow": (0xCD, 0xCD, 0x00),
|
||||
"ansiblue": (0x00, 0x00, 0xCD),
|
||||
"ansimagenta": (0xCD, 0x00, 0xCD),
|
||||
"ansicyan": (0x00, 0xCD, 0xCD),
|
||||
# High intensity.
|
||||
"ansibrightred": (0xFF, 0x00, 0x00),
|
||||
"ansibrightgreen": (0x00, 0xFF, 0x00),
|
||||
"ansibrightyellow": (0xFF, 0xFF, 0x00),
|
||||
"ansibrightblue": (0x00, 0x00, 0xFF),
|
||||
"ansibrightmagenta": (0xFF, 0x00, 0xFF),
|
||||
"ansibrightcyan": (0x00, 0xFF, 0xFF),
|
||||
}
|
||||
|
||||
|
||||
assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES)
|
||||
assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES)
|
||||
assert set(ANSI_COLORS_TO_RGB) == set(ANSI_COLOR_NAMES)
|
||||
|
||||
|
||||
def _get_closest_ansi_color(r: int, g: int, b: int, exclude: Sequence[str] = ()) -> str:
|
||||
"""
|
||||
Find closest ANSI color. Return it by name.
|
||||
|
||||
:param r: Red (Between 0 and 255.)
|
||||
:param g: Green (Between 0 and 255.)
|
||||
:param b: Blue (Between 0 and 255.)
|
||||
:param exclude: A tuple of color names to exclude. (E.g. ``('ansired', )``.)
|
||||
"""
|
||||
exclude = list(exclude)
|
||||
|
||||
# When we have a bit of saturation, avoid the gray-like colors, otherwise,
|
||||
# too often the distance to the gray color is less.
|
||||
saturation = abs(r - g) + abs(g - b) + abs(b - r) # Between 0..510
|
||||
|
||||
if saturation > 30:
|
||||
exclude.extend(["ansilightgray", "ansidarkgray", "ansiwhite", "ansiblack"])
|
||||
|
||||
# Take the closest color.
|
||||
# (Thanks to Pygments for this part.)
|
||||
distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff)
|
||||
match = "ansidefault"
|
||||
|
||||
for name, (r2, g2, b2) in ANSI_COLORS_TO_RGB.items():
|
||||
if name != "ansidefault" and name not in exclude:
|
||||
d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2
|
||||
|
||||
if d < distance:
|
||||
match = name
|
||||
distance = d
|
||||
|
||||
return match
|
||||
|
||||
|
||||
_ColorCodeAndName = Tuple[int, str]
|
||||
|
||||
|
||||
class _16ColorCache:
|
||||
"""
|
||||
Cache which maps (r, g, b) tuples to 16 ansi colors.
|
||||
|
||||
:param bg: Cache for background colors, instead of foreground.
|
||||
"""
|
||||
|
||||
def __init__(self, bg: bool = False) -> None:
|
||||
self.bg = bg
|
||||
self._cache: Dict[Hashable, _ColorCodeAndName] = {}
|
||||
|
||||
def get_code(
|
||||
self, value: Tuple[int, int, int], exclude: Sequence[str] = ()
|
||||
) -> _ColorCodeAndName:
|
||||
"""
|
||||
Return a (ansi_code, ansi_name) tuple. (E.g. ``(44, 'ansiblue')``.) for
|
||||
a given (r,g,b) value.
|
||||
"""
|
||||
key: Hashable = (value, tuple(exclude))
|
||||
cache = self._cache
|
||||
|
||||
if key not in cache:
|
||||
cache[key] = self._get(value, exclude)
|
||||
|
||||
return cache[key]
|
||||
|
||||
def _get(
|
||||
self, value: Tuple[int, int, int], exclude: Sequence[str] = ()
|
||||
) -> _ColorCodeAndName:
|
||||
|
||||
r, g, b = value
|
||||
match = _get_closest_ansi_color(r, g, b, exclude=exclude)
|
||||
|
||||
# Turn color name into code.
|
||||
if self.bg:
|
||||
code = BG_ANSI_COLORS[match]
|
||||
else:
|
||||
code = FG_ANSI_COLORS[match]
|
||||
|
||||
return code, match
|
||||
|
||||
|
||||
class _256ColorCache(Dict[Tuple[int, int, int], int]):
|
||||
"""
|
||||
Cache which maps (r, g, b) tuples to 256 colors.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
# Build color table.
|
||||
colors: List[Tuple[int, int, int]] = []
|
||||
|
||||
# colors 0..15: 16 basic colors
|
||||
colors.append((0x00, 0x00, 0x00)) # 0
|
||||
colors.append((0xCD, 0x00, 0x00)) # 1
|
||||
colors.append((0x00, 0xCD, 0x00)) # 2
|
||||
colors.append((0xCD, 0xCD, 0x00)) # 3
|
||||
colors.append((0x00, 0x00, 0xEE)) # 4
|
||||
colors.append((0xCD, 0x00, 0xCD)) # 5
|
||||
colors.append((0x00, 0xCD, 0xCD)) # 6
|
||||
colors.append((0xE5, 0xE5, 0xE5)) # 7
|
||||
colors.append((0x7F, 0x7F, 0x7F)) # 8
|
||||
colors.append((0xFF, 0x00, 0x00)) # 9
|
||||
colors.append((0x00, 0xFF, 0x00)) # 10
|
||||
colors.append((0xFF, 0xFF, 0x00)) # 11
|
||||
colors.append((0x5C, 0x5C, 0xFF)) # 12
|
||||
colors.append((0xFF, 0x00, 0xFF)) # 13
|
||||
colors.append((0x00, 0xFF, 0xFF)) # 14
|
||||
colors.append((0xFF, 0xFF, 0xFF)) # 15
|
||||
|
||||
# colors 16..232: the 6x6x6 color cube
|
||||
valuerange = (0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF)
|
||||
|
||||
for i in range(217):
|
||||
r = valuerange[(i // 36) % 6]
|
||||
g = valuerange[(i // 6) % 6]
|
||||
b = valuerange[i % 6]
|
||||
colors.append((r, g, b))
|
||||
|
||||
# colors 233..253: grayscale
|
||||
for i in range(1, 22):
|
||||
v = 8 + i * 10
|
||||
colors.append((v, v, v))
|
||||
|
||||
self.colors = colors
|
||||
|
||||
def __missing__(self, value: Tuple[int, int, int]) -> int:
|
||||
r, g, b = value
|
||||
|
||||
# Find closest color.
|
||||
# (Thanks to Pygments for this!)
|
||||
distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff)
|
||||
match = 0
|
||||
|
||||
for i, (r2, g2, b2) in enumerate(self.colors):
|
||||
if i >= 16: # XXX: We ignore the 16 ANSI colors when mapping RGB
|
||||
# to the 256 colors, because these highly depend on
|
||||
# the color scheme of the terminal.
|
||||
d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2
|
||||
|
||||
if d < distance:
|
||||
match = i
|
||||
distance = d
|
||||
|
||||
# Turn color name into code.
|
||||
self[value] = match
|
||||
return match
|
||||
|
||||
|
||||
_16_fg_colors = _16ColorCache(bg=False)
|
||||
_16_bg_colors = _16ColorCache(bg=True)
|
||||
_256_colors = _256ColorCache()
|
||||
|
||||
|
||||
class _EscapeCodeCache(Dict[Attrs, str]):
|
||||
"""
|
||||
Cache for VT100 escape codes. It maps
|
||||
(fgcolor, bgcolor, bold, underline, reverse) tuples to VT100 escape sequences.
|
||||
|
||||
:param true_color: When True, use 24bit colors instead of 256 colors.
|
||||
"""
|
||||
|
||||
def __init__(self, color_depth: ColorDepth) -> None:
|
||||
self.color_depth = color_depth
|
||||
|
||||
def __missing__(self, attrs: Attrs) -> str:
|
||||
fgcolor, bgcolor, bold, underline, italic, blink, reverse, hidden = attrs
|
||||
parts: List[str] = []
|
||||
|
||||
parts.extend(self._colors_to_code(fgcolor or "", bgcolor or ""))
|
||||
|
||||
if bold:
|
||||
parts.append("1")
|
||||
if italic:
|
||||
parts.append("3")
|
||||
if blink:
|
||||
parts.append("5")
|
||||
if underline:
|
||||
parts.append("4")
|
||||
if reverse:
|
||||
parts.append("7")
|
||||
if hidden:
|
||||
parts.append("8")
|
||||
|
||||
if parts:
|
||||
result = "\x1b[0;" + ";".join(parts) + "m"
|
||||
else:
|
||||
result = "\x1b[0m"
|
||||
|
||||
self[attrs] = result
|
||||
return result
|
||||
|
||||
def _color_name_to_rgb(self, color: str) -> Tuple[int, int, int]:
|
||||
" Turn 'ffffff', into (0xff, 0xff, 0xff). "
|
||||
try:
|
||||
rgb = int(color, 16)
|
||||
except ValueError:
|
||||
raise
|
||||
else:
|
||||
r = (rgb >> 16) & 0xFF
|
||||
g = (rgb >> 8) & 0xFF
|
||||
b = rgb & 0xFF
|
||||
return r, g, b
|
||||
|
||||
def _colors_to_code(self, fg_color: str, bg_color: str) -> Iterable[str]:
|
||||
"""
|
||||
Return a tuple with the vt100 values that represent this color.
|
||||
"""
|
||||
# When requesting ANSI colors only, and both fg/bg color were converted
|
||||
# to ANSI, ensure that the foreground and background color are not the
|
||||
# same. (Unless they were explicitly defined to be the same color.)
|
||||
fg_ansi = ""
|
||||
|
||||
def get(color: str, bg: bool) -> List[int]:
|
||||
nonlocal fg_ansi
|
||||
|
||||
table = BG_ANSI_COLORS if bg else FG_ANSI_COLORS
|
||||
|
||||
if not color or self.color_depth == ColorDepth.DEPTH_1_BIT:
|
||||
return []
|
||||
|
||||
# 16 ANSI colors. (Given by name.)
|
||||
elif color in table:
|
||||
return [table[color]]
|
||||
|
||||
# RGB colors. (Defined as 'ffffff'.)
|
||||
else:
|
||||
try:
|
||||
rgb = self._color_name_to_rgb(color)
|
||||
except ValueError:
|
||||
return []
|
||||
|
||||
# When only 16 colors are supported, use that.
|
||||
if self.color_depth == ColorDepth.DEPTH_4_BIT:
|
||||
if bg: # Background.
|
||||
if fg_color != bg_color:
|
||||
exclude = [fg_ansi]
|
||||
else:
|
||||
exclude = []
|
||||
code, name = _16_bg_colors.get_code(rgb, exclude=exclude)
|
||||
return [code]
|
||||
else: # Foreground.
|
||||
code, name = _16_fg_colors.get_code(rgb)
|
||||
fg_ansi = name
|
||||
return [code]
|
||||
|
||||
# True colors. (Only when this feature is enabled.)
|
||||
elif self.color_depth == ColorDepth.DEPTH_24_BIT:
|
||||
r, g, b = rgb
|
||||
return [(48 if bg else 38), 2, r, g, b]
|
||||
|
||||
# 256 RGB colors.
|
||||
else:
|
||||
return [(48 if bg else 38), 5, _256_colors[rgb]]
|
||||
|
||||
result: List[int] = []
|
||||
result.extend(get(fg_color, False))
|
||||
result.extend(get(bg_color, True))
|
||||
|
||||
return map(str, result)
|
||||
|
||||
|
||||
def _get_size(fileno: int) -> Tuple[int, int]:
|
||||
# Thanks to fabric (fabfile.org), and
|
||||
# http://sqizit.bartletts.id.au/2011/02/14/pseudo-terminals-in-python/
|
||||
"""
|
||||
Get the size of this pseudo terminal.
|
||||
|
||||
:param fileno: stdout.fileno()
|
||||
:returns: A (rows, cols) tuple.
|
||||
"""
|
||||
# Inline imports, because these modules are not available on Windows.
|
||||
# (This file is used by ConEmuOutput, which is used on Windows.)
|
||||
import fcntl
|
||||
import termios
|
||||
|
||||
# Buffer for the C call
|
||||
buf = array.array("h", [0, 0, 0, 0])
|
||||
|
||||
# Do TIOCGWINSZ (Get)
|
||||
# Note: We should not pass 'True' as a fourth parameter to 'ioctl'. (True
|
||||
# is the default.) This causes segmentation faults on some systems.
|
||||
# See: https://github.com/jonathanslenders/python-prompt-toolkit/pull/364
|
||||
fcntl.ioctl(fileno, termios.TIOCGWINSZ, buf) # type: ignore
|
||||
|
||||
# Return rows, cols
|
||||
return buf[0], buf[1]
|
||||
|
||||
|
||||
class Vt100_Output(Output):
|
||||
"""
|
||||
:param get_size: A callable which returns the `Size` of the output terminal.
|
||||
:param stdout: Any object with has a `write` and `flush` method + an 'encoding' property.
|
||||
:param term: The terminal environment variable. (xterm, xterm-256color, linux, ...)
|
||||
:param write_binary: Encode the output before writing it. If `True` (the
|
||||
default), the `stdout` object is supposed to expose an `encoding` attribute.
|
||||
"""
|
||||
|
||||
# For the error messages. Only display "Output is not a terminal" once per
|
||||
# file descriptor.
|
||||
_fds_not_a_terminal: Set[int] = set()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
stdout: TextIO,
|
||||
get_size: Callable[[], Size],
|
||||
term: Optional[str] = None,
|
||||
write_binary: bool = True,
|
||||
default_color_depth: Optional[ColorDepth] = None,
|
||||
) -> None:
|
||||
|
||||
assert all(hasattr(stdout, a) for a in ("write", "flush"))
|
||||
|
||||
if write_binary:
|
||||
assert hasattr(stdout, "encoding")
|
||||
|
||||
self._buffer: List[str] = []
|
||||
self.stdout = stdout
|
||||
self.write_binary = write_binary
|
||||
self.default_color_depth = default_color_depth
|
||||
self._get_size = get_size
|
||||
self.term = term
|
||||
|
||||
# Cache for escape codes.
|
||||
self._escape_code_caches: Dict[ColorDepth, _EscapeCodeCache] = {
|
||||
ColorDepth.DEPTH_1_BIT: _EscapeCodeCache(ColorDepth.DEPTH_1_BIT),
|
||||
ColorDepth.DEPTH_4_BIT: _EscapeCodeCache(ColorDepth.DEPTH_4_BIT),
|
||||
ColorDepth.DEPTH_8_BIT: _EscapeCodeCache(ColorDepth.DEPTH_8_BIT),
|
||||
ColorDepth.DEPTH_24_BIT: _EscapeCodeCache(ColorDepth.DEPTH_24_BIT),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_pty(
|
||||
cls,
|
||||
stdout: TextIO,
|
||||
term: Optional[str] = None,
|
||||
default_color_depth: Optional[ColorDepth] = None,
|
||||
) -> "Vt100_Output":
|
||||
"""
|
||||
Create an Output class from a pseudo terminal.
|
||||
(This will take the dimensions by reading the pseudo
|
||||
terminal attributes.)
|
||||
"""
|
||||
fd: Optional[int]
|
||||
# Normally, this requires a real TTY device, but people instantiate
|
||||
# this class often during unit tests as well. For convenience, we print
|
||||
# an error message, use standard dimensions, and go on.
|
||||
try:
|
||||
fd = stdout.fileno()
|
||||
except io.UnsupportedOperation:
|
||||
fd = None
|
||||
|
||||
if not stdout.isatty() and (fd is None or fd not in cls._fds_not_a_terminal):
|
||||
msg = "Warning: Output is not a terminal (fd=%r).\n"
|
||||
sys.stderr.write(msg % fd)
|
||||
sys.stderr.flush()
|
||||
if fd is not None:
|
||||
cls._fds_not_a_terminal.add(fd)
|
||||
|
||||
def get_size() -> Size:
|
||||
# If terminal (incorrectly) reports its size as 0, pick a
|
||||
# reasonable default. See
|
||||
# https://github.com/ipython/ipython/issues/10071
|
||||
rows, columns = (None, None)
|
||||
|
||||
# It is possible that `stdout` is no longer a TTY device at this
|
||||
# point. In that case we get an `OSError` in the ioctl call in
|
||||
# `get_size`. See:
|
||||
# https://github.com/prompt-toolkit/python-prompt-toolkit/pull/1021
|
||||
try:
|
||||
rows, columns = _get_size(stdout.fileno())
|
||||
except OSError:
|
||||
pass
|
||||
return Size(rows=rows or 24, columns=columns or 80)
|
||||
|
||||
return cls(stdout, get_size, term=term, default_color_depth=default_color_depth)
|
||||
|
||||
def get_size(self) -> Size:
|
||||
return self._get_size()
|
||||
|
||||
def fileno(self) -> int:
|
||||
" Return file descriptor. "
|
||||
return self.stdout.fileno()
|
||||
|
||||
def encoding(self) -> str:
|
||||
" Return encoding used for stdout. "
|
||||
return self.stdout.encoding
|
||||
|
||||
def write_raw(self, data: str) -> None:
|
||||
"""
|
||||
Write raw data to output.
|
||||
"""
|
||||
self._buffer.append(data)
|
||||
|
||||
def write(self, data: str) -> None:
|
||||
"""
|
||||
Write text to output.
|
||||
(Removes vt100 escape codes. -- used for safely writing text.)
|
||||
"""
|
||||
self._buffer.append(data.replace("\x1b", "?"))
|
||||
|
||||
def set_title(self, title: str) -> None:
|
||||
"""
|
||||
Set terminal title.
|
||||
"""
|
||||
if self.term not in (
|
||||
"linux",
|
||||
"eterm-color",
|
||||
): # Not supported by the Linux console.
|
||||
self.write_raw(
|
||||
"\x1b]2;%s\x07" % title.replace("\x1b", "").replace("\x07", "")
|
||||
)
|
||||
|
||||
def clear_title(self) -> None:
|
||||
self.set_title("")
|
||||
|
||||
def erase_screen(self) -> None:
|
||||
"""
|
||||
Erases the screen with the background colour and moves the cursor to
|
||||
home.
|
||||
"""
|
||||
self.write_raw("\x1b[2J")
|
||||
|
||||
def enter_alternate_screen(self) -> None:
|
||||
self.write_raw("\x1b[?1049h\x1b[H")
|
||||
|
||||
def quit_alternate_screen(self) -> None:
|
||||
self.write_raw("\x1b[?1049l")
|
||||
|
||||
def enable_mouse_support(self) -> None:
|
||||
self.write_raw("\x1b[?1000h")
|
||||
|
||||
# Enable urxvt Mouse mode. (For terminals that understand this.)
|
||||
self.write_raw("\x1b[?1015h")
|
||||
|
||||
# Also enable Xterm SGR mouse mode. (For terminals that understand this.)
|
||||
self.write_raw("\x1b[?1006h")
|
||||
|
||||
# Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr
|
||||
# extensions.
|
||||
|
||||
def disable_mouse_support(self) -> None:
|
||||
self.write_raw("\x1b[?1000l")
|
||||
self.write_raw("\x1b[?1015l")
|
||||
self.write_raw("\x1b[?1006l")
|
||||
|
||||
def erase_end_of_line(self) -> None:
|
||||
"""
|
||||
Erases from the current cursor position to the end of the current line.
|
||||
"""
|
||||
self.write_raw("\x1b[K")
|
||||
|
||||
def erase_down(self) -> None:
|
||||
"""
|
||||
Erases the screen from the current line down to the bottom of the
|
||||
screen.
|
||||
"""
|
||||
self.write_raw("\x1b[J")
|
||||
|
||||
def reset_attributes(self) -> None:
|
||||
self.write_raw("\x1b[0m")
|
||||
|
||||
def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None:
|
||||
"""
|
||||
Create new style and output.
|
||||
|
||||
:param attrs: `Attrs` instance.
|
||||
"""
|
||||
# Get current depth.
|
||||
escape_code_cache = self._escape_code_caches[color_depth]
|
||||
|
||||
# Write escape character.
|
||||
self.write_raw(escape_code_cache[attrs])
|
||||
|
||||
def disable_autowrap(self) -> None:
|
||||
self.write_raw("\x1b[?7l")
|
||||
|
||||
def enable_autowrap(self) -> None:
|
||||
self.write_raw("\x1b[?7h")
|
||||
|
||||
def enable_bracketed_paste(self) -> None:
|
||||
self.write_raw("\x1b[?2004h")
|
||||
|
||||
def disable_bracketed_paste(self) -> None:
|
||||
self.write_raw("\x1b[?2004l")
|
||||
|
||||
def cursor_goto(self, row: int = 0, column: int = 0) -> None:
|
||||
"""
|
||||
Move cursor position.
|
||||
"""
|
||||
self.write_raw("\x1b[%i;%iH" % (row, column))
|
||||
|
||||
def cursor_up(self, amount: int) -> None:
|
||||
if amount == 0:
|
||||
pass
|
||||
elif amount == 1:
|
||||
self.write_raw("\x1b[A")
|
||||
else:
|
||||
self.write_raw("\x1b[%iA" % amount)
|
||||
|
||||
def cursor_down(self, amount: int) -> None:
|
||||
if amount == 0:
|
||||
pass
|
||||
elif amount == 1:
|
||||
# Note: Not the same as '\n', '\n' can cause the window content to
|
||||
# scroll.
|
||||
self.write_raw("\x1b[B")
|
||||
else:
|
||||
self.write_raw("\x1b[%iB" % amount)
|
||||
|
||||
def cursor_forward(self, amount: int) -> None:
|
||||
if amount == 0:
|
||||
pass
|
||||
elif amount == 1:
|
||||
self.write_raw("\x1b[C")
|
||||
else:
|
||||
self.write_raw("\x1b[%iC" % amount)
|
||||
|
||||
def cursor_backward(self, amount: int) -> None:
|
||||
if amount == 0:
|
||||
pass
|
||||
elif amount == 1:
|
||||
self.write_raw("\b") # '\x1b[D'
|
||||
else:
|
||||
self.write_raw("\x1b[%iD" % amount)
|
||||
|
||||
def hide_cursor(self) -> None:
|
||||
self.write_raw("\x1b[?25l")
|
||||
|
||||
def show_cursor(self) -> None:
|
||||
self.write_raw("\x1b[?12l\x1b[?25h") # Stop blinking cursor and show.
|
||||
|
||||
def flush(self) -> None:
|
||||
"""
|
||||
Write to output stream and flush.
|
||||
"""
|
||||
if not self._buffer:
|
||||
return
|
||||
|
||||
data = "".join(self._buffer)
|
||||
|
||||
try:
|
||||
# (We try to encode ourself, because that way we can replace
|
||||
# characters that don't exist in the character set, avoiding
|
||||
# UnicodeEncodeError crashes. E.g. u'\xb7' does not appear in 'ascii'.)
|
||||
# My Arch Linux installation of july 2015 reported 'ANSI_X3.4-1968'
|
||||
# for sys.stdout.encoding in xterm.
|
||||
out: IO
|
||||
if self.write_binary:
|
||||
if hasattr(self.stdout, "buffer"):
|
||||
out = self.stdout.buffer # Py3.
|
||||
else:
|
||||
out = self.stdout
|
||||
out.write(data.encode(self.stdout.encoding or "utf-8", "replace"))
|
||||
else:
|
||||
self.stdout.write(data)
|
||||
|
||||
self.stdout.flush()
|
||||
except IOError as e:
|
||||
if e.args and e.args[0] == errno.EINTR:
|
||||
# Interrupted system call. Can happen in case of a window
|
||||
# resize signal. (Just ignore. The resize handler will render
|
||||
# again anyway.)
|
||||
pass
|
||||
elif e.args and e.args[0] == 0:
|
||||
# This can happen when there is a lot of output and the user
|
||||
# sends a KeyboardInterrupt by pressing Control-C. E.g. in
|
||||
# a Python REPL when we execute "while True: print('test')".
|
||||
# (The `ptpython` REPL uses this `Output` class instead of
|
||||
# `stdout` directly -- in order to be network transparent.)
|
||||
# So, just ignore.
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
self._buffer = []
|
||||
|
||||
def ask_for_cpr(self) -> None:
|
||||
"""
|
||||
Asks for a cursor position report (CPR).
|
||||
"""
|
||||
self.write_raw("\x1b[6n")
|
||||
self.flush()
|
||||
|
||||
@property
|
||||
def responds_to_cpr(self) -> bool:
|
||||
# When the input is a tty, we assume that CPR is supported.
|
||||
# It's not when the input is piped from Pexpect.
|
||||
if os.environ.get("PROMPT_TOOLKIT_NO_CPR", "") == "1":
|
||||
return False
|
||||
|
||||
if is_dumb_terminal(self.term):
|
||||
return False
|
||||
try:
|
||||
return self.stdout.isatty()
|
||||
except ValueError:
|
||||
return False # ValueError: I/O operation on closed file
|
||||
|
||||
def bell(self) -> None:
|
||||
" Sound bell. "
|
||||
self.write_raw("\a")
|
||||
self.flush()
|
||||
|
||||
def get_default_color_depth(self) -> ColorDepth:
|
||||
"""
|
||||
Return the default color depth for a vt100 terminal, according to the
|
||||
our term value.
|
||||
|
||||
We prefer 256 colors almost always, because this is what most terminals
|
||||
support these days, and is a good default.
|
||||
"""
|
||||
if self.default_color_depth is not None:
|
||||
return self.default_color_depth
|
||||
|
||||
term = self.term
|
||||
|
||||
if term is None:
|
||||
return ColorDepth.DEFAULT
|
||||
|
||||
if is_dumb_terminal(term):
|
||||
return ColorDepth.DEPTH_1_BIT
|
||||
|
||||
if term in ("linux", "eterm-color"):
|
||||
return ColorDepth.DEPTH_4_BIT
|
||||
|
||||
return ColorDepth.DEFAULT
|
656
venv/Lib/site-packages/prompt_toolkit/output/win32.py
Normal file
656
venv/Lib/site-packages/prompt_toolkit/output/win32.py
Normal file
|
@ -0,0 +1,656 @@
|
|||
import os
|
||||
from ctypes import (
|
||||
ArgumentError,
|
||||
byref,
|
||||
c_char,
|
||||
c_long,
|
||||
c_uint,
|
||||
c_ulong,
|
||||
pointer,
|
||||
windll,
|
||||
)
|
||||
from ctypes.wintypes import DWORD, HANDLE
|
||||
from typing import Dict, List, Optional, TextIO, Tuple
|
||||
|
||||
from prompt_toolkit.data_structures import Size
|
||||
from prompt_toolkit.renderer import Output
|
||||
from prompt_toolkit.styles import ANSI_COLOR_NAMES, Attrs
|
||||
from prompt_toolkit.utils import get_cwidth
|
||||
from prompt_toolkit.win32_types import (
|
||||
CONSOLE_SCREEN_BUFFER_INFO,
|
||||
COORD,
|
||||
SMALL_RECT,
|
||||
STD_INPUT_HANDLE,
|
||||
STD_OUTPUT_HANDLE,
|
||||
)
|
||||
|
||||
from .color_depth import ColorDepth
|
||||
|
||||
__all__ = [
|
||||
"Win32Output",
|
||||
]
|
||||
|
||||
|
||||
def _coord_byval(coord):
|
||||
"""
|
||||
Turns a COORD object into a c_long.
|
||||
This will cause it to be passed by value instead of by reference. (That is what I think at least.)
|
||||
|
||||
When running ``ptipython`` is run (only with IPython), we often got the following error::
|
||||
|
||||
Error in 'SetConsoleCursorPosition'.
|
||||
ArgumentError("argument 2: <class 'TypeError'>: wrong type",)
|
||||
argument 2: <class 'TypeError'>: wrong type
|
||||
|
||||
It was solved by turning ``COORD`` parameters into a ``c_long`` like this.
|
||||
|
||||
More info: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx
|
||||
"""
|
||||
return c_long(coord.Y * 0x10000 | coord.X & 0xFFFF)
|
||||
|
||||
|
||||
#: If True: write the output of the renderer also to the following file. This
|
||||
#: is very useful for debugging. (e.g.: to see that we don't write more bytes
|
||||
#: than required.)
|
||||
_DEBUG_RENDER_OUTPUT = False
|
||||
_DEBUG_RENDER_OUTPUT_FILENAME = r"prompt-toolkit-windows-output.log"
|
||||
|
||||
|
||||
class NoConsoleScreenBufferError(Exception):
|
||||
"""
|
||||
Raised when the application is not running inside a Windows Console, but
|
||||
the user tries to instantiate Win32Output.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
# Are we running in 'xterm' on Windows, like git-bash for instance?
|
||||
xterm = "xterm" in os.environ.get("TERM", "")
|
||||
|
||||
if xterm:
|
||||
message = (
|
||||
"Found %s, while expecting a Windows console. "
|
||||
'Maybe try to run this program using "winpty" '
|
||||
"or run it in cmd.exe instead. Or otherwise, "
|
||||
"in case of Cygwin, use the Python executable "
|
||||
"that is compiled for Cygwin." % os.environ["TERM"]
|
||||
)
|
||||
else:
|
||||
message = "No Windows console found. Are you running cmd.exe?"
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class Win32Output(Output):
|
||||
"""
|
||||
I/O abstraction for rendering to Windows consoles.
|
||||
(cmd.exe and similar.)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
stdout: TextIO,
|
||||
use_complete_width: bool = False,
|
||||
default_color_depth: Optional[ColorDepth] = None,
|
||||
) -> None:
|
||||
self.use_complete_width = use_complete_width
|
||||
self.default_color_depth = default_color_depth
|
||||
|
||||
self._buffer: List[str] = []
|
||||
self.stdout = stdout
|
||||
self.hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE))
|
||||
|
||||
self._in_alternate_screen = False
|
||||
self._hidden = False
|
||||
|
||||
self.color_lookup_table = ColorLookupTable()
|
||||
|
||||
# Remember the default console colors.
|
||||
info = self.get_win32_screen_buffer_info()
|
||||
self.default_attrs = info.wAttributes if info else 15
|
||||
|
||||
if _DEBUG_RENDER_OUTPUT:
|
||||
self.LOG = open(_DEBUG_RENDER_OUTPUT_FILENAME, "ab")
|
||||
|
||||
def fileno(self) -> int:
|
||||
" Return file descriptor. "
|
||||
return self.stdout.fileno()
|
||||
|
||||
def encoding(self) -> str:
|
||||
" Return encoding used for stdout. "
|
||||
return self.stdout.encoding
|
||||
|
||||
def write(self, data: str) -> None:
|
||||
if self._hidden:
|
||||
data = " " * get_cwidth(data)
|
||||
|
||||
self._buffer.append(data)
|
||||
|
||||
def write_raw(self, data: str) -> None:
|
||||
" For win32, there is no difference between write and write_raw. "
|
||||
self.write(data)
|
||||
|
||||
def get_size(self) -> Size:
|
||||
info = self.get_win32_screen_buffer_info()
|
||||
|
||||
# We take the width of the *visible* region as the size. Not the width
|
||||
# of the complete screen buffer. (Unless use_complete_width has been
|
||||
# set.)
|
||||
if self.use_complete_width:
|
||||
width = info.dwSize.X
|
||||
else:
|
||||
width = info.srWindow.Right - info.srWindow.Left
|
||||
|
||||
height = info.srWindow.Bottom - info.srWindow.Top + 1
|
||||
|
||||
# We avoid the right margin, windows will wrap otherwise.
|
||||
maxwidth = info.dwSize.X - 1
|
||||
width = min(maxwidth, width)
|
||||
|
||||
# Create `Size` object.
|
||||
return Size(rows=height, columns=width)
|
||||
|
||||
def _winapi(self, func, *a, **kw):
|
||||
"""
|
||||
Flush and call win API function.
|
||||
"""
|
||||
self.flush()
|
||||
|
||||
if _DEBUG_RENDER_OUTPUT:
|
||||
self.LOG.write(("%r" % func.__name__).encode("utf-8") + b"\n")
|
||||
self.LOG.write(
|
||||
b" " + ", ".join(["%r" % i for i in a]).encode("utf-8") + b"\n"
|
||||
)
|
||||
self.LOG.write(
|
||||
b" "
|
||||
+ ", ".join(["%r" % type(i) for i in a]).encode("utf-8")
|
||||
+ b"\n"
|
||||
)
|
||||
self.LOG.flush()
|
||||
|
||||
try:
|
||||
return func(*a, **kw)
|
||||
except ArgumentError as e:
|
||||
if _DEBUG_RENDER_OUTPUT:
|
||||
self.LOG.write(
|
||||
(" Error in %r %r %s\n" % (func.__name__, e, e)).encode("utf-8")
|
||||
)
|
||||
|
||||
def get_win32_screen_buffer_info(self):
|
||||
"""
|
||||
Return Screen buffer info.
|
||||
"""
|
||||
# NOTE: We don't call the `GetConsoleScreenBufferInfo` API through
|
||||
# `self._winapi`. Doing so causes Python to crash on certain 64bit
|
||||
# Python versions. (Reproduced with 64bit Python 2.7.6, on Windows
|
||||
# 10). It is not clear why. Possibly, it has to do with passing
|
||||
# these objects as an argument, or through *args.
|
||||
|
||||
# The Python documentation contains the following - possibly related - warning:
|
||||
# ctypes does not support passing unions or structures with
|
||||
# bit-fields to functions by value. While this may work on 32-bit
|
||||
# x86, it's not guaranteed by the library to work in the general
|
||||
# case. Unions and structures with bit-fields should always be
|
||||
# passed to functions by pointer.
|
||||
|
||||
# Also see:
|
||||
# - https://github.com/ipython/ipython/issues/10070
|
||||
# - https://github.com/jonathanslenders/python-prompt-toolkit/issues/406
|
||||
# - https://github.com/jonathanslenders/python-prompt-toolkit/issues/86
|
||||
|
||||
self.flush()
|
||||
sbinfo = CONSOLE_SCREEN_BUFFER_INFO()
|
||||
success = windll.kernel32.GetConsoleScreenBufferInfo(
|
||||
self.hconsole, byref(sbinfo)
|
||||
)
|
||||
|
||||
# success = self._winapi(windll.kernel32.GetConsoleScreenBufferInfo,
|
||||
# self.hconsole, byref(sbinfo))
|
||||
|
||||
if success:
|
||||
return sbinfo
|
||||
else:
|
||||
raise NoConsoleScreenBufferError
|
||||
|
||||
def set_title(self, title: str) -> None:
|
||||
"""
|
||||
Set terminal title.
|
||||
"""
|
||||
self._winapi(windll.kernel32.SetConsoleTitleW, title)
|
||||
|
||||
def clear_title(self) -> None:
|
||||
self._winapi(windll.kernel32.SetConsoleTitleW, "")
|
||||
|
||||
def erase_screen(self) -> None:
|
||||
start = COORD(0, 0)
|
||||
sbinfo = self.get_win32_screen_buffer_info()
|
||||
length = sbinfo.dwSize.X * sbinfo.dwSize.Y
|
||||
|
||||
self.cursor_goto(row=0, column=0)
|
||||
self._erase(start, length)
|
||||
|
||||
def erase_down(self) -> None:
|
||||
sbinfo = self.get_win32_screen_buffer_info()
|
||||
size = sbinfo.dwSize
|
||||
|
||||
start = sbinfo.dwCursorPosition
|
||||
length = (size.X - size.X) + size.X * (size.Y - sbinfo.dwCursorPosition.Y)
|
||||
|
||||
self._erase(start, length)
|
||||
|
||||
def erase_end_of_line(self) -> None:
|
||||
""""""
|
||||
sbinfo = self.get_win32_screen_buffer_info()
|
||||
start = sbinfo.dwCursorPosition
|
||||
length = sbinfo.dwSize.X - sbinfo.dwCursorPosition.X
|
||||
|
||||
self._erase(start, length)
|
||||
|
||||
def _erase(self, start, length):
|
||||
chars_written = c_ulong()
|
||||
|
||||
self._winapi(
|
||||
windll.kernel32.FillConsoleOutputCharacterA,
|
||||
self.hconsole,
|
||||
c_char(b" "),
|
||||
DWORD(length),
|
||||
_coord_byval(start),
|
||||
byref(chars_written),
|
||||
)
|
||||
|
||||
# Reset attributes.
|
||||
sbinfo = self.get_win32_screen_buffer_info()
|
||||
self._winapi(
|
||||
windll.kernel32.FillConsoleOutputAttribute,
|
||||
self.hconsole,
|
||||
sbinfo.wAttributes,
|
||||
length,
|
||||
_coord_byval(start),
|
||||
byref(chars_written),
|
||||
)
|
||||
|
||||
def reset_attributes(self) -> None:
|
||||
" Reset the console foreground/background color. "
|
||||
self._winapi(
|
||||
windll.kernel32.SetConsoleTextAttribute, self.hconsole, self.default_attrs
|
||||
)
|
||||
self._hidden = False
|
||||
|
||||
def set_attributes(self, attrs: Attrs, color_depth: ColorDepth) -> None:
|
||||
fgcolor, bgcolor, bold, underline, italic, blink, reverse, hidden = attrs
|
||||
self._hidden = bool(hidden)
|
||||
|
||||
# Start from the default attributes.
|
||||
win_attrs: int = self.default_attrs
|
||||
|
||||
if color_depth != ColorDepth.DEPTH_1_BIT:
|
||||
# Override the last four bits: foreground color.
|
||||
if fgcolor:
|
||||
win_attrs = win_attrs & ~0xF
|
||||
win_attrs |= self.color_lookup_table.lookup_fg_color(fgcolor)
|
||||
|
||||
# Override the next four bits: background color.
|
||||
if bgcolor:
|
||||
win_attrs = win_attrs & ~0xF0
|
||||
win_attrs |= self.color_lookup_table.lookup_bg_color(bgcolor)
|
||||
|
||||
# Reverse: swap these four bits groups.
|
||||
if reverse:
|
||||
win_attrs = (
|
||||
(win_attrs & ~0xFF)
|
||||
| ((win_attrs & 0xF) << 4)
|
||||
| ((win_attrs & 0xF0) >> 4)
|
||||
)
|
||||
|
||||
self._winapi(windll.kernel32.SetConsoleTextAttribute, self.hconsole, win_attrs)
|
||||
|
||||
def disable_autowrap(self) -> None:
|
||||
# Not supported by Windows.
|
||||
pass
|
||||
|
||||
def enable_autowrap(self) -> None:
|
||||
# Not supported by Windows.
|
||||
pass
|
||||
|
||||
def cursor_goto(self, row: int = 0, column: int = 0) -> None:
|
||||
pos = COORD(x=column, y=row)
|
||||
self._winapi(
|
||||
windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos)
|
||||
)
|
||||
|
||||
def cursor_up(self, amount: int) -> None:
|
||||
sr = self.get_win32_screen_buffer_info().dwCursorPosition
|
||||
pos = COORD(sr.X, sr.Y - amount)
|
||||
self._winapi(
|
||||
windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos)
|
||||
)
|
||||
|
||||
def cursor_down(self, amount: int) -> None:
|
||||
self.cursor_up(-amount)
|
||||
|
||||
def cursor_forward(self, amount: int) -> None:
|
||||
sr = self.get_win32_screen_buffer_info().dwCursorPosition
|
||||
# assert sr.X + amount >= 0, 'Negative cursor position: x=%r amount=%r' % (sr.X, amount)
|
||||
|
||||
pos = COORD(max(0, sr.X + amount), sr.Y)
|
||||
self._winapi(
|
||||
windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos)
|
||||
)
|
||||
|
||||
def cursor_backward(self, amount: int) -> None:
|
||||
self.cursor_forward(-amount)
|
||||
|
||||
def flush(self) -> None:
|
||||
"""
|
||||
Write to output stream and flush.
|
||||
"""
|
||||
if not self._buffer:
|
||||
# Only flush stdout buffer. (It could be that Python still has
|
||||
# something in its buffer. -- We want to be sure to print that in
|
||||
# the correct color.)
|
||||
self.stdout.flush()
|
||||
return
|
||||
|
||||
data = "".join(self._buffer)
|
||||
|
||||
if _DEBUG_RENDER_OUTPUT:
|
||||
self.LOG.write(("%r" % data).encode("utf-8") + b"\n")
|
||||
self.LOG.flush()
|
||||
|
||||
# Print characters one by one. This appears to be the best solution
|
||||
# in oder to avoid traces of vertical lines when the completion
|
||||
# menu disappears.
|
||||
for b in data:
|
||||
written = DWORD()
|
||||
|
||||
retval = windll.kernel32.WriteConsoleW(
|
||||
self.hconsole, b, 1, byref(written), None
|
||||
)
|
||||
assert retval != 0
|
||||
|
||||
self._buffer = []
|
||||
|
||||
def get_rows_below_cursor_position(self) -> int:
|
||||
info = self.get_win32_screen_buffer_info()
|
||||
return info.srWindow.Bottom - info.dwCursorPosition.Y + 1
|
||||
|
||||
def scroll_buffer_to_prompt(self) -> None:
|
||||
"""
|
||||
To be called before drawing the prompt. This should scroll the console
|
||||
to left, with the cursor at the bottom (if possible).
|
||||
"""
|
||||
# Get current window size
|
||||
info = self.get_win32_screen_buffer_info()
|
||||
sr = info.srWindow
|
||||
cursor_pos = info.dwCursorPosition
|
||||
|
||||
result = SMALL_RECT()
|
||||
|
||||
# Scroll to the left.
|
||||
result.Left = 0
|
||||
result.Right = sr.Right - sr.Left
|
||||
|
||||
# Scroll vertical
|
||||
win_height = sr.Bottom - sr.Top
|
||||
if 0 < sr.Bottom - cursor_pos.Y < win_height - 1:
|
||||
# no vertical scroll if cursor already on the screen
|
||||
result.Bottom = sr.Bottom
|
||||
else:
|
||||
result.Bottom = max(win_height, cursor_pos.Y)
|
||||
result.Top = result.Bottom - win_height
|
||||
|
||||
# Scroll API
|
||||
self._winapi(
|
||||
windll.kernel32.SetConsoleWindowInfo, self.hconsole, True, byref(result)
|
||||
)
|
||||
|
||||
def enter_alternate_screen(self) -> None:
|
||||
"""
|
||||
Go to alternate screen buffer.
|
||||
"""
|
||||
if not self._in_alternate_screen:
|
||||
GENERIC_READ = 0x80000000
|
||||
GENERIC_WRITE = 0x40000000
|
||||
|
||||
# Create a new console buffer and activate that one.
|
||||
handle = HANDLE(
|
||||
self._winapi(
|
||||
windll.kernel32.CreateConsoleScreenBuffer,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
DWORD(0),
|
||||
None,
|
||||
DWORD(1),
|
||||
None,
|
||||
)
|
||||
)
|
||||
|
||||
self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, handle)
|
||||
self.hconsole = handle
|
||||
self._in_alternate_screen = True
|
||||
|
||||
def quit_alternate_screen(self) -> None:
|
||||
"""
|
||||
Make stdout again the active buffer.
|
||||
"""
|
||||
if self._in_alternate_screen:
|
||||
stdout = HANDLE(
|
||||
self._winapi(windll.kernel32.GetStdHandle, STD_OUTPUT_HANDLE)
|
||||
)
|
||||
self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, stdout)
|
||||
self._winapi(windll.kernel32.CloseHandle, self.hconsole)
|
||||
self.hconsole = stdout
|
||||
self._in_alternate_screen = False
|
||||
|
||||
def enable_mouse_support(self) -> None:
|
||||
ENABLE_MOUSE_INPUT = 0x10
|
||||
handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE))
|
||||
|
||||
original_mode = DWORD()
|
||||
self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode))
|
||||
self._winapi(
|
||||
windll.kernel32.SetConsoleMode,
|
||||
handle,
|
||||
original_mode.value | ENABLE_MOUSE_INPUT,
|
||||
)
|
||||
|
||||
def disable_mouse_support(self) -> None:
|
||||
ENABLE_MOUSE_INPUT = 0x10
|
||||
handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE))
|
||||
|
||||
original_mode = DWORD()
|
||||
self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode))
|
||||
self._winapi(
|
||||
windll.kernel32.SetConsoleMode,
|
||||
handle,
|
||||
original_mode.value & ~ENABLE_MOUSE_INPUT,
|
||||
)
|
||||
|
||||
def hide_cursor(self) -> None:
|
||||
pass
|
||||
|
||||
def show_cursor(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def win32_refresh_window(cls) -> None:
|
||||
"""
|
||||
Call win32 API to refresh the whole Window.
|
||||
|
||||
This is sometimes necessary when the application paints background
|
||||
for completion menus. When the menu disappears, it leaves traces due
|
||||
to a bug in the Windows Console. Sending a repaint request solves it.
|
||||
"""
|
||||
# Get console handle
|
||||
handle = HANDLE(windll.kernel32.GetConsoleWindow())
|
||||
|
||||
RDW_INVALIDATE = 0x0001
|
||||
windll.user32.RedrawWindow(handle, None, None, c_uint(RDW_INVALIDATE))
|
||||
|
||||
def get_default_color_depth(self) -> ColorDepth:
|
||||
"""
|
||||
Return the default color depth for a windows terminal.
|
||||
|
||||
Contrary to the Vt100 implementation, this doesn't depend on a $TERM
|
||||
variable.
|
||||
"""
|
||||
if self.default_color_depth is not None:
|
||||
return self.default_color_depth
|
||||
|
||||
# For now, by default, always use 4 bit color on Windows 10 by default,
|
||||
# even when vt100 escape sequences with
|
||||
# ENABLE_VIRTUAL_TERMINAL_PROCESSING are supported. We don't have a
|
||||
# reliable way yet to know whether our console supports true color or
|
||||
# only 4-bit.
|
||||
return ColorDepth.DEPTH_4_BIT
|
||||
|
||||
|
||||
class FOREGROUND_COLOR:
|
||||
BLACK = 0x0000
|
||||
BLUE = 0x0001
|
||||
GREEN = 0x0002
|
||||
CYAN = 0x0003
|
||||
RED = 0x0004
|
||||
MAGENTA = 0x0005
|
||||
YELLOW = 0x0006
|
||||
GRAY = 0x0007
|
||||
INTENSITY = 0x0008 # Foreground color is intensified.
|
||||
|
||||
|
||||
class BACKGROUND_COLOR:
|
||||
BLACK = 0x0000
|
||||
BLUE = 0x0010
|
||||
GREEN = 0x0020
|
||||
CYAN = 0x0030
|
||||
RED = 0x0040
|
||||
MAGENTA = 0x0050
|
||||
YELLOW = 0x0060
|
||||
GRAY = 0x0070
|
||||
INTENSITY = 0x0080 # Background color is intensified.
|
||||
|
||||
|
||||
def _create_ansi_color_dict(color_cls) -> Dict[str, int]:
|
||||
" Create a table that maps the 16 named ansi colors to their Windows code. "
|
||||
return {
|
||||
"ansidefault": color_cls.BLACK,
|
||||
"ansiblack": color_cls.BLACK,
|
||||
"ansigray": color_cls.GRAY,
|
||||
"ansibrightblack": color_cls.BLACK | color_cls.INTENSITY,
|
||||
"ansiwhite": color_cls.GRAY | color_cls.INTENSITY,
|
||||
# Low intensity.
|
||||
"ansired": color_cls.RED,
|
||||
"ansigreen": color_cls.GREEN,
|
||||
"ansiyellow": color_cls.YELLOW,
|
||||
"ansiblue": color_cls.BLUE,
|
||||
"ansimagenta": color_cls.MAGENTA,
|
||||
"ansicyan": color_cls.CYAN,
|
||||
# High intensity.
|
||||
"ansibrightred": color_cls.RED | color_cls.INTENSITY,
|
||||
"ansibrightgreen": color_cls.GREEN | color_cls.INTENSITY,
|
||||
"ansibrightyellow": color_cls.YELLOW | color_cls.INTENSITY,
|
||||
"ansibrightblue": color_cls.BLUE | color_cls.INTENSITY,
|
||||
"ansibrightmagenta": color_cls.MAGENTA | color_cls.INTENSITY,
|
||||
"ansibrightcyan": color_cls.CYAN | color_cls.INTENSITY,
|
||||
}
|
||||
|
||||
|
||||
FG_ANSI_COLORS = _create_ansi_color_dict(FOREGROUND_COLOR)
|
||||
BG_ANSI_COLORS = _create_ansi_color_dict(BACKGROUND_COLOR)
|
||||
|
||||
assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES)
|
||||
assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES)
|
||||
|
||||
|
||||
class ColorLookupTable:
|
||||
"""
|
||||
Inspired by pygments/formatters/terminal256.py
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._win32_colors = self._build_color_table()
|
||||
|
||||
# Cache (map color string to foreground and background code).
|
||||
self.best_match: Dict[str, Tuple[int, int]] = {}
|
||||
|
||||
@staticmethod
|
||||
def _build_color_table() -> List[Tuple[int, int, int, int, int]]:
|
||||
"""
|
||||
Build an RGB-to-256 color conversion table
|
||||
"""
|
||||
FG = FOREGROUND_COLOR
|
||||
BG = BACKGROUND_COLOR
|
||||
|
||||
return [
|
||||
(0x00, 0x00, 0x00, FG.BLACK, BG.BLACK),
|
||||
(0x00, 0x00, 0xAA, FG.BLUE, BG.BLUE),
|
||||
(0x00, 0xAA, 0x00, FG.GREEN, BG.GREEN),
|
||||
(0x00, 0xAA, 0xAA, FG.CYAN, BG.CYAN),
|
||||
(0xAA, 0x00, 0x00, FG.RED, BG.RED),
|
||||
(0xAA, 0x00, 0xAA, FG.MAGENTA, BG.MAGENTA),
|
||||
(0xAA, 0xAA, 0x00, FG.YELLOW, BG.YELLOW),
|
||||
(0x88, 0x88, 0x88, FG.GRAY, BG.GRAY),
|
||||
(0x44, 0x44, 0xFF, FG.BLUE | FG.INTENSITY, BG.BLUE | BG.INTENSITY),
|
||||
(0x44, 0xFF, 0x44, FG.GREEN | FG.INTENSITY, BG.GREEN | BG.INTENSITY),
|
||||
(0x44, 0xFF, 0xFF, FG.CYAN | FG.INTENSITY, BG.CYAN | BG.INTENSITY),
|
||||
(0xFF, 0x44, 0x44, FG.RED | FG.INTENSITY, BG.RED | BG.INTENSITY),
|
||||
(0xFF, 0x44, 0xFF, FG.MAGENTA | FG.INTENSITY, BG.MAGENTA | BG.INTENSITY),
|
||||
(0xFF, 0xFF, 0x44, FG.YELLOW | FG.INTENSITY, BG.YELLOW | BG.INTENSITY),
|
||||
(0x44, 0x44, 0x44, FG.BLACK | FG.INTENSITY, BG.BLACK | BG.INTENSITY),
|
||||
(0xFF, 0xFF, 0xFF, FG.GRAY | FG.INTENSITY, BG.GRAY | BG.INTENSITY),
|
||||
]
|
||||
|
||||
def _closest_color(self, r: int, g: int, b: int) -> Tuple[int, int]:
|
||||
distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff)
|
||||
fg_match = 0
|
||||
bg_match = 0
|
||||
|
||||
for r_, g_, b_, fg_, bg_ in self._win32_colors:
|
||||
rd = r - r_
|
||||
gd = g - g_
|
||||
bd = b - b_
|
||||
|
||||
d = rd * rd + gd * gd + bd * bd
|
||||
|
||||
if d < distance:
|
||||
fg_match = fg_
|
||||
bg_match = bg_
|
||||
distance = d
|
||||
return fg_match, bg_match
|
||||
|
||||
def _color_indexes(self, color: str) -> Tuple[int, int]:
|
||||
indexes = self.best_match.get(color, None)
|
||||
if indexes is None:
|
||||
try:
|
||||
rgb = int(str(color), 16)
|
||||
except ValueError:
|
||||
rgb = 0
|
||||
|
||||
r = (rgb >> 16) & 0xFF
|
||||
g = (rgb >> 8) & 0xFF
|
||||
b = rgb & 0xFF
|
||||
indexes = self._closest_color(r, g, b)
|
||||
self.best_match[color] = indexes
|
||||
return indexes
|
||||
|
||||
def lookup_fg_color(self, fg_color: str) -> int:
|
||||
"""
|
||||
Return the color for use in the
|
||||
`windll.kernel32.SetConsoleTextAttribute` API call.
|
||||
|
||||
:param fg_color: Foreground as text. E.g. 'ffffff' or 'red'
|
||||
"""
|
||||
# Foreground.
|
||||
if fg_color in FG_ANSI_COLORS:
|
||||
return FG_ANSI_COLORS[fg_color]
|
||||
else:
|
||||
return self._color_indexes(fg_color)[0]
|
||||
|
||||
def lookup_bg_color(self, bg_color: str) -> int:
|
||||
"""
|
||||
Return the color for use in the
|
||||
`windll.kernel32.SetConsoleTextAttribute` API call.
|
||||
|
||||
:param bg_color: Background as text. E.g. 'ffffff' or 'red'
|
||||
"""
|
||||
# Background.
|
||||
if bg_color in BG_ANSI_COLORS:
|
||||
return BG_ANSI_COLORS[bg_color]
|
||||
else:
|
||||
return self._color_indexes(bg_color)[1]
|
104
venv/Lib/site-packages/prompt_toolkit/output/windows10.py
Normal file
104
venv/Lib/site-packages/prompt_toolkit/output/windows10.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
from ctypes import byref, windll
|
||||
from ctypes.wintypes import DWORD, HANDLE
|
||||
from typing import Any, Optional, TextIO
|
||||
|
||||
from prompt_toolkit.data_structures import Size
|
||||
from prompt_toolkit.renderer import Output
|
||||
from prompt_toolkit.utils import is_windows
|
||||
from prompt_toolkit.win32_types import STD_OUTPUT_HANDLE
|
||||
|
||||
from .color_depth import ColorDepth
|
||||
from .vt100 import Vt100_Output
|
||||
from .win32 import Win32Output
|
||||
|
||||
__all__ = [
|
||||
"Windows10_Output",
|
||||
]
|
||||
|
||||
# See: https://msdn.microsoft.com/pl-pl/library/windows/desktop/ms686033(v=vs.85).aspx
|
||||
ENABLE_PROCESSED_INPUT = 0x0001
|
||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
|
||||
|
||||
|
||||
class Windows10_Output:
|
||||
"""
|
||||
Windows 10 output abstraction. This enables and uses vt100 escape sequences.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, stdout: TextIO, default_color_depth: Optional[ColorDepth] = None
|
||||
) -> None:
|
||||
self.win32_output = Win32Output(stdout, default_color_depth=default_color_depth)
|
||||
self.vt100_output = Vt100_Output(
|
||||
stdout, lambda: Size(0, 0), default_color_depth=default_color_depth
|
||||
)
|
||||
self._hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE))
|
||||
|
||||
def flush(self) -> None:
|
||||
"""
|
||||
Write to output stream and flush.
|
||||
"""
|
||||
original_mode = DWORD(0)
|
||||
|
||||
# Remember the previous console mode.
|
||||
windll.kernel32.GetConsoleMode(self._hconsole, byref(original_mode))
|
||||
|
||||
# Enable processing of vt100 sequences.
|
||||
windll.kernel32.SetConsoleMode(
|
||||
self._hconsole,
|
||||
DWORD(ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING),
|
||||
)
|
||||
|
||||
try:
|
||||
self.vt100_output.flush()
|
||||
finally:
|
||||
# Restore console mode.
|
||||
windll.kernel32.SetConsoleMode(self._hconsole, original_mode)
|
||||
|
||||
@property
|
||||
def responds_to_cpr(self) -> bool:
|
||||
return False # We don't need this on Windows.
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
if name in (
|
||||
"get_size",
|
||||
"get_rows_below_cursor_position",
|
||||
"enable_mouse_support",
|
||||
"disable_mouse_support",
|
||||
"scroll_buffer_to_prompt",
|
||||
"get_win32_screen_buffer_info",
|
||||
"enable_bracketed_paste",
|
||||
"disable_bracketed_paste",
|
||||
"get_default_color_depth",
|
||||
):
|
||||
return getattr(self.win32_output, name)
|
||||
else:
|
||||
return getattr(self.vt100_output, name)
|
||||
|
||||
|
||||
Output.register(Windows10_Output)
|
||||
|
||||
|
||||
def is_win_vt100_enabled() -> bool:
|
||||
"""
|
||||
Returns True when we're running Windows and VT100 escape sequences are
|
||||
supported.
|
||||
"""
|
||||
if not is_windows():
|
||||
return False
|
||||
|
||||
hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE))
|
||||
|
||||
# Get original console mode.
|
||||
original_mode = DWORD(0)
|
||||
windll.kernel32.GetConsoleMode(hconsole, byref(original_mode))
|
||||
|
||||
try:
|
||||
# Try to enable VT100 sequences.
|
||||
result = windll.kernel32.SetConsoleMode(
|
||||
hconsole, DWORD(ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
)
|
||||
|
||||
return result == 1
|
||||
finally:
|
||||
windll.kernel32.SetConsoleMode(hconsole, original_mode)
|
Loading…
Add table
Add a link
Reference in a new issue