| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 8eb6fb0 commit 55fe0e6
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -952,13 +952,21 @@ Window objects | |||
| 952 | 952 | ``(y, x)`` with attributes | |
| 953 | 953 | *attr*, overwriting anything previously on the display. | |
| 954 | 954 | ||
| 955 | + .. versionchanged:: next | ||
| 956 | + *str* may now also be a :class:`complexstr`; see :meth:`addstr`. | ||
| 957 | + | ||
| 955 | 958 | ||
| 956 | 959 | .. method:: window.addstr(str[, attr]) | |
| 957 | 960 | window.addstr(y, x, str[, attr]) | |
| 958 | 961 | ||
| 959 | 962 | Paint the character string *str* at ``(y, x)`` with attributes | |
| 960 | 963 | *attr*, overwriting anything previously on the display. | |
| 961 | 964 | ||
| 965 | + *str* may also be a :class:`complexstr`, in which case each cell carries its | ||
| 966 | + own attributes and color pair, so *attr* must not be given. A | ||
| 967 | + :class:`complexstr` obtained from :meth:`in_wchstr` is written back | ||
| 968 | + unchanged. | ||
| 969 | + | ||
| 962 | 970 | .. note:: | |
| 963 | 971 | ||
| 964 | 972 | * Writing outside the window, subwindow, or pad raises :exc:`curses.error`. | |
@@ -971,6 +979,9 @@ Window objects | |||
| 971 | 979 | not calling :meth:`!addstr` with a *str* that has embedded newlines; | |
| 972 | 980 | instead, call :meth:`!addstr` separately for each line. | |
| 973 | 981 | ||
| 982 | + .. versionchanged:: next | ||
| 983 | + *str* may now also be a :class:`complexstr`, as described above. | ||
| 984 | + | ||
| 974 | 985 | ||
| 975 | 986 | .. method:: window.attroff(attr) | |
| 976 | 987 | ||
@@ -1428,6 +1439,9 @@ Window objects | |||
| 1428 | 1439 | cursor are shifted right, with the rightmost characters on the line being lost. | |
| 1429 | 1440 | The cursor position does not change (after moving to *y*, *x*, if specified). | |
| 1430 | 1441 | ||
| 1442 | + .. versionchanged:: next | ||
| 1443 | + *str* may now also be a :class:`complexstr`; see :meth:`insstr`. | ||
| 1444 | + | ||
| 1431 | 1445 | ||
| 1432 | 1446 | .. method:: window.insstr(str[, attr]) | |
| 1433 | 1447 | window.insstr(y, x, str[, attr]) | |
@@ -1437,6 +1451,12 @@ Window objects | |||
| 1437 | 1451 | shifted right, with the rightmost characters on the line being lost. The cursor | |
| 1438 | 1452 | position does not change (after moving to *y*, *x*, if specified). | |
| 1439 | 1453 | ||
| 1454 | + *str* may also be a :class:`complexstr`, in which case each cell carries its | ||
| 1455 | + own attributes and color pair, so *attr* must not be given. | ||
| 1456 | + | ||
| 1457 | + .. versionchanged:: next | ||
| 1458 | + *str* may now also be a :class:`complexstr`, as described above. | ||
| 1459 | + | ||
| 1440 | 1460 | ||
| 1441 | 1461 | .. method:: window.instr([n]) | |
| 1442 | 1462 | window.instr(y, x[, n]) | |
@@ -1465,6 +1485,25 @@ Window objects | |||
| 1465 | 1485 | .. versionadded:: next | |
| 1466 | 1486 | ||
| 1467 | 1487 | ||
| 1488 | + .. method:: window.in_wchstr([n]) | ||
| 1489 | + window.in_wchstr(y, x[, n]) | ||
| 1490 | + | ||
| 1491 | + Return a :class:`complexstr` of the styled cells extracted from the window | ||
| 1492 | + starting at the current cursor position, or at *y*, *x* if specified, and | ||
| 1493 | + stopping at the end of the line. This is the variant of :meth:`instr` and | ||
| 1494 | + :meth:`in_wstr` that *keeps* each cell's attributes and color pair (those | ||
| 1495 | + methods strip the rendition). If *n* is specified, at most *n* cells are | ||
| 1496 | + returned. The maximum value for *n* is 2047. | ||
| 1497 | + | ||
| 1498 | + The result can be written back unchanged with :meth:`addstr` (a read and a | ||
| 1499 | + re-write is a round-trip that preserves every cell's rendition). | ||
| 1500 | + | ||
| 1501 | + This method is only available if Python was built against a wide-character | ||
| 1502 | + version of the underlying curses library. | ||
| 1503 | + | ||
| 1504 | + .. versionadded:: next | ||
| 1505 | + | ||
| 1506 | + | ||
| 1468 | 1507 | .. method:: window.is_cleared() | |
| 1469 | 1508 | ||
| 1470 | 1509 | Return the current value set by :meth:`clearok`. | |
@@ -1913,6 +1952,43 @@ Complex character objects | |||
| 1913 | 1952 | .. versionadded:: next | |
| 1914 | 1953 | ||
| 1915 | 1954 | ||
| 1955 | + .. class:: complexstr(cells[, attr[, pair]]) | ||
| 1956 | + | ||
| 1957 | + A *complex character string* (or *complexstr*) is an immutable sequence of | ||
| 1958 | + styled wide-character cells -- the string counterpart of | ||
| 1959 | + :class:`complexchar` (as :class:`str` is to a single character). | ||
| 1960 | + | ||
| 1961 | + If *cells* is a string, it is split into character cells (each a spacing | ||
| 1962 | + character optionally followed by combining characters), and *attr* (a | ||
| 1963 | + combination of the :ref:`WA_* attributes <curses-wa-constants>`) and *pair* | ||
| 1964 | + (a color pair number), if given, are applied to every cell. | ||
| 1965 | + | ||
| 1966 | + Otherwise *cells* is an iterable whose items are themselves cells, each a | ||
| 1967 | + :class:`complexchar` or a string; each item then carries its own rendition, | ||
| 1968 | + and *attr* and *pair* must be omitted. | ||
| 1969 | + | ||
| 1970 | + It is returned by :meth:`window.in_wchstr`, and accepted by | ||
| 1971 | + :meth:`window.addstr`, :meth:`~window.addnstr`, :meth:`~window.insstr` and | ||
| 1972 | + :meth:`~window.insnstr`, so a run read from a window can be written back | ||
| 1973 | + unchanged. | ||
| 1974 | + | ||
| 1975 | + It behaves like an immutable sequence: ``len(s)`` is the number of cells, | ||
| 1976 | + ``s[i]`` is the *i*-th cell as a :class:`complexchar`, slicing and | ||
| 1977 | + concatenation produce new :class:`!complexstr` instances, and iterating | ||
| 1978 | + yields the cells. :func:`str` returns the cells' text joined together, and | ||
| 1979 | + two complex character strings are equal when their cells all match. It is | ||
| 1980 | + hashable. | ||
| 1981 | + | ||
| 1982 | + To build or edit a run of cells, use an ordinary :class:`list` of | ||
| 1983 | + :class:`complexchar` (or strings); a :class:`!complexstr` is the immutable | ||
| 1984 | + form returned by a read. | ||
| 1985 | + | ||
| 1986 | + This type is only available if Python was built against a wide-character | ||
| 1987 | + version of the underlying curses library. | ||
| 1988 | + | ||
| 1989 | + .. versionadded:: next | ||
| 1990 | + | ||
| 1991 | + | ||
| 1916 | 1992 | Constants | |
| 1917 | 1993 | --------- | |
| 1918 | 1994 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -148,6 +148,14 @@ curses | |||
| 148 | 148 | :class:`~curses.complexchar`. | |
| 149 | 149 | (Contributed by Serhiy Storchaka in :gh:`152233`.) | |
| 150 | 150 | ||
| 151 | + * Add the :class:`curses.complexstr` type, an immutable run of styled cells | ||
| 152 | + (the string counterpart of :class:`~curses.complexchar`), and the window | ||
| 153 | + method :meth:`~curses.window.in_wchstr` that returns one. The string-cell | ||
| 154 | + methods :meth:`~curses.window.addstr`, :meth:`~curses.window.addnstr`, | ||
| 155 | + :meth:`~curses.window.insstr` and :meth:`~curses.window.insnstr` now also | ||
| 156 | + accept a :class:`~curses.complexstr`. | ||
| 157 | + (Contributed by Serhiy Storchaka in :gh:`152233`.) | ||
| 158 | + | ||
| 151 | 159 | gzip | |
| 152 | 160 | ---- | |
| 153 | 161 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -469,6 +469,145 @@ def test_getbkgrnd(self): | |||
| 469 | 469 | self.assertEqual(str(cc), ' ') | |
| 470 | 470 | self.assertTrue(cc.attr & curses.A_BOLD) | |
| 471 | 471 | ||
| 472 | + @requires_curses_func('complexstr') | ||
| 473 | + def test_complexstr(self): | ||
| 474 | + # A complexstr is an immutable run of styled wide-character cells: the | ||
| 475 | + # string counterpart of complexchar (as str is to a single character). | ||
| 476 | + cc = curses.complexchar | ||
| 477 | + B = curses.A_BOLD | ||
| 478 | + # Built from an iterable whose items are complexchar or str cells. | ||
| 479 | + s = curses.complexstr([cc('A', B), 'b', cc('c')]) | ||
| 480 | + self.assertEqual(len(s), 3) | ||
| 481 | + self.assertEqual(str(s), 'Abc') | ||
| 482 | + # Indexing yields a complexchar carrying the cell's rendition. | ||
| 483 | + self.assertIsInstance(s[0], curses.complexchar) | ||
| 484 | + self.assertEqual(str(s[0]), 'A') | ||
| 485 | + self.assertTrue(s[0].attr & B) | ||
| 486 | + self.assertEqual(s[-1], cc('c')) | ||
| 487 | + self.assertRaises(IndexError, lambda: s[3]) | ||
| 488 | + # Iteration walks the cells. | ||
| 489 | + self.assertEqual([str(c) for c in s], ['A', 'b', 'c']) | ||
| 490 | + # Slicing and concatenation produce new complexstr instances. | ||
| 491 | + self.assertIsInstance(s[1:], curses.complexstr) | ||
| 492 | + self.assertEqual(str(s[1:]), 'bc') | ||
| 493 | + self.assertEqual(str(s[::-1]), 'cbA') | ||
| 494 | + self.assertEqual(str(s + curses.complexstr(['Z'])), 'AbcZ') | ||
| 495 | + # The empty complexstr. | ||
| 496 | + self.assertEqual(len(curses.complexstr([])), 0) | ||
| 497 | + self.assertEqual(str(curses.complexstr('')), '') | ||
| 498 | + # Equality and hashing compare the cells (text, attributes, pair). | ||
| 499 | + self.assertEqual(s, curses.complexstr([cc('A', B), 'b', cc('c')])) | ||
| 500 | + self.assertEqual(hash(s), | ||
| 501 | + hash(curses.complexstr([cc('A', B), 'b', cc('c')]))) | ||
| 502 | + self.assertNotEqual(s, curses.complexstr([cc('A'), 'b', cc('c')])) | ||
| 503 | + self.assertNotEqual(s, curses.complexstr([cc('A', B), 'b'])) | ||
| 504 | + # A spacing character optionally followed by combining characters. | ||
| 505 | + if self._encodable('é'): | ||
| 506 | + self.assertEqual(str(curses.complexstr(['é', 'x'])), | ||
| 507 | + 'éx') | ||
| 508 | + # cells is positional-only. | ||
| 509 | + self.assertRaises(TypeError, lambda: curses.complexstr(cells=['x'])) | ||
| 510 | + # Invalid arguments. | ||
| 511 | + self.assertRaises(TypeError, curses.complexstr, 5) | ||
| 512 | + self.assertRaises(TypeError, curses.complexstr, [65]) | ||
| 513 | + self.assertRaises(ValueError, curses.complexstr, ['ab']) | ||
| 514 | + | ||
| 515 | + # A string is split into character cells, grouping each base character | ||
| 516 | + # with the combining characters that follow it (not one cell per code | ||
| 517 | + # point), unlike a generic sequence whose items are each one cell. | ||
| 518 | + self.assertEqual(len(curses.complexstr('abc')), 3) | ||
| 519 | + self.assertEqual(str(curses.complexstr('abc')), 'abc') | ||
| 520 | + self.assertEqual(len(curses.complexstr('')), 0) | ||
| 521 | + base = 'é' # 'e' + combining acute: two code points, one cell | ||
| 522 | + if self._encodable(base): | ||
| 523 | + self.assertEqual(len(curses.complexstr(base)), 1) | ||
| 524 | + self.assertEqual(curses.complexstr(base)[0], cc(base)) | ||
| 525 | + self.assertEqual(len(curses.complexstr('a' + base + 'b')), 3) | ||
| 526 | + # A combining character cannot begin a cell: one that leads the | ||
| 527 | + # string, or overflows a base's combining slots, has no base. | ||
| 528 | + self.assertRaises(ValueError, curses.complexstr, '\u0301') | ||
| 529 | + self.assertRaises(ValueError, curses.complexstr, 'e' + '\u0301' * 10) | ||
| 530 | + # A control character may stand alone but not carry combining marks. | ||
| 531 | + self.assertRaises(ValueError, curses.complexstr, '\n\u0301') | ||
| 532 | + # attr and pair apply to every cell of a string; pair is optional. | ||
| 533 | + styled = curses.complexstr('hi', B, 0) | ||
| 534 | + self.assertTrue(all(styled[i].attr & B for i in range(len(styled)))) | ||
| 535 | + self.assertEqual(curses.complexstr('x', B)[0], cc('x', B)) | ||
| 536 | + self.assertEqual(curses.complexstr('x', B, 0)[0], cc('x', B, 0)) | ||
| 537 | + # attr and pair may also be passed by keyword. | ||
| 538 | + self.assertEqual(curses.complexstr('x', attr=B)[0], cc('x', B)) | ||
| 539 | + self.assertEqual(curses.complexstr('x', attr=B, pair=0)[0], cc('x', B, 0)) | ||
| 540 | + self.assertEqual(curses.complexstr('x', pair=0)[0], cc('x', 0, 0)) | ||
| 541 | + # cells is positional-only. | ||
| 542 | + self.assertRaises(TypeError, lambda: curses.complexstr(cells='x')) | ||
| 543 | + self.assertRaises(ValueError, curses.complexstr, 'a', 0, -1) | ||
| 544 | + self.assertRaises(ValueError, lambda: curses.complexstr('a', pair=-1)) | ||
| 545 | + # For a non-string, giving attr/pair at all is an error (the cells | ||
| 546 | + # carry their own rendition) -- even attr=0. | ||
| 547 | + self.assertRaises(TypeError, curses.complexstr, [cc('A')], B) | ||
| 548 | + self.assertRaises(TypeError, curses.complexstr, [cc('A')], 0) | ||
| 549 | + self.assertRaises(TypeError, curses.complexstr, ['A'], 0, 0) | ||
| 550 | + self.assertRaises(TypeError, | ||
| 551 | + lambda: curses.complexstr([cc('A')], attr=B)) | ||
| 552 | + self.assertRaises(TypeError, | ||
| 553 | + lambda: curses.complexstr(['A'], pair=0)) | ||
| 554 | + | ||
| 555 | + @requires_curses_window_meth('in_wchstr') | ||
| 556 | + def test_in_wchstr(self): | ||
| 557 | + # in_wchstr() returns a complexstr -- the styled-cell counterpart of | ||
| 558 | + # instr() (bytes) and in_wstr() (str), which both strip the rendition. | ||
| 559 | + stdscr = self.stdscr | ||
| 560 | + cc = curses.complexchar | ||
| 561 | + B = curses.A_BOLD | ||
| 562 | + s = curses.complexstr([cc('A', B), cc('b'), cc('C', B)]) | ||
| 563 | + stdscr.addstr(0, 0, s) | ||
| 564 | + r = stdscr.in_wchstr(0, 0, 3) | ||
| 565 | + self.assertIsInstance(r, curses.complexstr) | ||
| 566 | + # A read followed by a re-write is an exact round-trip. | ||
| 567 | + self.assertEqual(r, s) | ||
| 568 | + self.assertEqual(str(r), 'AbC') | ||
| 569 | + self.assertTrue(r[0].attr & B) | ||
| 570 | + self.assertFalse(r[1].attr & B) | ||
| 571 | + # The count is optional and reads to the end of the line by default. | ||
| 572 | + stdscr.move(0, 0) | ||
| 573 | + self.assertEqual(str(stdscr.in_wchstr())[:3], 'AbC') | ||
| 574 | + | ||
| 575 | + @requires_curses_window_meth('in_wchstr') | ||
| 576 | + def test_complexstr_in_write_methods(self): | ||
| 577 | + # addstr/addnstr/insstr/insnstr also accept a complexstr, written via | ||
| 578 | + # the wide-character functions; a plain str keeps its current meaning. | ||
| 579 | + stdscr = self.stdscr | ||
| 580 | + cc = curses.complexchar | ||
| 581 | + B = curses.A_BOLD | ||
| 582 | + s = curses.complexstr([cc('A', B), cc('b'), cc('C', B)]) | ||
| 583 | + # addstr with a complexstr round-trips. | ||
| 584 | + stdscr.addstr(0, 0, s) | ||
| 585 | + self.assertEqual(stdscr.in_wchstr(0, 0, 3), s) | ||
| 586 | + # addnstr writes at most n cells. | ||
| 587 | + stdscr.addstr(2, 0, '....') | ||
| 588 | + stdscr.addnstr(2, 0, s, 2) | ||
| 589 | + self.assertEqual(str(stdscr.in_wchstr(2, 0, 4)), 'Ab..') | ||
| 590 | + # insstr inserts the cells in order. | ||
| 591 | + stdscr.move(3, 0) | ||
| 592 | + stdscr.addstr('END') | ||
| 593 | + stdscr.insstr(3, 0, curses.complexstr([cc('P'), cc('Q')])) | ||
| 594 | + self.assertEqual(str(stdscr.in_wchstr(3, 0, 5)), 'PQEND') | ||
| 595 | + # insnstr inserts at most n cells. | ||
| 596 | + stdscr.move(4, 0) | ||
| 597 | + stdscr.addstr('END') | ||
| 598 | + stdscr.insnstr(4, 0, curses.complexstr(['1', '2', '3']), 2) | ||
| 599 | + self.assertEqual(str(stdscr.in_wchstr(4, 0, 5)), '12END') | ||
| 600 | + # An empty run is accepted (and still honours the move). | ||
| 601 | + stdscr.addstr(5, 0, curses.complexstr([])) | ||
| 602 | + stdscr.insstr(5, 0, curses.complexstr([])) | ||
| 603 | + # Cells carry their own rendition, so an explicit attr is rejected. | ||
| 604 | + self.assertRaises(TypeError, stdscr.addstr, s, B) | ||
| 605 | + self.assertRaises(TypeError, stdscr.addnstr, s, 2, B) | ||
| 606 | + self.assertRaises(TypeError, stdscr.insstr, s, B) | ||
| 607 | + self.assertRaises(TypeError, stdscr.insnstr, s, 2, B) | ||
| 608 | + # A bare sequence of cells is not accepted; build a complexstr first. | ||
| 609 | + self.assertRaises(TypeError, stdscr.addstr, [cc('A'), 'b']) | ||
| 610 | + self.assertRaises(TypeError, stdscr.insstr, [cc('A'), 'b']) | ||
| 472 | 611 | ||
| 473 | 612 | def test_output_character(self): | |
| 474 | 613 | stdscr = self.stdscr | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,6 @@ | |||
| 1 | + Add the :class:`curses.complexstr` type, an immutable string of styled | ||
| 2 | + wide-character cells (the counterpart of :class:`curses.complexchar`), and the | ||
| 3 | + :mod:`curses` window method :meth:`~curses.window.in_wchstr` that returns one. | ||
| 4 | + The string-cell methods :meth:`~curses.window.addstr`, | ||
| 5 | + :meth:`~curses.window.addnstr`, :meth:`~curses.window.insstr` and | ||
| 6 | + :meth:`~curses.window.insnstr` now also accept a :class:`~curses.complexstr`. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments