Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Features
* Subcommand completions for the `/dsn` command.
* Allow file target of `$>` redirection to be quoted.
* Display of inline plots returned from `.|` operations.
* Don't attempt inline plots in the Windows console.


Bug Fixes
Expand Down
22 changes: 22 additions & 0 deletions mycli/compat.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
"""Platform and Python version compatibility support."""

from importlib import import_module
import sys

WIN: bool = sys.platform in ("win32", "cygwin")


def _is_win32() -> bool:
return sys.platform == 'win32'


def is_windows_console(output: object | None) -> bool:
"""Return whether output uses a native Windows console backend."""
if not _is_win32() or output is None:
return False

output_types = tuple(
getattr(import_module(module_name), class_name)
for module_name, class_name in (
('prompt_toolkit.output.conemu', 'ConEmuOutput'),
('prompt_toolkit.output.win32', 'Win32Output'),
('prompt_toolkit.output.windows10', 'Windows10_Output'),
)
)

return isinstance(output, output_types)
5 changes: 3 additions & 2 deletions mycli/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from pygments.style import Style as PygmentsStyle
from pymysql.cursors import Cursor

from mycli.compat import WIN
from mycli.compat import WIN, is_windows_console
from mycli.constants import DEFAULT_HEIGHT, DEFAULT_WIDTH
import mycli.main_modes.repl as repl_mode
from mycli.packages import special
Expand Down Expand Up @@ -116,7 +116,8 @@ def output(
is_warnings_style: bool = False,
) -> None:
"""Output text to stdout or a pager command."""
if result.image is not None:
prompt_output = self.prompt_session.output if self.prompt_session is not None else None
if result.image is not None and not is_windows_console(prompt_output):
if result.image_protocol == 'iterm2':
click.secho('')
self.output_iterm2_image(result.image)
Expand Down
58 changes: 57 additions & 1 deletion test/pytests/test_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import itertools
import shutil
from types import SimpleNamespace
import sys
from types import ModuleType, SimpleNamespace
from typing import Any, cast

import click
Expand All @@ -11,9 +12,11 @@
from prompt_toolkit.formatted_text import ANSI, FormattedText, to_plain_text
import pytest

from mycli import compat
from mycli import output as output_module
from mycli.output import OutputMixin
from mycli.packages.sqlresult import SQLResult
from mycli.types import ImageProtocol
from test.utils import DummyFormatter, FakeCursorBase, make_bare_mycli # type: ignore[attr-defined]


Expand Down Expand Up @@ -112,6 +115,59 @@ def test_output_emits_kitty_image_in_base64_chunks(monkeypatch: pytest.MonkeyPat
]


@pytest.mark.parametrize('image_protocol', ['iterm2', 'kitty'])
def test_output_suppresses_images_in_windows_console(
monkeypatch: pytest.MonkeyPatch,
image_protocol: ImageProtocol,
) -> None:
cli = make_bare_mycli()
cli.prompt_session = cast(
Any,
SimpleNamespace(output=SimpleNamespace(get_size=lambda: SimpleNamespace(columns=80, rows=24))),
)
cli.get_output_margin = lambda status=None: 1 # type: ignore[assignment]
emitted: list[str | None] = []
monkeypatch.setattr(output_module, 'is_windows_console', lambda output: True)
monkeypatch.setattr(click, 'echo', lambda value=None, **_kwargs: emitted.append(value))

OutputMixin.output(cli, itertools.chain(), SQLResult(image=b'png', image_protocol=image_protocol))

assert emitted == []


def test_is_windows_console_detects_prompt_toolkit_backends(monkeypatch: pytest.MonkeyPatch) -> None:
class FakeWin32Output:
pass

class FakeWindows10Output:
pass

class FakeConEmuOutput:
pass

modules = (
('prompt_toolkit.output.win32', 'Win32Output', FakeWin32Output),
('prompt_toolkit.output.windows10', 'Windows10_Output', FakeWindows10Output),
('prompt_toolkit.output.conemu', 'ConEmuOutput', FakeConEmuOutput),
)
for module_name, class_name, output_class in modules:
module = ModuleType(module_name)
setattr(module, class_name, output_class)
monkeypatch.setitem(sys.modules, module_name, module)
monkeypatch.setattr(compat.sys, 'platform', 'win32')

assert compat.is_windows_console(FakeWin32Output())
assert compat.is_windows_console(FakeWindows10Output())
assert compat.is_windows_console(FakeConEmuOutput())
assert not compat.is_windows_console(object())


def test_is_windows_console_ignores_non_windows_platform(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(compat.sys, 'platform', 'cygwin')

assert not compat.is_windows_console(object())


def test_get_output_margin_renders_prompt_once_and_counts_status_lines(monkeypatch: pytest.MonkeyPatch) -> None:
cli = make_bare_mycli()
cli.prompt_lines = 0
Expand Down
Loading