Skip to content
Browse files

Merge pull request #696 from fperez/fix_columnize

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

Thanks to Mani Chandra for reporting on list and Julian Taylor for the script to reproduce the crash.
  • Loading branch information...
2 parents ba8f067 + d4d04ba commit aa7247e8fa25866849a8349eefde5226e19464a1 @fperez fperez committed Aug 17, 2011
Showing with 119 additions and 46 deletions.
  1. +2 −46 IPython/frontend/qt/console/console_widget.py
  2. +44 −0 IPython/utils/tests/test_text.py
  3. +73 −0 IPython/utils/text.py
View
48 IPython/frontend/qt/console/console_widget.py
@@ -19,6 +19,7 @@
from IPython.config.configurable import Configurable
from IPython.frontend.qt.rich_text import HtmlExporter
from IPython.frontend.qt.util import MetaQObjectHasTraits, get_font
+from IPython.utils.text import columnize
from IPython.utils.traitlets import Bool, Enum, Int, Unicode
from ansi_code_processor import QtAnsiCodeProcessor
from completion_widget import CompletionWidget
@@ -1295,52 +1296,7 @@ def _format_as_columns(self, items, separator=' '):
width = self._control.viewport().width()
char_width = QtGui.QFontMetrics(self.font).width(' ')
displaywidth = max(10, (width / char_width) - 1)
-
- # Some degenerate cases.
- size = len(items)
- if size == 0:
- return '\n'
- elif size == 1:
- return '%s\n' % items[0]
-
- # Try every row count from 1 upwards
- array_index = lambda nrows, row, col: nrows*col + row
- for nrows in range(1, size):
- ncols = (size + nrows - 1) // nrows
- colwidths = []
- totwidth = -len(separator)
- for col in range(ncols):
- # Get max column width for this column
- colwidth = 0
- for row in range(nrows):
- i = array_index(nrows, row, col)
- if i >= size: break
- x = items[i]
- colwidth = max(colwidth, len(x))
- colwidths.append(colwidth)
- totwidth += colwidth + len(separator)
- if totwidth > displaywidth:
- break
- if totwidth <= displaywidth:
- break
-
- # The smallest number of rows computed and the max widths for each
- # column has been obtained. Now we just have to format each of the rows.
- string = ''
- for row in range(nrows):
- texts = []
- for col in range(ncols):
- i = row + nrows*col
- if i >= size:
- texts.append('')
- else:
- texts.append(items[i])
- while texts and not texts[-1]:
- del texts[-1]
- for col in range(len(texts)):
- texts[col] = texts[col].ljust(colwidths[col])
- string += '%s\n' % separator.join(texts)
- return string
+ return columnize(items, separator, displaywidth)
def _get_block_plain_text(self, block):
""" Given a QTextBlock, return its unformatted text.
View
44 IPython/utils/tests/test_text.py
@@ -0,0 +1,44 @@
+# encoding: utf-8
+"""Tests for IPython.utils.text"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2011 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+import os
+
+import nose.tools as nt
+
+from nose import with_setup
+
+from IPython.testing import decorators as dec
+from IPython.utils import text
+
+#-----------------------------------------------------------------------------
+# Globals
+#-----------------------------------------------------------------------------
+
+def test_columnize():
+ """Basic columnize tests."""
+ size = 5
+ items = [l*size for l in 'abc']
+ out = text.columnize(items, displaywidth=80)
+ nt.assert_equals(out, 'aaaaa bbbbb ccccc\n')
+ out = text.columnize(items, displaywidth=10)
+ nt.assert_equals(out, 'aaaaa ccccc\nbbbbb\n')
+
+
+def test_columnize_long():
+ """Test columnize with inputs longer than the display window"""
+ text.columnize(['a'*81, 'b'*81], displaywidth=80)
+ size = 11
+ items = [l*size for l in 'abc']
+ out = text.columnize(items, displaywidth=size-1)
+ nt.assert_equals(out, '\n'.join(items+['']))
View
73 IPython/utils/text.py
@@ -611,3 +611,76 @@ def get_value(self, key, args, kwargs):
raise KeyError(key)
+def columnize(items, separator=' ', displaywidth=80):
+ """ Transform a list of strings into a single string with columns.
+
+ Parameters
+ ----------
+ items : sequence of strings
+ The strings to process.
+
+ separator : str, optional [default is two spaces]
+ The string that separates columns.
+
+ displaywidth : int, optional [default is 80]
+ Width of the display in number of characters.
+
+ Returns
+ -------
+ The formatted string.
+ """
+ # Note: this code is adapted from columnize 0.3.2.
+ # See http://code.google.com/p/pycolumnize/
+
+ # Some degenerate cases.
+ size = len(items)
+ if size == 0:
+ return '\n'
+ elif size == 1:
+ return '%s\n' % items[0]
+
+ # Special case: if any item is longer than the maximum width, there's no
+ # point in triggering the logic below...
+ item_len = map(len, items) # save these, we can reuse them below
+ longest = max(item_len)
+ if longest >= displaywidth:
+ return '\n'.join(items+[''])
+
+ # Try every row count from 1 upwards
+ array_index = lambda nrows, row, col: nrows*col + row
+ for nrows in range(1, size):
+ ncols = (size + nrows - 1) // nrows
+ colwidths = []
+ totwidth = -len(separator)
+ for col in range(ncols):
+ # Get max column width for this column
+ colwidth = 0
+ for row in range(nrows):
+ i = array_index(nrows, row, col)
+ if i >= size: break
+ x, len_x = items[i], item_len[i]
+ colwidth = max(colwidth, len_x)
+ colwidths.append(colwidth)
+ totwidth += colwidth + len(separator)
+ if totwidth > displaywidth:
+ break
+ if totwidth <= displaywidth:
+ break
+
+ # The smallest number of rows computed and the max widths for each
+ # column has been obtained. Now we just have to format each of the rows.
+ string = ''
+ for row in range(nrows):
+ texts = []
+ for col in range(ncols):
+ i = row + nrows*col
+ if i >= size:
+ texts.append('')
+ else:
+ texts.append(items[i])
+ while texts and not texts[-1]:
+ del texts[-1]
+ for col in range(len(texts)):
+ texts[col] = texts[col].ljust(colwidths[col])
+ string += '%s\n' % separator.join(texts)
+ return string

0 comments on commit aa7247e

Please sign in to comment.
Something went wrong with that request. Please try again.