Skip to content

Commit 7a43084

Browse files
authored
Merge pull request #2044 from dbcli/RW/suppress-inline-images-on-windows-console
Don't attempt inline plots in the Windows console
2 parents cc5b8bf + 9856fa2 commit 7a43084

4 files changed

Lines changed: 83 additions & 3 deletions

File tree

‎changelog.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Features
66
* Subcommand completions for the `/dsn` command.
77
* Allow file target of `$>` redirection to be quoted.
88
* Display of inline plots returned from `.|` operations.
9+
* Don't attempt inline plots in the Windows console.
910

1011

1112
Bug Fixes

‎mycli/compat.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
"""Platform and Python version compatibility support."""
22

3+
from importlib import import_module
34
import sys
45

56
WIN: bool = sys.platform in ("win32", "cygwin")
7+
8+
9+
def _is_win32() -> bool:
10+
return sys.platform == 'win32'
11+
12+
13+
def is_windows_console(output: object | None) -> bool:
14+
"""Return whether output uses a native Windows console backend."""
15+
if not _is_win32() or output is None:
16+
return False
17+
18+
output_types = tuple(
19+
getattr(import_module(module_name), class_name)
20+
for module_name, class_name in (
21+
('prompt_toolkit.output.conemu', 'ConEmuOutput'),
22+
('prompt_toolkit.output.win32', 'Win32Output'),
23+
('prompt_toolkit.output.windows10', 'Windows10_Output'),
24+
)
25+
)
26+
27+
return isinstance(output, output_types)

‎mycli/output.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from pygments.style import Style as PygmentsStyle
3030
from pymysql.cursors import Cursor
3131

32-
from mycli.compat import WIN
32+
from mycli.compat import WIN, is_windows_console
3333
from mycli.constants import DEFAULT_HEIGHT, DEFAULT_WIDTH
3434
import mycli.main_modes.repl as repl_mode
3535
from mycli.packages import special
@@ -116,7 +116,8 @@ def output(
116116
is_warnings_style: bool = False,
117117
) -> None:
118118
"""Output text to stdout or a pager command."""
119-
if result.image is not None:
119+
prompt_output = self.prompt_session.output if self.prompt_session is not None else None
120+
if result.image is not None and not is_windows_console(prompt_output):
120121
if result.image_protocol == 'iterm2':
121122
click.secho('')
122123
self.output_iterm2_image(result.image)

‎test/pytests/test_output.py‎

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import itertools
44
import shutil
5-
from types import SimpleNamespace
5+
import sys
6+
from types import ModuleType, SimpleNamespace
67
from typing import Any, cast
78

89
import click
@@ -11,9 +12,11 @@
1112
from prompt_toolkit.formatted_text import ANSI, FormattedText, to_plain_text
1213
import pytest
1314

15+
from mycli import compat
1416
from mycli import output as output_module
1517
from mycli.output import OutputMixin
1618
from mycli.packages.sqlresult import SQLResult
19+
from mycli.types import ImageProtocol
1720
from test.utils import DummyFormatter, FakeCursorBase, make_bare_mycli # type: ignore[attr-defined]
1821

1922

@@ -112,6 +115,59 @@ def test_output_emits_kitty_image_in_base64_chunks(monkeypatch: pytest.MonkeyPat
112115
]
113116

114117

118+
@pytest.mark.parametrize('image_protocol', ['iterm2', 'kitty'])
119+
def test_output_suppresses_images_in_windows_console(
120+
monkeypatch: pytest.MonkeyPatch,
121+
image_protocol: ImageProtocol,
122+
) -> None:
123+
cli = make_bare_mycli()
124+
cli.prompt_session = cast(
125+
Any,
126+
SimpleNamespace(output=SimpleNamespace(get_size=lambda: SimpleNamespace(columns=80, rows=24))),
127+
)
128+
cli.get_output_margin = lambda status=None: 1 # type: ignore[assignment]
129+
emitted: list[str | None] = []
130+
monkeypatch.setattr(output_module, 'is_windows_console', lambda output: True)
131+
monkeypatch.setattr(click, 'echo', lambda value=None, **_kwargs: emitted.append(value))
132+
133+
OutputMixin.output(cli, itertools.chain(), SQLResult(image=b'png', image_protocol=image_protocol))
134+
135+
assert emitted == []
136+
137+
138+
def test_is_windows_console_detects_prompt_toolkit_backends(monkeypatch: pytest.MonkeyPatch) -> None:
139+
class FakeWin32Output:
140+
pass
141+
142+
class FakeWindows10Output:
143+
pass
144+
145+
class FakeConEmuOutput:
146+
pass
147+
148+
modules = (
149+
('prompt_toolkit.output.win32', 'Win32Output', FakeWin32Output),
150+
('prompt_toolkit.output.windows10', 'Windows10_Output', FakeWindows10Output),
151+
('prompt_toolkit.output.conemu', 'ConEmuOutput', FakeConEmuOutput),
152+
)
153+
for module_name, class_name, output_class in modules:
154+
module = ModuleType(module_name)
155+
setattr(module, class_name, output_class)
156+
monkeypatch.setitem(sys.modules, module_name, module)
157+
monkeypatch.setattr(compat.sys, 'platform', 'win32')
158+
159+
assert compat.is_windows_console(FakeWin32Output())
160+
assert compat.is_windows_console(FakeWindows10Output())
161+
assert compat.is_windows_console(FakeConEmuOutput())
162+
assert not compat.is_windows_console(object())
163+
164+
165+
def test_is_windows_console_ignores_non_windows_platform(monkeypatch: pytest.MonkeyPatch) -> None:
166+
monkeypatch.setattr(compat.sys, 'platform', 'cygwin')
167+
168+
assert not compat.is_windows_console(object())
169+
170+
115171
def test_get_output_margin_renders_prompt_once_and_counts_status_lines(monkeypatch: pytest.MonkeyPatch) -> None:
116172
cli = make_bare_mycli()
117173
cli.prompt_lines = 0

0 commit comments

Comments
 (0)