Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix terminal size detection #299

Merged
merged 1 commit into from
Jun 16, 2023
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 news/299.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed terminal size detection.
4 changes: 2 additions & 2 deletions src/cleo/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import os
import re
import shutil
import sys

from contextlib import suppress
Expand Down Expand Up @@ -30,6 +29,7 @@
from cleo.io.io import IO
from cleo.io.outputs.output import Verbosity
from cleo.io.outputs.stream_output import StreamOutput
from cleo.terminal import Terminal
from cleo.ui.ui import UI


Expand Down Expand Up @@ -61,7 +61,7 @@ def __init__(self, name: str = "console", version: str = "") -> None:
self._name = name
self._version = version
self._display_name: str | None = None
self._terminal = shutil.get_terminal_size()
self._terminal = Terminal().size
self._default_command = "list"
self._single_command = False
self._commands: dict[str, Command] = {}
Expand Down
6 changes: 3 additions & 3 deletions src/cleo/io/outputs/section_output.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from __future__ import annotations

import math
import shutil

from typing import TYPE_CHECKING
from typing import TextIO

from cleo.io.outputs.output import Verbosity
from cleo.io.outputs.stream_output import StreamOutput
from cleo.terminal import Terminal


if TYPE_CHECKING:
Expand All @@ -31,7 +31,7 @@ def __init__(
self._lines = 0
sections.insert(0, self)
self._sections = sections
self._terminal = shutil.get_terminal_size()
self._terminal = Terminal().size

@property
def content(self) -> str:
Expand Down Expand Up @@ -67,7 +67,7 @@ def add_content(self, content: str) -> None:
self._lines += (
math.ceil(
len(self.remove_format(line_content).replace("\t", " "))
/ self._terminal.columns
/ self._terminal.width
)
or 1
)
Expand Down
67 changes: 67 additions & 0 deletions src/cleo/terminal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from __future__ import annotations

import os
import sys

from typing import NamedTuple


class TerminalSize(NamedTuple):
width: int
height: int


class Terminal:
def __init__(
self,
width: int | None = None,
height: int | None = None,
fallback: tuple[int, int] | None = None,
) -> None:
self._width = width
self._height = height
self._fallback = TerminalSize(*(fallback if fallback is not None else (80, 25)))

@property
def width(self) -> int:
return self.size.width

@property
def height(self) -> int:
return self.size.height

@property
def size(self) -> TerminalSize:
return self._get_terminal_size()

def _get_terminal_size(self) -> TerminalSize:
if self._width is not None and self._height is not None:
return TerminalSize(self._width, self._height)

width = 0
height = 0

columns = os.environ.get("COLUMNS")
if columns is not None and columns.isdigit():
width = int(columns)
lines = os.environ.get("LINES")
if lines is not None and lines.isdigit():
height = int(lines)

if width <= 0 or height <= 0:
try:
os_size = os.get_terminal_size(sys.__stdout__.fileno())
size = TerminalSize(*os_size)
except (AttributeError, ValueError, OSError):
# stdout is None, closed, detached, or not a terminal, or
# os.get_terminal_size() is unsupported # noqa: ERA001
size = self._fallback
if width <= 0:
neersighted marked this conversation as resolved.
Show resolved Hide resolved
width = size.width or self._fallback.width
if height <= 0:
height = size.height or self._fallback.height

return TerminalSize(
neersighted marked this conversation as resolved.
Show resolved Hide resolved
width if self._width is None else self._width,
height if self._height is None else self._height,
)
8 changes: 4 additions & 4 deletions src/cleo/ui/progress_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import math
import re
import shutil
import time

from typing import TYPE_CHECKING
Expand All @@ -12,6 +11,7 @@
from cleo.cursor import Cursor
from cleo.io.io import IO
from cleo.io.outputs.section_output import SectionOutput
from cleo.terminal import Terminal
from cleo.ui.component import Component


Expand Down Expand Up @@ -57,7 +57,7 @@ def __init__(
io = io.error_output

self._io = io
self._terminal = shutil.get_terminal_size()
self._terminal = Terminal().size
self._max = 0
self._step_width: int = 1
self._set_max_steps(max)
Expand Down Expand Up @@ -317,7 +317,7 @@ def _overwrite(self, message: str) -> None:
int(
math.floor(
len(self._io.remove_format(message))
/ self._terminal.columns
/ self._terminal.width
)
)
+ self._format_line_count
Expand Down Expand Up @@ -443,7 +443,7 @@ def _build_line(self) -> str:

lines_width = max(lines_length)

terminal_width = self._terminal.columns
terminal_width = self._terminal.width

if lines_width <= terminal_width:
return line
Expand Down
57 changes: 57 additions & 0 deletions tests/test_terminal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from __future__ import annotations

import os

from typing import TYPE_CHECKING

import pytest

from cleo.terminal import Terminal


if TYPE_CHECKING:
from pytest_mock import MockerFixture


def test_size() -> None:
terminal = Terminal()
w, h = terminal.size
assert terminal.width == w

terminal = Terminal(width=99, height=101)
w, h = terminal.size
assert w == 99 and h == 101


@pytest.mark.parametrize(
"columns_env_value, init_value, expected",
(
("314", None, 314),
("200", 40, 40),
("random", 40, 40),
("random", None, 80),
),
)
def test_columns_env(
mocker: MockerFixture, columns_env_value: str, init_value: int | None, expected: int
) -> None:
mocker.patch.dict(os.environ, {"COLUMNS": columns_env_value}, clear=False)
console = Terminal(width=init_value)
assert console.width == expected


@pytest.mark.parametrize(
"lines_env_value, init_value, expected",
(
("314", None, 314),
("200", 40, 40),
("random", 40, 40),
("random", None, 25),
),
)
def test_lines_env(
mocker: MockerFixture, lines_env_value: str, init_value: int | None, expected: int
) -> None:
mocker.patch.dict(os.environ, {"LINES": lines_env_value}, clear=False)
console = Terminal(height=init_value)
assert console.height == expected