Setting terminal window title (#2200)

* Adding tests for setting console title

* Add Windows note

* Update changelog regarding changing terminal window title

* Add test for window title control code -> legacy windows conversion

* Fix docstring typo
This commit is contained in:
Darren Burns 2022-04-19 17:20:17 +01:00 committed by GitHub
parent 3b36864ad0
commit cb56ec7ebb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 94 additions and 10 deletions

View File

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Ability to change terminal window title https://github.com/Textualize/rich/pull/2200
### Fixed
- Fall back to `sys.__stderr__` on POSIX systems when trying to get the terminal size (fix issues when Rich is piped to another process)
@ -25,9 +29,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Progress.open and Progress.wrap_file method to track the progress while reading from a file or file-like object https://github.com/willmcgugan/rich/pull/1759
- SVG export functionality https://github.com/Textualize/rich/pull/2101
### Added
- Adding Indonesian translation
### Fixed

View File

@ -51,3 +51,6 @@ def legacy_windows_render(buffer: Iterable[Segment], term: LegacyWindowsTerm) ->
term.erase_start_of_line()
elif mode == 2:
term.erase_line()
elif control_type == ControlType.SET_WINDOW_TITLE:
_, title = cast(Tuple[ControlType, str], control_code)
term.set_title(title)

View File

@ -1181,6 +1181,38 @@ class Console:
"""
return self._is_alt_screen
def set_window_title(self, title: str) -> bool:
"""Set the title of the console terminal window.
Warning: There is no means within Rich of "resetting" the window title to its
previous value, meaning the title you set will persist even after your application
exits.
``fish`` shell resets the window title before and after each command by default,
negating this issue. Windows Terminal and command prompt will also reset the title for you.
Most other shells and terminals, however, do not do this.
Some terminals may require configuration changes before you can set the title.
Some terminals may not support setting the title at all.
Other software (including the terminal itself, the shell, custom prompts, plugins, etc.)
may also set the terminal window title. This could result in whatever value you write
using this method being overwritten.
Args:
title (str): The new title of the terminal window.
Returns:
bool: True if the control code to change the terminal title was
written, otherwise False. Note that a return value of True
does not guarantee that the window title has actually changed,
since the feature may be unsupported/disabled in some terminals.
"""
if self.is_terminal:
self.control(Control.title(title))
return True
return False
def screen(
self, hide_cursor: bool = True, style: Optional[StyleType] = None
) -> "ScreenContext":

View File

@ -1,4 +1,5 @@
from typing import Callable, Dict, Iterable, List, TYPE_CHECKING, Union
import time
from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Union
from .segment import ControlCode, ControlType, Segment
@ -30,6 +31,7 @@ CONTROL_CODES_FORMAT: Dict[int, Callable[..., str]] = {
ControlType.CURSOR_MOVE_TO_COLUMN: lambda param: f"\x1b[{param+1}G",
ControlType.ERASE_IN_LINE: lambda param: f"\x1b[{param}K",
ControlType.CURSOR_MOVE_TO: lambda x, y: f"\x1b[{y+1};{x+1}H",
ControlType.SET_WINDOW_TITLE: lambda title: f"\x1b]0;{title}\x07",
}
@ -147,6 +149,15 @@ class Control:
else:
return cls(ControlType.DISABLE_ALT_SCREEN)
@classmethod
def title(cls, title: str) -> "Control":
"""Set the terminal window title
Args:
title (str): The new terminal window title
"""
return cls((ControlType.SET_WINDOW_TITLE, title))
def __str__(self) -> str:
return self.segment.text
@ -172,4 +183,11 @@ def strip_control_codes(
if __name__ == "__main__": # pragma: no cover
print(strip_control_codes("hello\rWorld"))
from rich.console import Console
console = Console()
console.print("Look at the title of your terminal window ^")
# console.print(Control((ControlType.SET_WINDOW_TITLE, "Hello, world!")))
for i in range(10):
console.set_window_title("🚀 Loading" + "." * i)
time.sleep(0.5)

View File

@ -49,10 +49,13 @@ class ControlType(IntEnum):
CURSOR_MOVE_TO_COLUMN = 13
CURSOR_MOVE_TO = 14
ERASE_IN_LINE = 15
SET_WINDOW_TITLE = 16
ControlCode = Union[
Tuple[ControlType], Tuple[ControlType, int], Tuple[ControlType, int, int]
Tuple[ControlType],
Tuple[ControlType, Union[int, str]],
Tuple[ControlType, int, int],
]

View File

@ -877,6 +877,18 @@ def test_is_alt_screen():
assert not console.is_alt_screen
def test_set_console_title():
console = Console(force_terminal=True, _environ={})
if console.legacy_windows:
return
with console.capture() as captured:
console.set_window_title("hello")
result = captured.get()
assert result == "\x1b]0;hello\x07"
def test_update_screen():
console = Console(force_terminal=True, width=20, height=5, _environ={})
if console.legacy_windows:

View File

@ -1,5 +1,5 @@
from rich.control import Control, strip_control_codes
from rich.segment import Segment, ControlType
from rich.segment import ControlType, Segment
def test_control():
@ -45,3 +45,12 @@ def test_move_to_column():
None,
[(ControlType.CURSOR_MOVE_TO_COLUMN, 10), (ControlType.CURSOR_UP, 20)],
)
def test_title():
control_segment = Control.title("hello").segment
assert control_segment == Segment(
"\x1b]0;hello\x07",
None,
[(ControlType.SET_WINDOW_TITLE, "hello")],
)

View File

@ -4,8 +4,8 @@ from typing import Optional
# import pytest
from rich.console import Console
from rich.text import Text
from rich.live import Live
from rich.text import Text
def create_capture_console(
@ -116,8 +116,6 @@ def test_growing_display_overflow_visible() -> None:
def test_growing_display_autorefresh() -> None:
"""Test generating a table but using auto-refresh from threading"""
console = create_capture_console()
console = create_capture_console(height=5)
console.begin_capture()
with Live(console=console, auto_refresh=True, vertical_overflow="visible") as live:

View File

@ -131,3 +131,11 @@ def test_control_cursor_move_to_column(legacy_term_mock):
legacy_windows_render(buffer, legacy_term_mock)
legacy_term_mock.move_cursor_to_column.assert_called_once_with(2)
def test_control_set_terminal_window_title(legacy_term_mock):
buffer = [Segment("", None, [(ControlType.SET_WINDOW_TITLE, "Hello, world!")])]
legacy_windows_render(buffer, legacy_term_mock)
legacy_term_mock.set_title.assert_called_once_with("Hello, world!")