Skip to content
This repository

Fix columnize bug, where tab completion with very long filenames would crash Qt console #696

Merged
merged 3 commits into from over 2 years ago

1 participant

Fernando Perez
Fernando Perez
Owner

Reported on list: http://mail.scipy.org/pipermail/ipython-user/2011-August/008184.html

Thanks to Mani Chandra for the report and Julian for the script to reproduce the bug.

added some commits
Fernando Perez Move columnization code out of GUI code so we can test it better. b46acf0
Fernando Perez Add failing test: columnize called with very long entries.
Bug reported on-list.
371bb8f
Fernando Perez Fix bug where tab-completion with very long filenames would crash the…
… qt console.

Any time tab completion was activated with a filename in the path
longer than the width of the window, the console would crash.
d4d04ba
Fernando Perez
Owner

Reviewed in detail on IRC channel, @minrk signs off on it so I'm merging it now.

Fernando Perez fperez merged commit aa7247e into from
Fernando Perez fperez closed this
Fernando Perez fperez referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 3 unique commits by 1 author.

Aug 12, 2011
Fernando Perez Move columnization code out of GUI code so we can test it better. b46acf0
Fernando Perez Add failing test: columnize called with very long entries.
Bug reported on-list.
371bb8f
Fernando Perez Fix bug where tab-completion with very long filenames would crash the…
… qt console.

Any time tab completion was activated with a filename in the path
longer than the width of the window, the console would crash.
d4d04ba
This page is out of date. Refresh to see the latest.
48  IPython/frontend/qt/console/console_widget.py
@@ -19,6 +19,7 @@
19 19
 from IPython.config.configurable import Configurable
20 20
 from IPython.frontend.qt.rich_text import HtmlExporter
21 21
 from IPython.frontend.qt.util import MetaQObjectHasTraits, get_font
  22
+from IPython.utils.text import columnize
22 23
 from IPython.utils.traitlets import Bool, Enum, Int, Unicode
23 24
 from ansi_code_processor import QtAnsiCodeProcessor
24 25
 from completion_widget import CompletionWidget
@@ -1295,52 +1296,7 @@ def _format_as_columns(self, items, separator='  '):
1295 1296
         width = self._control.viewport().width()
1296 1297
         char_width = QtGui.QFontMetrics(self.font).width(' ')
1297 1298
         displaywidth = max(10, (width / char_width) - 1)
1298  
-
1299  
-        # Some degenerate cases.
1300  
-        size = len(items)
1301  
-        if size == 0: 
1302  
-            return '\n'
1303  
-        elif size == 1:
1304  
-            return '%s\n' % items[0]
1305  
-
1306  
-        # Try every row count from 1 upwards
1307  
-        array_index = lambda nrows, row, col: nrows*col + row
1308  
-        for nrows in range(1, size):
1309  
-            ncols = (size + nrows - 1) // nrows
1310  
-            colwidths = []
1311  
-            totwidth = -len(separator)
1312  
-            for col in range(ncols):
1313  
-                # Get max column width for this column
1314  
-                colwidth = 0
1315  
-                for row in range(nrows):
1316  
-                    i = array_index(nrows, row, col)
1317  
-                    if i >= size: break
1318  
-                    x = items[i]
1319  
-                    colwidth = max(colwidth, len(x))
1320  
-                colwidths.append(colwidth)
1321  
-                totwidth += colwidth + len(separator)
1322  
-                if totwidth > displaywidth: 
1323  
-                    break
1324  
-            if totwidth <= displaywidth: 
1325  
-                break
1326  
-
1327  
-        # The smallest number of rows computed and the max widths for each
1328  
-        # column has been obtained. Now we just have to format each of the rows.
1329  
-        string = ''
1330  
-        for row in range(nrows):
1331  
-            texts = []
1332  
-            for col in range(ncols):
1333  
-                i = row + nrows*col
1334  
-                if i >= size:
1335  
-                    texts.append('')
1336  
-                else:
1337  
-                    texts.append(items[i])
1338  
-            while texts and not texts[-1]:
1339  
-                del texts[-1]
1340  
-            for col in range(len(texts)):
1341  
-                texts[col] = texts[col].ljust(colwidths[col])
1342  
-            string += '%s\n' % separator.join(texts)
1343  
-        return string
  1299
+        return columnize(items, separator, displaywidth)
1344 1300
 
1345 1301
     def _get_block_plain_text(self, block):
1346 1302
         """ Given a QTextBlock, return its unformatted text.
44  IPython/utils/tests/test_text.py
... ...
@@ -0,0 +1,44 @@
  1
+# encoding: utf-8
  2
+"""Tests for IPython.utils.text"""
  3
+
  4
+#-----------------------------------------------------------------------------
  5
+#  Copyright (C) 2011  The IPython Development Team
  6
+#
  7
+#  Distributed under the terms of the BSD License.  The full license is in
  8
+#  the file COPYING, distributed as part of this software.
  9
+#-----------------------------------------------------------------------------
  10
+
  11
+#-----------------------------------------------------------------------------
  12
+# Imports
  13
+#-----------------------------------------------------------------------------
  14
+
  15
+import os
  16
+
  17
+import nose.tools as nt
  18
+
  19
+from nose import with_setup
  20
+
  21
+from IPython.testing import decorators as dec
  22
+from IPython.utils import text
  23
+
  24
+#-----------------------------------------------------------------------------
  25
+# Globals
  26
+#-----------------------------------------------------------------------------
  27
+
  28
+def test_columnize():
  29
+    """Basic columnize tests."""
  30
+    size = 5
  31
+    items = [l*size for l in 'abc']
  32
+    out = text.columnize(items, displaywidth=80)
  33
+    nt.assert_equals(out, 'aaaaa  bbbbb  ccccc\n')
  34
+    out = text.columnize(items, displaywidth=10)
  35
+    nt.assert_equals(out, 'aaaaa  ccccc\nbbbbb\n')
  36
+
  37
+
  38
+def test_columnize_long():
  39
+    """Test columnize with inputs longer than the display window"""
  40
+    text.columnize(['a'*81, 'b'*81], displaywidth=80)
  41
+    size = 11
  42
+    items = [l*size for l in 'abc']
  43
+    out = text.columnize(items, displaywidth=size-1)
  44
+    nt.assert_equals(out, '\n'.join(items+['']))
73  IPython/utils/text.py
@@ -611,3 +611,76 @@ def get_value(self, key, args, kwargs):
611 611
                 raise KeyError(key)
612 612
 
613 613
 
  614
+def columnize(items, separator='  ', displaywidth=80):
  615
+    """ Transform a list of strings into a single string with columns.
  616
+
  617
+    Parameters
  618
+    ----------
  619
+    items : sequence of strings
  620
+        The strings to process.
  621
+
  622
+    separator : str, optional [default is two spaces]
  623
+        The string that separates columns.
  624
+
  625
+    displaywidth : int, optional [default is 80]
  626
+        Width of the display in number of characters.
  627
+    
  628
+    Returns
  629
+    -------
  630
+    The formatted string.
  631
+    """
  632
+    # Note: this code is adapted from columnize 0.3.2.
  633
+    # See http://code.google.com/p/pycolumnize/
  634
+
  635
+    # Some degenerate cases.
  636
+    size = len(items)
  637
+    if size == 0: 
  638
+        return '\n'
  639
+    elif size == 1:
  640
+        return '%s\n' % items[0]
  641
+
  642
+    # Special case: if any item is longer than the maximum width, there's no
  643
+    # point in triggering the logic below...
  644
+    item_len = map(len, items) # save these, we can reuse them below
  645
+    longest = max(item_len)
  646
+    if longest >= displaywidth:
  647
+        return '\n'.join(items+[''])
  648
+
  649
+    # Try every row count from 1 upwards
  650
+    array_index = lambda nrows, row, col: nrows*col + row
  651
+    for nrows in range(1, size):
  652
+        ncols = (size + nrows - 1) // nrows
  653
+        colwidths = []
  654
+        totwidth = -len(separator)
  655
+        for col in range(ncols):
  656
+            # Get max column width for this column
  657
+            colwidth = 0
  658
+            for row in range(nrows):
  659
+                i = array_index(nrows, row, col)
  660
+                if i >= size: break
  661
+                x, len_x = items[i], item_len[i]
  662
+                colwidth = max(colwidth, len_x)
  663
+            colwidths.append(colwidth)
  664
+            totwidth += colwidth + len(separator)
  665
+            if totwidth > displaywidth: 
  666
+                break
  667
+        if totwidth <= displaywidth: 
  668
+            break
  669
+
  670
+    # The smallest number of rows computed and the max widths for each
  671
+    # column has been obtained. Now we just have to format each of the rows.
  672
+    string = ''
  673
+    for row in range(nrows):
  674
+        texts = []
  675
+        for col in range(ncols):
  676
+            i = row + nrows*col
  677
+            if i >= size:
  678
+                texts.append('')
  679
+            else:
  680
+                texts.append(items[i])
  681
+        while texts and not texts[-1]:
  682
+            del texts[-1]
  683
+        for col in range(len(texts)):
  684
+            texts[col] = texts[col].ljust(colwidths[col])
  685
+        string += '%s\n' % separator.join(texts)
  686
+    return string
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.