Repository: inducer/pudb

Issue:
"Canvas text is wider than the maxcol specified" with Chinese
The full script is simple:
```
data = "中文"
```

Run it with
```
pudb3 a.py
```
And press "n" I got this:

```
Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/pudb/__init__.py", line 83, in runscript
    dbg._runscript(mainpyfile)
  File "/usr/lib/python3.6/site-packages/pudb/debugger.py", line 419, in _runscript
    self.run(statement, globals=globals_, locals=locals_)
  File "/usr/lib/python3.6/bdb.py", line 431, in run
    exec(cmd, globals, locals)
  File "<string>", line 1, in <module>
  File "a.py", line 1, in <module>
    data = "中文"
  File "/usr/lib/python3.6/bdb.py", line 52, in trace_dispatch
    return self.dispatch_return(frame, arg)
  File "/usr/lib/python3.6/bdb.py", line 93, in dispatch_return
    self.user_return(frame, arg)
  File "/usr/lib/python3.6/site-packages/pudb/debugger.py", line 385, in user_return
    self.interaction(frame)
  File "/usr/lib/python3.6/site-packages/pudb/debugger.py", line 339, in interaction
    show_exc_dialog=show_exc_dialog)
  File "/usr/lib/python3.6/site-packages/pudb/debugger.py", line 2079, in call_with_ui
    return f(*args, **kwargs)
  File "/usr/lib/python3.6/site-packages/pudb/debugger.py", line 2307, in interaction
    self.event_loop()
  File "/usr/lib/python3.6/site-packages/pudb/debugger.py", line 2265, in event_loop
    canvas = toplevel.render(self.size, focus=True)
  File "/usr/lib/python3.6/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/widget.py", line 1750, in render
    canv = get_delegate(self).render(size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/container.py", line 1083, in render
    focus and self.focus_part == 'body')
  File "/usr/lib/python3.6/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/decoration.py", line 225, in render
    canv = self._original_widget.render(size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/container.py", line 2085, in render
    focus = focus and self.focus_position == i)
  File "/usr/lib/python3.6/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/widget.py", line 1750, in render
    canv = get_delegate(self).render(size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/container.py", line 1526, in render
    canv = w.render((maxcol, rows), focus=focus and item_focus)
  File "/usr/lib/python3.6/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/decoration.py", line 225, in render
    canv = self._original_widget.render(size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/container.py", line 1526, in render
    canv = w.render((maxcol, rows), focus=focus and item_focus)
  File "/usr/lib/python3.6/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/decoration.py", line 225, in render
    canv = self._original_widget.render(size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/widget.py", line 1750, in render
    canv = get_delegate(self).render(size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/usr/lib/python3.6/site-packages/urwid/listbox.py", line 485, in render
    canvas = widget.render((maxcol,))
  File "/usr/lib/python3.6/site-packages/urwid/widget.py", line 141, in cached_render
    canv = fn(self, size, focus=focus)
  File "/usr/lib/python3.6/site-packages/pudb/var_view.py", line 163, in render
    return make_canvas(text, attr, maxcol, apfx+"value")
  File "/usr/lib/python3.6/site-packages/pudb/ui_tools.py", line 48, in make_canvas
    maxcol=maxcol)
  File "/usr/lib/python3.6/site-packages/urwid/canvas.py", line 356, in __init__
    raise CanvasError("Canvas text is wider than the maxcol specified \n%r\n%r\n%r"%(maxcol,widths,text))
urwid.canvas.CanvasError: Canvas text is wider than the maxcol specified 
53
[55]
[b"data: '\xe4\xb8\xad\xe6\x96\x87'                                           "]
```

This is a Python 3.6.0 on Arch Linux, with zh_CN.UTF-8 locale. And pudb is "python-pudb 2017.1.1-1" (the pudb3 script doesn't accept `--version` nor `-V` :-( )

Comments:

Patch:
diff --git a/pudb/ui_tools.py b/pudb/ui_tools.py
index 9997cad..a398865 100644
--- a/pudb/ui_tools.py
+++ b/pudb/ui_tools.py
@@ -1,6 +1,6 @@
 from __future__ import absolute_import, division, print_function
 import urwid
-from urwid.util import _target_encoding
+from urwid.util import _target_encoding, calc_width
 
 
 # generic urwid helpers -------------------------------------------------------
@@ -14,7 +14,7 @@ def make_canvas(txt, attr, maxcol, fill_attr=None):
         # filter out zero-length attrs
         line_attr = [(aname, l) for aname, l in line_attr if l > 0]
 
-        diff = maxcol - len(line)
+        diff = maxcol - calc_width(line, 0, len(line))
         if diff > 0:
             line += " "*diff
             line_attr.append((fill_attr, diff))


Test patch:
diff --git a/test/test_make_canvas.py b/test/test_make_canvas.py
index 093cd63..b1ed681 100644
--- a/test/test_make_canvas.py
+++ b/test/test_make_canvas.py
@@ -49,6 +49,19 @@ def test_byte_boundary():
     )
     assert list(canvas.content()) == [[('var value', None, b'aaaaaa\xc3\xa9')]]
 
+def test_wide_chars():
+    text = u"data: '中文'"
+    canvas = make_canvas(
+        txt=[text],
+        attr=[[('var label', 6), ('var value', 4)]],
+        maxcol=47,
+    )
+    assert list(canvas.content()) == [[
+        ('var label', None, b'data: '),
+        ('var value', None, u"'中文'".encode('utf-8')),
+        (None, None, b' '*(47 - 12)), # 10 chars, 2 of which are double width
+        ]]
+
 
 if __name__ == "__main__":
     import sys