Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

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

Merged
merged 3 commits into from

1 participant

@fperez
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.

fperez added some commits
@fperez fperez Move columnization code out of GUI code so we can test it better. b46acf0
@fperez fperez Add failing test: columnize called with very long entries.
Bug reported on-list.
371bb8f
@fperez fperez 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
@fperez
Owner

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

@fperez fperez merged commit aa7247e into ipython:master
@fperez 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
Commits on Aug 13, 2011
  1. @fperez
  2. @fperez

    Add failing test: columnize called with very long entries.

    fperez authored
    Bug reported on-list.
  3. @fperez

    Fix bug where tab-completion with very long filenames would crash the…

    fperez authored
    … 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.
This page is out of date. Refresh to see the latest.
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
Something went wrong with that request. Please try again.