Skip to content

Commit

Permalink
feat(printer): hide cursor when supported
Browse files Browse the repository at this point in the history
  • Loading branch information
lengau committed Sep 11, 2024
1 parent c6457c0 commit 164ba6e
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 7 deletions.
12 changes: 10 additions & 2 deletions craft_cli/printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import platform
import queue
import shutil
import sys
import threading
import time
from dataclasses import dataclass, field
Expand All @@ -46,7 +47,9 @@
# craft_cli/pytest_plugin.py )
TESTMODE = False

ANSI_CLEAR_LINE_TO_END = "\033[K" # ANSI escape code to clear the rest of the line.
ANSI_CLEAR_LINE_TO_END = "\x1b[K" # ANSI escape code to clear the rest of the line.
ANSI_HIDE_CURSOR = "\x1b[?25l"
ANSI_SHOW_CURSOR = "\x1b[?25h"


@dataclass
Expand Down Expand Up @@ -224,6 +227,9 @@ def __init__(self, log_filepath: pathlib.Path) -> None:
self.spinner = _Spinner(self)
if not TESTMODE:
self.spinner.start()
if _supports_ansi_escape_sequences() and _stream_is_terminal(sys.stderr):
# pass
print(ANSI_HIDE_CURSOR, end="", file=sys.stderr, flush=True)

def set_terminal_prefix(self, prefix: str) -> None:
"""Set the string to be prepended to every message shown to the terminal."""
Expand Down Expand Up @@ -466,11 +472,14 @@ def stop(self) -> None:
In detail:
- stop the spinner
- show the cursor
- add a new line to the screen (if needed)
- close the log file
"""
if not TESTMODE:
self.spinner.stop()
if _supports_ansi_escape_sequences() and _stream_is_terminal(sys.stderr):
print(ANSI_SHOW_CURSOR, end="", file=sys.stderr, flush=True)
if self.unfinished_stream is not None:
# With unfinished_stream set, the prv_msg object is valid.
if self.prv_msg is not None and self.prv_msg.ephemeral:
Expand All @@ -483,7 +492,6 @@ def stop(self) -> None:
# The last printed message is permanent. Leave the cursor on
# the next clean line.
print(flush=True, file=self.unfinished_stream)

self.log.close()
self.stopped = True

Expand Down
11 changes: 10 additions & 1 deletion tests/integration/test_messages_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ def logger():
return logger


def remove_control_characters(string: str) -> str:
"""Strip the non-printing characters from an output string."""
return (
string.replace(printer.ANSI_CLEAR_LINE_TO_END, "")
.replace(printer.ANSI_HIDE_CURSOR, "")
.replace(printer.ANSI_SHOW_CURSOR, "")
)


@dataclass
class Line:
"""A line that is expected to be in the result."""
Expand All @@ -95,7 +104,7 @@ def compare_lines(expected_lines: Collection[Line], raw_stream: str, std_stream)

if terminal:
if printer._supports_ansi_escape_sequences():
lines = raw_stream.replace("\033[K", "").splitlines(keepends=True)
lines = remove_control_characters(raw_stream).splitlines(keepends=True)
else:
# If the terminal doesn't support ANSI escape sequences, we fill the screen
# width and don't terminate lines, so we split lines according to that length
Expand Down
22 changes: 18 additions & 4 deletions tests/unit/test_printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@
from craft_cli import printer as printermod
from craft_cli.printer import Printer, _MessageInfo, _Spinner

pytestmark = [
# Always use capsys, giving the printer its own stdout and stderr.
# This is useful because the printer will print control characters
# directly to stderr.
pytest.mark.usefixtures("capsys"),
]


@pytest.fixture(autouse=True)
def init_emitter():
Expand Down Expand Up @@ -77,6 +84,15 @@ def _supports_ansi_escape_sequences():
return request.param


def remove_control_characters(string: str) -> str:
"""Strip the non-printing characters from an output string."""
return (
string.replace(printermod.ANSI_CLEAR_LINE_TO_END, "")
.replace(printermod.ANSI_HIDE_CURSOR, "")
.replace(printermod.ANSI_SHOW_CURSOR, "")
)


# -- simple helpers


Expand Down Expand Up @@ -1423,7 +1439,7 @@ def test_secrets_progress_bar(capsys, log_filepath, monkeypatch):
printer.progress_bar(stream, message, progress=0.0, total=1.0, use_timestamp=False)

_, stderr = capsys.readouterr()
assert stderr.startswith(expected)
assert remove_control_characters(stderr).startswith(expected)


def test_secrets_terminal_prefix(capsys, log_filepath, monkeypatch):
Expand All @@ -1449,7 +1465,5 @@ def test_secrets_terminal_prefix(capsys, log_filepath, monkeypatch):
]

_, stderr = capsys.readouterr()
obtained = [
l.replace(printermod.ANSI_CLEAR_LINE_TO_END, "").strip() for l in stderr.splitlines()
]
obtained = [remove_control_characters(l).rstrip() for l in stderr.splitlines()]
assert obtained == expected

0 comments on commit 164ba6e

Please sign in to comment.