summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexey Stepanov <penguinolog@users.noreply.github.com>2023-05-09 09:13:40 +0200
committerGitHub <noreply@github.com>2023-05-09 09:13:40 +0200
commitffbfa07533809a523938d5e342ff7482a12dd5d0 (patch)
treef82251e47e72b9fe873f0eb20410c105387407b9
parentd26cb42a9fd28cb0743ad04d5ed2a0c7f28b89e3 (diff)
downloadurwid-ffbfa07533809a523938d5e342ff7482a12dd5d0.tar.gz
Fix `TextCanvas` `CanvasError("Attribute extends beyond text...")` (#555)HEADmaster
* Fix TextCanvas `CanvasError("Attribute extends beyond text...") * `[[]] * ...` causes list of 1 list with pointers amount equal to multiplier instead of "list of lists" * Add 2 basic font tests which check for Canvas create issue * Add few type annotations during debug process Fix: #554 * Force tests to restore default encoding in tearDown Tests order change should not cause tests failures --------- Co-authored-by: Aleksei Stepanov <alekseis@nvidia.com>
-rw-r--r--urwid/canvas.py33
-rwxr-xr-xurwid/curses_display.py8
-rw-r--r--urwid/tests/test_canvas.py6
-rw-r--r--urwid/tests/test_font.py30
-rw-r--r--urwid/tests/test_graphics.py17
-rw-r--r--urwid/tests/test_text_layout.py63
-rw-r--r--urwid/tests/test_util.py22
-rw-r--r--urwid/tests/test_widget.py33
8 files changed, 172 insertions, 40 deletions
diff --git a/urwid/canvas.py b/urwid/canvas.py
index ed71c54..4d467e8 100644
--- a/urwid/canvas.py
+++ b/urwid/canvas.py
@@ -61,7 +61,7 @@ class CanvasCache:
"""
_widgets = {}
_refs = {}
- _deps = {}
+ _deps: dict[Widget, list[Widget]] = {}
hits = 0
fetches = 0
cleanups = 0
@@ -157,7 +157,7 @@ class CanvasCache:
@classmethod
def cleanup(cls, ref):
- cls.cleanups += 1 # collect stats
+ cls.cleanups += 1 # collect stats
w = cls._refs.get(ref, None)
del cls._refs[ref]
@@ -198,12 +198,16 @@ class Canvas:
"""
cacheable = True
- _finalized_error = CanvasError("This canvas has been finalized. "
+ _finalized_error = CanvasError(
+ "This canvas has been finalized. "
"Use CompositeCanvas to wrap this canvas if "
- "you need to make changes.")
- _renamed_error = CanvasError("The old Canvas class is now called "
+ "you need to make changes."
+ )
+ _renamed_error = CanvasError(
+ "The old Canvas class is now called "
"TextCanvas. Canvas is now the base class for all canvas "
- "classes.")
+ "classes."
+ )
def __init__(
self,
@@ -221,7 +225,12 @@ class Canvas:
self.coords = {}
self.shortcuts = {}
- def finalize(self, widget, size, focus):
+ def finalize(
+ self,
+ widget: Widget,
+ size: tuple[()] | tuple[int] | tuple[int, int],
+ focus: bool,
+ ) -> None:
"""
Mark this canvas as finalized (should not be any future
changes to its content). This is required before caching
@@ -386,9 +395,9 @@ class TextCanvas(Canvas):
maxcol = 0
if attr is None:
- attr = [[]] * len(text)
+ attr = [[] for _ in range(len(text))]
if cs is None:
- cs = [[]] * len(text)
+ cs = [[] for _ in range(len(text))]
# pad text and attr to maxcol
for i in range(len(text)):
@@ -397,11 +406,11 @@ class TextCanvas(Canvas):
raise CanvasError(f"Canvas text is wider than the maxcol specified \n{maxcol!r}\n{widths!r}\n{text!r}")
if w < maxcol:
text[i] += b''.rjust(maxcol - w)
- a_gap = len(text[i]) - rle_len( attr[i] )
+ a_gap = len(text[i]) - rle_len(attr[i])
if a_gap < 0:
- raise CanvasError(f"Attribute extends beyond text \n{text[i]!r}\n{attr[i]!r}" )
+ raise CanvasError(f"Attribute extends beyond text \n{text[i]!r}\n{attr[i]!r}")
if a_gap:
- rle_append_modify( attr[i], (None, a_gap))
+ rle_append_modify(attr[i], (None, a_gap))
cs_gap = len(text[i]) - rle_len( cs[i] )
if cs_gap < 0:
diff --git a/urwid/curses_display.py b/urwid/curses_display.py
index e9519f7..86805f8 100755
--- a/urwid/curses_display.py
+++ b/urwid/curses_display.py
@@ -597,10 +597,10 @@ class _test:
attr += [[],[],[]]
cols,rows = self.ui.get_cols_rows()
keys = None
- while keys!=['q']:
- r.text=([t.ljust(cols) for t in text]+[""]*rows)[:rows]
- r.attr=(attr+[[]]*rows) [:rows]
- self.ui.draw_screen((cols,rows),r)
+ while keys != ['q']:
+ r.text = ([t.ljust(cols) for t in text] + [""] * rows)[:rows]
+ r.attr = (attr + [[] for _ in range(rows)])[:rows]
+ self.ui.draw_screen((cols, rows), r)
keys, raw = self.ui.get_input( raw_keys = True )
if 'window resize' in keys:
cols, rows = self.ui.get_cols_rows()
diff --git a/urwid/tests/test_canvas.py b/urwid/tests/test_canvas.py
index 5a1d454..d0c624c 100644
--- a/urwid/tests/test_canvas.py
+++ b/urwid/tests/test_canvas.py
@@ -302,6 +302,12 @@ class CanvasJoinTest(unittest.TestCase):
class CanvasOverlayTest(unittest.TestCase):
+ def setUp(self) -> None:
+ self.old_encoding = urwid.util._target_encoding
+
+ def tearDown(self) -> None:
+ urwid.set_encoding(self.old_encoding)
+
def cotest(self, desc, bgt, bga, fgt, fga, l, r, et):
bgt = bgt.encode('iso8859-1')
fgt = fgt.encode('iso8859-1')
diff --git a/urwid/tests/test_font.py b/urwid/tests/test_font.py
new file mode 100644
index 0000000..3c9be7a
--- /dev/null
+++ b/urwid/tests/test_font.py
@@ -0,0 +1,30 @@
+from __future__ import annotations
+
+import unittest
+
+import urwid
+
+
+class TestFontRender(unittest.TestCase):
+ def setUp(self) -> None:
+ self.old_encoding = urwid.util._target_encoding
+ urwid.set_encoding("utf-8")
+
+ def tearDown(self) -> None:
+ urwid.set_encoding(self.old_encoding)
+
+ def test_001_basic(self):
+ font = urwid.Thin3x3Font()
+ rendered = b'\n'.join(font.render("1").text).decode()
+ expected = ' ┐ \n │ \n ┴ '
+ self.assertEqual(expected, rendered)
+
+ def test_002_non_rect(self):
+ """Test non rect symbol, which causes spaces based padding.
+
+ Lines as bytes should be not equal length.
+ """
+ font = urwid.Thin3x3Font()
+ rendered = b'\n'.join(font.render("2").text).decode()
+ expected = '┌─┐\n┌─┘\n└─ '
+ self.assertEqual(expected, rendered)
diff --git a/urwid/tests/test_graphics.py b/urwid/tests/test_graphics.py
index f731208..1ae1f98 100644
--- a/urwid/tests/test_graphics.py
+++ b/urwid/tests/test_graphics.py
@@ -7,6 +7,13 @@ from urwid import graphics
class LineBoxTest(unittest.TestCase):
+ def setUp(self) -> None:
+ self.old_encoding = urwid.util._target_encoding
+ urwid.set_encoding("utf-8")
+
+ def tearDown(self) -> None:
+ urwid.set_encoding(self.old_encoding)
+
def border(self, tl, t, tr, l, r, bl, b, br):
return [b''.join([tl, t, tr]),
b''.join([l, b" ", r]),
@@ -14,7 +21,6 @@ class LineBoxTest(unittest.TestCase):
def test_linebox_pack(self):
# Bug #346 'pack' Padding does not run with LineBox
- urwid.set_encoding("utf-8")
t = urwid.Text("AAA\nCCC\nDDD")
size = t.pack()
l = urwid.LineBox(t)
@@ -23,7 +29,6 @@ class LineBoxTest(unittest.TestCase):
self.assertEqual(l.pack()[1], size[1] + 2)
def test_linebox_border(self):
- urwid.set_encoding("utf-8")
t = urwid.Text("")
l = urwid.LineBox(t).render((3,)).text
@@ -87,8 +92,14 @@ class BarGraphTest(unittest.TestCase):
(1,[(3,3)]) ] )
class SmoothBarGraphTest(unittest.TestCase):
+ def setUp(self) -> None:
+ self.old_encoding = urwid.util._target_encoding
+ urwid.set_encoding("utf-8")
+
+ def tearDown(self) -> None:
+ urwid.set_encoding(self.old_encoding)
+
def sbgtest(self, desc, data, top, exp ):
- urwid.set_encoding('utf-8')
g = urwid.BarGraph( ['black','red','blue'],
None, {(1,0):'red/black', (2,1):'blue/red'})
g.set_data( data, top )
diff --git a/urwid/tests/test_text_layout.py b/urwid/tests/test_text_layout.py
index c87d12c..95cba1c 100644
--- a/urwid/tests/test_text_layout.py
+++ b/urwid/tests/test_text_layout.py
@@ -1,11 +1,24 @@
from __future__ import annotations
+import contextlib
import unittest
+from collections.abc import Generator
import urwid
from urwid import text_layout
+@contextlib.contextmanager
+def encoding(encoding_name: str) -> Generator[None, None, None]:
+ old_encoding = 'ascii' # Default fallback value
+ try:
+ old_encoding = urwid.util._target_encoding
+ urwid.set_encoding(encoding_name)
+ yield
+ finally:
+ urwid.set_encoding(old_encoding)
+
+
class CalcBreaksTest:
def cbtest(self, width, exp):
result = text_layout.default_layout.calculate_text_segments(
@@ -33,8 +46,12 @@ class CalcBreaksCharTest(CalcBreaksTest, unittest.TestCase):
class CalcBreaksDBCharTest(CalcBreaksTest, unittest.TestCase):
def setUp(self):
+ self.old_encoding = urwid.util._target_encoding
urwid.set_encoding("euc-jp")
+ def tearDown(self) -> None:
+ urwid.set_encoding(self.old_encoding)
+
mode = 'any'
text = "abfgh\xA1\xA1j\xA1\xA1xskhtrvs\naltjhgsdf\xA1\xA1jahtshgf"
# tests
@@ -68,8 +85,12 @@ class CalcBreaksWordTest2(CalcBreaksTest, unittest.TestCase):
class CalcBreaksDBWordTest(CalcBreaksTest, unittest.TestCase):
def setUp(self):
+ self.old_encoding = urwid.util._target_encoding
urwid.set_encoding("euc-jp")
+ def tearDown(self) -> None:
+ urwid.set_encoding(self.old_encoding)
+
mode = 'space'
text = "hel\xA1\xA1 world\nout-\xA1\xA1tre blah"
# tests
@@ -81,9 +102,13 @@ class CalcBreaksDBWordTest(CalcBreaksTest, unittest.TestCase):
class CalcBreaksUTF8Test(CalcBreaksTest, unittest.TestCase):
- def setUp(self):
+ def setUp(self) -> None:
+ self.old_encoding = urwid.util._target_encoding
urwid.set_encoding("utf-8")
+ def tearDown(self) -> None:
+ urwid.set_encoding(self.old_encoding)
+
mode = 'space'
text = '\xe6\x9b\xbf\xe6\xb4\xbc\xe6\xb8\x8e\xe6\xba\x8f\xe6\xbd\xba'
do = [
@@ -95,20 +120,28 @@ class CalcBreaksUTF8Test(CalcBreaksTest, unittest.TestCase):
class CalcBreaksCantDisplayTest(unittest.TestCase):
def test(self):
- urwid.set_encoding("euc-jp")
- self.assertRaises(text_layout.CanNotDisplayText,
- text_layout.default_layout.calculate_text_segments,
- b'\xA1\xA1', 1, 'space' )
- urwid.set_encoding("utf-8")
- self.assertRaises(text_layout.CanNotDisplayText,
- text_layout.default_layout.calculate_text_segments,
- b'\xe9\xa2\x96', 1, 'space' )
+ with encoding("euc-jp"):
+ self.assertRaises(
+ text_layout.CanNotDisplayText,
+ text_layout.default_layout.calculate_text_segments,
+ b'\xA1\xA1', 1, 'space'
+ )
+ with encoding("utf-8"):
+ self.assertRaises(
+ text_layout.CanNotDisplayText,
+ text_layout.default_layout.calculate_text_segments,
+ b'\xe9\xa2\x96', 1, 'space'
+ )
class SubsegTest(unittest.TestCase):
def setUp(self):
+ self.old_encoding = urwid.util._target_encoding
urwid.set_encoding("euc-jp")
+ def tearDown(self) -> None:
+ urwid.set_encoding(self.old_encoding)
+
def st(self, seg, text, start, end, exp):
text = text.encode('iso8859-1')
s = urwid.LayoutSegment(seg)
@@ -151,9 +184,13 @@ class SubsegTest(unittest.TestCase):
class CalcTranslateTest:
- def setUp(self):
+ def setUp(self) -> None:
+ self.old_encoding = urwid.util._target_encoding
urwid.set_encoding("utf-8")
+ def tearDown(self) -> None:
+ urwid.set_encoding(self.old_encoding)
+
def test1_left(self):
result = urwid.default_layout.layout( self.text,
self.width, 'left', self.mode)
@@ -225,9 +262,13 @@ class CalcTranslateWordTest2(CalcTranslateTest, unittest.TestCase):
class CalcTranslateWordTest3(CalcTranslateTest, unittest.TestCase):
- def setUp(self):
+ def setUp(self) -> None:
+ self.old_encoding = urwid.util._target_encoding
urwid.set_encoding('utf-8')
+ def tearDown(self) -> None:
+ urwid.set_encoding(self.old_encoding)
+
text = b'\xe6\x9b\xbf\xe6\xb4\xbc\n\xe6\xb8\x8e\xe6\xba\x8f\xe6\xbd\xba'
width = 10
mode = 'space'
diff --git a/urwid/tests/test_util.py b/urwid/tests/test_util.py
index 06de8c8..d893eac 100644
--- a/urwid/tests/test_util.py
+++ b/urwid/tests/test_util.py
@@ -8,6 +8,12 @@ from urwid import util
class CalcWidthTest(unittest.TestCase):
+ def setUp(self) -> None:
+ self.old_encoding = urwid.util._target_encoding
+
+ def tearDown(self) -> None:
+ urwid.set_encoding(self.old_encoding)
+
def wtest(self, desc, s, exp):
s = s.encode('iso8859-1')
result = util.calc_width( s, 0, len(s))
@@ -29,6 +35,12 @@ class CalcWidthTest(unittest.TestCase):
class ConvertDecSpecialTest(unittest.TestCase):
+ def setUp(self) -> None:
+ self.old_encoding = urwid.util._target_encoding
+
+ def tearDown(self) -> None:
+ urwid.set_encoding(self.old_encoding)
+
def ctest(self, desc, s, exp, expcs):
exp = exp.encode('iso8859-1')
util.set_encoding('ascii')
@@ -51,8 +63,12 @@ class ConvertDecSpecialTest(unittest.TestCase):
class WithinDoubleByteTest(unittest.TestCase):
def setUp(self):
+ self.old_encoding = urwid.util._target_encoding
urwid.set_encoding("euc-jp")
+ def tearDown(self) -> None:
+ urwid.set_encoding(self.old_encoding)
+
def wtest(self, s, ls, pos, expected, desc):
result = util.within_double_byte(s.encode('iso8859-1'), ls, pos)
assert result==expected, f"{desc} got:{result!r} expected: {expected!r}"
@@ -88,6 +104,12 @@ class WithinDoubleByteTest(unittest.TestCase):
class CalcTextPosTest(unittest.TestCase):
+ def setUp(self) -> None:
+ self.old_encoding = urwid.util._target_encoding
+
+ def tearDown(self) -> None:
+ urwid.set_encoding(self.old_encoding)
+
def ctptest(self, text, tests):
text = text.encode('iso8859-1')
for s,e,p, expected in tests:
diff --git a/urwid/tests/test_widget.py b/urwid/tests/test_widget.py
index 4ea2944..9da2abb 100644
--- a/urwid/tests/test_widget.py
+++ b/urwid/tests/test_widget.py
@@ -1,10 +1,23 @@
from __future__ import annotations
+import contextlib
import unittest
+from collections.abc import Generator
import urwid
+@contextlib.contextmanager
+def encoding(encoding_name: str) -> Generator[None, None, None]:
+ old_encoding = 'ascii' # Default fallback value
+ try:
+ old_encoding = urwid.util._target_encoding
+ urwid.set_encoding(encoding_name)
+ yield
+ finally:
+ urwid.set_encoding(old_encoding)
+
+
class TextTest(unittest.TestCase):
def setUp(self):
self.t = urwid.Text("I walk the\ncity in the night")
@@ -33,10 +46,10 @@ class TextTest(unittest.TestCase):
assert got == expected, f"got: {got!r} expected: {expected!r}"
def test5_encode_error(self):
- urwid.set_encoding("ascii")
- expected = [b"? "]
- got = urwid.Text('û').render((3,))._text
- assert got == expected, f"got: {got!r} expected: {expected!r}"
+ with encoding("ascii"):
+ expected = [b"? "]
+ got = urwid.Text('û').render((3,))._text
+ assert got == expected, f"got: {got!r} expected: {expected!r}"
class EditTest(unittest.TestCase):
@@ -87,12 +100,12 @@ class EditTest(unittest.TestCase):
self.ktest(self.t3,'down','down',15,"down at bottom")
def test_utf8_input(self):
- urwid.set_encoding("utf-8")
- self.t1.set_edit_text('')
- self.t1.keypress((12,), 'û')
- self.assertEqual(self.t1.edit_text, 'û'.encode())
- self.t4.keypress((12,), 'û')
- self.assertEqual(self.t4.edit_text, 'û')
+ with encoding("utf-8"):
+ self.t1.set_edit_text('')
+ self.t1.keypress((12,), 'û')
+ self.assertEqual(self.t1.edit_text, 'û'.encode())
+ self.t4.keypress((12,), 'û')
+ self.assertEqual(self.t4.edit_text, 'û')
class EditRenderTest(unittest.TestCase):