Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
python-version: ['3.9', '3.10', '3.11', '3.12']
test_quick: [1]

include:
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ Install and run tox
Py.test is used as the test runner, supporting positional arguments, you may
for example use `looponfailing
<https://pytest.org/latest/xdist.html#running-tests-in-looponfailing-mode>`_
with python 3.8, stopping at the first failing test case::
with python 3.9, stopping at the first failing test case::

tox -epy38 -- -x
tox -epy39 -- -x

The test runner (``tox``) ensures all code and documentation complies with
standard python style guides, pep8 and pep257, as well as various static
Expand Down
18 changes: 18 additions & 0 deletions bin/display-modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
python bin/display-modes.py --force # bypass cached results
"""
# std imports
import os
import sys

# local
Expand Down Expand Up @@ -113,6 +114,15 @@ def display_sugar_methods(term):
print(f" Colors: {xtgettcap.num_colors}")
print(f" Capabilities: {len(xtgettcap)}")

print(' Testing XTVERSION...' + term.clear_eol, end='\r', flush=True)
version = term.get_software_version()
print(f" {_yn(term, version is not None)} XTVERSION (CSI > q)" + term.clear_eol)
if version is not None:
source = 'TERM_PROGRAM' if os.environ.get('TERM_PROGRAM') else 'inquiry'
print(f" Terminal name: {term.bright_cyan(version.name or '--')}")
print(f" Version: {version.version or '--'}")
print(f" Id Source: {source}")

print(' Testing Kitty graphics...' + term.clear_eol, end='\r', flush=True)
print(f" {_yn(term, term.does_kitty_graphics())} "
f"Kitty graphics protocol (APC)" + term.clear_eol)
Expand All @@ -134,6 +144,10 @@ def display_sugar_methods(term):
print(f" {_yn(term, term.does_kitty_notifications())} "
f"Kitty desktop notifications (OSC 99)" + term.clear_eol)

print(' Testing Kitty Text Sizing...' + term.clear_eol, end='\r', flush=True)
print(f" {_yn(term, term.does_text_sizing())} "
f"Kitty text sizing (OSC 66)" + term.clear_eol)


def display_decrqss(term):
"""Query and display terminal state via DECRQSS."""
Expand Down Expand Up @@ -229,6 +243,10 @@ def main():
_24bit = term.bright_green('24-bit')
_no_colors = term.bright_red(str(term.number_of_colors))
print(f" .number_of_colors: {_24bit if term.number_of_colors == 1 << 24 else _no_colors}")
_ambig = term.bright_cyan('Wide (2)') if term.ambiguous_width == 2 else 'Narrow (1)'
print(f" .ambiguous_width: {_ambig}")
_program = term.bright_cyan(term.term_program or term.bright_black('unknown'))
print(f" .term_program: {_program}")
print()

# Display Device Attributes
Expand Down
69 changes: 69 additions & 0 deletions bin/display-widths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python
"""Display graphemes whose rendered width differs by terminal as a labeled grid."""
# pylint: disable=invalid-name
# Invalid module name "display-terminal-width"

# local
from blessed import Terminal

LABEL_WIDTH = 20
CELL_WIDTH = 12

# Examples "problematic" graphemes, derived from wcwidth "correction tables" and
# the ucs-detect project data: graphemes that carry per-terminal width corrections
ROWS = (
('Ambiguous width', ('\u00a7', '\u00b1', '\u00d7', '\u00f7')),
('VS-16', ('\u2764\ufe0f', '\u263a\ufe0f', '\u26a0\ufe0f', '\u2708\ufe0f')),
('Regional Indicator', ('\U0001f1e6', '\U0001f1fa\U0001f1f8',
'\U0001f1ef\U0001f1f5', '\U0001f1e9\U0001f1ea')),
('ZWJ', ('\U0001f9d1\u200d\U0001f33e',
'\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466',
'\U0001f9d1\u200d\U0001f393', '\U0001f9d1\u200d\U0001f4bb')),
('Devanagari', ('\u0924\u093e\u0903', '\u0930\u094d\u200d\u092f\u093e', '\u0915\u094d\u0937',
'\u0915\u094d\u0915')),
('Bengali', ('\u0995\u09bf\u0982', '\u09b8\u09cd\u09af\u09c7', '\u09b8\u09cd\u09ac\u09c0',
'\u09b8\u09cd\u09a4\u09bf')),
('Gujarati', ('\u0ab8\u0acd\u0ab8\u0abe', '\u0ab8\u0acd\u0ab5\u0ac0',
'\u0ab8\u0acd\u0ab5\u0abe', '\u0ab8\u0acd\u0ab0\u0ac0')),
('Gurmukhi', ('\u0a5c\u0a40\u0a02', '\u0a39\u0a40\u0a02',
'\u0a39\u0a3f\u0a71', '\u0a39\u0a3f\u0a70')),
('Tamil', ('\u0bb5\u0bbe', '\u0bb3\u0bbe',
'\u0bb2\u0bbe', '\u0bb1\u0bbe')),
('Telugu', ('\u0c2f\u0c41\u0c02', '\u0c28\u0c41\u0c02',
'\u0c26\u0c41\u0c02', '\u0c1f\u0c41\u0c02')),
('Kannada', ('\u0cb8\u0cbe\u0c82', '\u0cb6\u0cbe\u0c82',
'\u0cb5\u0cc1\u0c82', '\u0cb0\u0cbe\u0c82')),
('Malayalam', ('\u0d39\u0d3f\u0d02', '\u0d38\u0d4d\u0d38\u0d4b', '\u0d38\u0d4d\u0d38\u0d3f',
'\u0d38\u0d4d\u0d35\u0d40')),
('Sinhala', ('\u0dc4\u0dd2\u0d82', '\u0daf\u0dd2\u0d82', '\u0dc4\u0dcf', '\u0dc3\u0dcf')),
('Khmer', ('\u1796\u17c4\u17c7', '\u1794\u17c4\u17c7',
'\u1793\u17c4\u17c7', '\u1793\u17c1\u17c7')),
('Myanmar', ('\u101c\u103b\u103e\u1031', '\u101c\u103b\u1031', '\u1019\u103c\u1031',
'\u1017\u103c\u1031')),
)


def main():
"""Program entry point."""
term = Terminal()
term.detect_ambiguous_width()

columns = len(ROWS[0][1])
border = term.cyan('+' + '-' * LABEL_WIDTH + ('+' + '-' * CELL_WIDTH) * columns + '+')
fill = term.bright_yellow('.')
title = 'Unidentified Terminal'
if (_sw_ver := term.get_software_version()):
title = repr(_sw_ver)

print(term.center(title))
print(border)
for label, graphemes in ROWS:
row = term.cyan('|') + term.magenta((' ' + label).ljust(LABEL_WIDTH)) + term.cyan('|')
for grapheme in graphemes:
row += term.magenta(term.center(grapheme, CELL_WIDTH, fill)) + term.cyan('|')
print(row)
print(border)


if __name__ == '__main__':
main()
8 changes: 2 additions & 6 deletions bin/keyboard_kitty_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,5 @@
else "released")
if key.pressed and key.value == 'q':
break
print(
f"name={
key.key_name} value={
key.key_value} kind={kind}, sequence={
key!r}",
end="\r\n")
print(f"name={key.key_name} value={key.key_value} kind={kind}, "
f"sequence={key!r}", end="\r\n")
3 changes: 3 additions & 0 deletions bin/line_editor_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def paste_line(ed):


with term.raw(), term.cursor_shape(term.CursorShape.BLINKING_BLOCK), term.bracketed_paste():
term.detect_ambiguous_width()
if has_clipboard:
echo("press ^C and ^V for OS clipboard, type 'quit' to exit")
else:
Expand All @@ -39,6 +40,8 @@ def paste_line(ed):
keymap['KEY_CTRL_C'] = copy_line
keymap['KEY_CTRL_V'] = paste_line
ed = LineEditor(history=history, max_width=ed_width, limit=200,
ambiguous_width=term.ambiguous_width,
term_program=term.term_program,
bg_sgr=term.on_brown,
keymap=keymap or None)
echo(term.move_x(col) + prompt, end='')
Expand Down
2 changes: 1 addition & 1 deletion blessed/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
from blessed.line_editor import LineEditor, LineHistory

__all__ = ('Terminal', 'LineEditor', 'LineHistory')
__version__ = "1.50.0"
__version__ = "1.51.0"
9 changes: 9 additions & 0 deletions blessed/keyboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -1465,6 +1465,7 @@ def resolve_sequence(text: str,
_match_legacy_csi_tilde_form,
_match_legacy_ss3_fkey_form,
_match_xtgettcap_response,
_match_xtversion_response,
_match_cpr_response]
if capture_cpr:
# prioritize capturing CPR_RESPONSE over legacy CSI Modifiers
Expand Down Expand Up @@ -1617,6 +1618,14 @@ def _match_xtgettcap_response(text: str) -> Optional[Keystroke]:
return None


def _match_xtversion_response(text: str) -> Optional[Keystroke]:
"""Match XTVERSION reply: ESC P > | <name> [version] ESC backslash."""
match = SoftwareVersion.RE_RESPONSE.match(text)
if match:
return Keystroke(ucs=match.group(0), name='XTVERSION_RESPONSE')
return None


def _match_cpr_response(text: str) -> Optional[Keystroke]:
"""Match CPR (Cursor Position Report): ESC [ row ; column R."""
# Matches terminal CPR responses where row >= 2. Row 1 is not
Expand Down
Loading
Loading