diff --git a/qtconsole/comms.py b/qtconsole/comms.py index 74f8ce5c..6ea9a693 100644 --- a/qtconsole/comms.py +++ b/qtconsole/comms.py @@ -11,12 +11,12 @@ from traitlets.config import LoggingConfigurable -from ipython_genutils.importstring import import_item import uuid from qtpy import QtCore from qtconsole.util import MetaQObjectHasTraits, SuperQObject +from .util import import_item class CommManager(MetaQObjectHasTraits( diff --git a/qtconsole/completion_html.py b/qtconsole/completion_html.py index 62391112..11250894 100644 --- a/qtconsole/completion_html.py +++ b/qtconsole/completion_html.py @@ -3,9 +3,9 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -import ipython_genutils.text as text from qtpy import QtCore, QtGui, QtWidgets +from .util import compute_item_matrix #-------------------------------------------------------------------------- # Return an HTML table with selected item in a special class @@ -311,9 +311,8 @@ def show_items(self, cursor, items, prefix_length=0): width = self._text_edit.document().textWidth() char_width = self._console_widget._get_font_width() displaywidth = int(max(10, (width / char_width) - 1)) - items_m, ci = text.compute_item_matrix(items, empty=' ', - displaywidth=displaywidth) - self._sliding_interval = SlidingInterval(len(items_m)-1, width=self._rows) + items_m, ci = compute_item_matrix(items, empty=" ", displaywidth=displaywidth) + self._sliding_interval = SlidingInterval(len(items_m) - 1, width=self._rows) self._items = items_m self._size = (ci['rows_numbers'], ci['columns_numbers']) diff --git a/qtconsole/completion_plain.py b/qtconsole/completion_plain.py index 6596c907..0f19cfb4 100644 --- a/qtconsole/completion_plain.py +++ b/qtconsole/completion_plain.py @@ -4,7 +4,7 @@ # Distributed under the terms of the Modified BSD License. from qtpy import QtCore, QtGui, QtWidgets -import ipython_genutils.text as text +from .util import columnize class CompletionPlain(QtWidgets.QWidget): @@ -53,7 +53,7 @@ def show_items(self, cursor, items, prefix_length=0): if not items : return self.cancel_completion() - strng = text.columnize(items) + strng = columnize(items) # Move cursor to start of the prefix to replace it # when a item is selected cursor.movePosition(QtGui.QTextCursor.Left, n=prefix_length) diff --git a/qtconsole/console_widget.py b/qtconsole/console_widget.py index 447be581..a4cdff6a 100644 --- a/qtconsole/console_widget.py +++ b/qtconsole/console_widget.py @@ -19,7 +19,6 @@ from qtconsole.rich_text import HtmlExporter from qtconsole.util import MetaQObjectHasTraits, get_font, superQ -from ipython_genutils.text import columnize from traitlets.config.configurable import LoggingConfigurable from traitlets import Bool, Enum, Integer, Unicode @@ -28,6 +27,7 @@ from .completion_html import CompletionHtml from .completion_plain import CompletionPlain from .kill_ring import QtKillRing +from .util import columnize def is_letter_or_number(char): diff --git a/qtconsole/frontend_widget.py b/qtconsole/frontend_widget.py index d7d1134d..9cc96a59 100644 --- a/qtconsole/frontend_widget.py +++ b/qtconsole/frontend_widget.py @@ -9,7 +9,6 @@ import re from qtpy import QtCore, QtGui, QtWidgets -from ipython_genutils.importstring import import_item from qtconsole.base_frontend_mixin import BaseFrontendMixin from traitlets import Any, Bool, Instance, Unicode, DottedObjectName, default @@ -17,6 +16,7 @@ from .call_tip_widget import CallTipWidget from .history_console_widget import HistoryConsoleWidget from .pygments_highlighter import PygmentsHighlighter +from .util import import_item class FrontendHighlighter(PygmentsHighlighter): diff --git a/qtconsole/util.py b/qtconsole/util.py index 7c1a116d..ce55e74f 100644 --- a/qtconsole/util.py +++ b/qtconsole/util.py @@ -106,3 +106,215 @@ def get_font(family, fallback=None): if fallback is not None and font_info.family() != family: font = QtGui.QFont(fallback) return font + + +# ----------------------------------------------------------------------------- +# Vendored from ipython_genutils +# ----------------------------------------------------------------------------- +def _col_chunks(l, max_rows, row_first=False): + """Yield successive max_rows-sized column chunks from l.""" + if row_first: + ncols = (len(l) // max_rows) + (len(l) % max_rows > 0) + for i in range(ncols): + yield [l[j] for j in range(i, len(l), ncols)] + else: + for i in range(0, len(l), max_rows): + yield l[i : (i + max_rows)] + + +def _find_optimal(rlist, row_first=False, separator_size=2, displaywidth=80): + """Calculate optimal info to columnize a list of string""" + for max_rows in range(1, len(rlist) + 1): + col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first))) + sumlength = sum(col_widths) + ncols = len(col_widths) + if sumlength + separator_size * (ncols - 1) <= displaywidth: + break + return { + "num_columns": ncols, + "optimal_separator_width": (displaywidth - sumlength) // (ncols - 1) + if (ncols - 1) + else 0, + "max_rows": max_rows, + "column_widths": col_widths, + } + + +def _get_or_default(mylist, i, default=None): + """return list item number, or default if don't exist""" + if i >= len(mylist): + return default + else: + return mylist[i] + + +def compute_item_matrix(items, row_first=False, empty=None, *args, **kwargs): + """Returns a nested list, and info to columnize items + + Parameters + ---------- + items + list of strings to columize + row_first : (default False) + Whether to compute columns for a row-first matrix instead of + column-first (default). + empty : (default None) + default value to fill list if needed + separator_size : int (default=2) + How much characters will be used as a separation between each columns. + displaywidth : int (default=80) + The width of the area onto which the columns should enter + + Returns + ------- + strings_matrix + nested list of string, the outer most list contains as many list as + rows, the innermost lists have each as many element as columns. If the + total number of elements in `items` does not equal the product of + rows*columns, the last element of some lists are filled with `None`. + dict_info + some info to make columnize easier: + + num_columns + number of columns + max_rows + maximum number of rows (final number may be less) + column_widths + list of with of each columns + optimal_separator_width + best separator width between columns + + Examples + -------- + :: + + In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l'] + In [2]: list, info = compute_item_matrix(l, displaywidth=12) + In [3]: list + Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]] + In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5} + In [5]: all((info[k] == ideal[k] for k in ideal.keys())) + Out[5]: True + """ + info = _find_optimal(list(map(len, items)), row_first, *args, **kwargs) + nrow, ncol = info["max_rows"], info["num_columns"] + if row_first: + return ( + [ + [ + _get_or_default(items, r * ncol + c, default=empty) + for c in range(ncol) + ] + for r in range(nrow) + ], + info, + ) + else: + return ( + [ + [ + _get_or_default(items, c * nrow + r, default=empty) + for c in range(ncol) + ] + for r in range(nrow) + ], + info, + ) + + +def columnize(items, row_first=False, separator=" ", displaywidth=80, spread=False): + """Transform a list of strings into a single string with columns. + + Parameters + ---------- + items : sequence of strings + The strings to process. + row_first : (default False) + Whether to compute columns for a row-first matrix instead of + column-first (default). + 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. + """ + if not items: + return "\n" + matrix, info = compute_item_matrix( + items, + row_first=row_first, + separator_size=len(separator), + displaywidth=displaywidth, + ) + if spread: + separator = separator.ljust(int(info["optimal_separator_width"])) + fmatrix = [filter(None, x) for x in matrix] + sjoin = lambda x: separator.join( + [y.ljust(w, " ") for y, w in zip(x, info["column_widths"])] + ) + return "\n".join(map(sjoin, fmatrix)) + "\n" + + +def get_text_list(list_, last_sep=" and ", sep=", ", wrap_item_with=""): + """ + Return a string with a natural enumeration of items + + >>> get_text_list(['a', 'b', 'c', 'd']) + 'a, b, c and d' + >>> get_text_list(['a', 'b', 'c'], ' or ') + 'a, b or c' + >>> get_text_list(['a', 'b', 'c'], ', ') + 'a, b, c' + >>> get_text_list(['a', 'b'], ' or ') + 'a or b' + >>> get_text_list(['a']) + 'a' + >>> get_text_list([]) + '' + >>> get_text_list(['a', 'b'], wrap_item_with="`") + '`a` and `b`' + >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ") + 'a + b + c = d' + """ + if len(list_) == 0: + return "" + if wrap_item_with: + list_ = ["%s%s%s" % (wrap_item_with, item, wrap_item_with) for item in list_] + if len(list_) == 1: + return list_[0] + return "%s%s%s" % (sep.join(i for i in list_[:-1]), last_sep, list_[-1]) + + +def import_item(name): + """Import and return ``bar`` given the string ``foo.bar``. + + Calling ``bar = import_item("foo.bar")`` is the functional equivalent of + executing the code ``from foo import bar``. + + Parameters + ---------- + name : string + The fully qualified name of the module/package being imported. + + Returns + ------- + mod : module object + The module that was imported. + """ + + parts = name.rsplit(".", 1) + if len(parts) == 2: + # called with 'foo.bar....' + package, obj = parts + module = __import__(package, fromlist=[obj]) + try: + pak = getattr(module, obj) + except AttributeError: + raise ImportError("No module named %s" % obj) + return pak + else: + # called with un-dotted string + return __import__(parts[0])