diff --git a/IPython/frontend/qt/console/completion_html.py b/IPython/frontend/qt/console/completion_html.py index 0875627..852fcf3 100644 --- a/IPython/frontend/qt/console/completion_html.py +++ b/IPython/frontend/qt/console/completion_html.py @@ -1,7 +1,116 @@ +"""a navigable completer for the qtconsole""" +# coding : utf-8 +#----------------------------------------------------------------------------- +# Copyright (c) 2012, IPython Development Team.$ +# +# Distributed under the terms of the Modified BSD License.$ +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + # System library imports +import IPython.utils.text as text + from IPython.external.qt import QtCore, QtGui -import IPython.utils.html_utils as html_utils +#-------------------------------------------------------------------------- +# Return an HTML table with selected item in a special class +#-------------------------------------------------------------------------- +def html_tableify(item_matrix, select=None, header=None , footer=None) : + """ returnr a string for an html table""" + if not item_matrix : + return '' + html_cols = [] + tds = lambda text : u''+text+u' ' + trs = lambda text : u''+text+u'' + tds_items = [map(tds, row) for row in item_matrix] + if select : + row, col = select + tds_items[row][col] = u''\ + +item_matrix[row][col]\ + +u' ' + #select the right item + html_cols = map(trs, (u''.join(row) for row in tds_items)) + head = '' + foot = '' + if header : + head = (u''\ + +''.join((u''+header+u'')*len(item_matrix[0]))\ + +'') + + if footer : + foot = (u''\ + +''.join((u''+footer+u'')*len(item_matrix[0]))\ + +'') + html = (u''+head+(u''.join(html_cols))+foot+u'
') + return html + +class SlidingInterval(object): + """a bound interval that follows a cursor + + internally used to scoll the completion view when the cursor + try to go beyond the edges, and show '...' when rows are hidden + """ + + _min = 0 + _max = 1 + _current = 0 + def __init__(self, maximum=1, width=6, minimum=0, sticky_lenght=1): + """Create a new bounded interval + + any value return by this will be bound between maximum and + minimum. usual width will be 'width', and sticky_length + set when the return interval should expand to max and min + """ + self._min = minimum + self._max = maximum + self._start = 0 + self._width = width + self._stop = self._start+self._width+1 + self._sticky_lenght = sticky_lenght + + @property + def current(self): + """current cursor position""" + return self._current + + @current.setter + def current(self, value): + """set current cursor position""" + current = min(max(self._min, value), self._max) + + self._current = current + + if current > self._stop : + self._stop = current + self._start = current-self._width + elif current < self._start : + self._start = current + self._stop = current + self._width + + if abs(self._start - self._min) <= self._sticky_lenght : + self._start = self._min + + if abs(self._stop - self._max) <= self._sticky_lenght : + self._stop = self._max + + @property + def start(self): + """begiiing of interval to show""" + return self._start + + @property + def stop(self): + """end of interval to show""" + return self._stop + + @property + def width(self): + return self._stop - self._start + + @property + def nth(self): + return self.current - self.start class CompletionHtml(QtGui.QWidget): """ A widget for tab completion, navigable by arrow keys """ @@ -16,6 +125,8 @@ class CompletionHtml(QtGui.QWidget): _size = (1, 1) _old_cursor = None _start_position = 0 + _slice_start = 0 + _slice_len = 4 def __init__(self, console_widget): """ Create a completion widget that is attached to the specified Qt @@ -27,6 +138,8 @@ class CompletionHtml(QtGui.QWidget): self._text_edit = console_widget._control self._console_widget = console_widget self._text_edit.installEventFilter(self) + self._sliding_interval = None + self._justified_items = None # Ensure that the text edit keeps focus when widget is displayed. self.setFocusProxy(self._text_edit) @@ -71,24 +184,36 @@ class CompletionHtml(QtGui.QWidget): self.select_left() self._update_list() return True + elif key in ( QtCore.Qt.Key_Escape,): + self.cancel_completion() + return True else : - self._cancel_completion() + self.cancel_completion() else: - self._cancel_completion() + self.cancel_completion() elif etype == QtCore.QEvent.FocusOut: - self._cancel_completion() + self.cancel_completion() return super(CompletionHtml, self).eventFilter(obj, event) #-------------------------------------------------------------------------- # 'CompletionHtml' interface #-------------------------------------------------------------------------- - def _cancel_completion(self): - """Cancel the completion, reseting internal variable, clearing buffer """ + def cancel_completion(self): + """Cancel the completion + + should be called when the completer have to be dismissed + + This reset internal variable, clearing the temporary buffer + of the console where the completion are shown. + """ self._consecutive_tab = 0 + self._slice_start = 0 self._console_widget._clear_temporary_buffer() self._index = (0, 0) + if(self._sliding_interval): + self._sliding_interval = None # # ... 2 4 4 4 4 4 4 4 4 4 4 4 4 @@ -147,6 +272,12 @@ class CompletionHtml(QtGui.QWidget): have gone before : %d:%d (%d:%d)"%(row, col, nr, nc) ) + @property + def _slice_end(self): + end = self._slice_start+self._slice_len + if end > len(self._items) : + return None + return end def select_up(self): """move cursor up""" @@ -176,27 +307,42 @@ class CompletionHtml(QtGui.QWidget): return self._start_position = cursor.position() self._consecutive_tab = 1 - ci = html_utils.columnize_info(items, empty=' ') - self._items = ci['item_matrix'] - self._size = (ci['rows_number'], ci['columns_number']) + items_m, ci = text.compute_item_matrix(items, empty=' ') + self._sliding_interval = SlidingInterval(len(items_m)-1) + + self._items = items_m + self._size = (ci['rows_numbers'], ci['columns_numbers']) self._old_cursor = cursor self._index = (0, 0) + sjoin = lambda x : [ y.ljust(w, ' ') for y, w in zip(x, ci['columns_width'])] + self._justified_items = map(sjoin, items_m) self._update_list(hilight=False) + + def _update_list(self, hilight=True): """ update the list of completion and hilight the currently selected completion """ - if len(self._items) > 100: - items = self._items[:100] - else : - items = self._items - items_m = items + self._sliding_interval.current = self._index[0] + head = None + foot = None + if self._sliding_interval.start > 0 : + head = '...' + + if self._sliding_interval.stop < self._sliding_interval._max: + foot = '...' + items_m = self._justified_items[\ + self._sliding_interval.start:\ + self._sliding_interval.stop+1\ + ] self._console_widget._clear_temporary_buffer() if(hilight): - strng = html_utils.html_tableify(items_m, select=self._index) - else: - strng = html_utils.html_tableify(items_m, select=None) + sel = (self._sliding_interval.nth, self._index[1]) + else : + sel = None + + strng = html_tableify(items_m, select=sel, header=head, footer=foot) self._console_widget._fill_temporary_buffer(self._old_cursor, strng, html=True) #-------------------------------------------------------------------------- @@ -211,7 +357,7 @@ class CompletionHtml(QtGui.QWidget): item = item.strip() if item : self._current_text_cursor().insertText(item) - self._cancel_completion() + self.cancel_completion() def _current_text_cursor(self): """ Returns a cursor with text between the start position and the @@ -223,4 +369,3 @@ class CompletionHtml(QtGui.QWidget): QtGui.QTextCursor.KeepAnchor) return cursor - diff --git a/IPython/frontend/qt/console/completion_plain.py b/IPython/frontend/qt/console/completion_plain.py index 8db0069..d6b4066 100644 --- a/IPython/frontend/qt/console/completion_plain.py +++ b/IPython/frontend/qt/console/completion_plain.py @@ -1,6 +1,15 @@ +"""a simple completer for the qtconsole""" +#----------------------------------------------------------------------------- +# Copyright (c) 2012, IPython Development Team.$ +# +# Distributed under the terms of the Modified BSD License.$ +# +# The full license is in the file COPYING.txt, distributed with this software. +#------------------------------------------------------------------- + # System library imports from IPython.external.qt import QtCore, QtGui -import IPython.utils.html_utils as html_utils +import IPython.utils.text as text class CompletionPlain(QtGui.QWidget): @@ -10,10 +19,6 @@ class CompletionPlain(QtGui.QWidget): # 'QObject' interface #-------------------------------------------------------------------------- - _items = () - _index = (0, 0) - _old_cursor = None - def __init__(self, console_widget): """ Create a completion widget that is attached to the specified Qt text edit widget. @@ -33,18 +38,17 @@ class CompletionPlain(QtGui.QWidget): if obj == self._text_edit: etype = event.type() - if etype == QtCore.QEvent.KeyPress: - self._cancel_completion() + if etype in( QtCore.QEvent.KeyPress, QtCore.QEvent.FocusOut ): + self.cancel_completion() return super(CompletionPlain, self).eventFilter(obj, event) #-------------------------------------------------------------------------- # 'CompletionPlain' interface #-------------------------------------------------------------------------- - def _cancel_completion(self): + def cancel_completion(self): """Cancel the completion, reseting internal variable, clearing buffer """ self._console_widget._clear_temporary_buffer() - self._index = (0, 0) def show_items(self, cursor, items): @@ -53,21 +57,6 @@ class CompletionPlain(QtGui.QWidget): """ if not items : return - - ci = html_utils.columnize_info(items, empty=' ') - self._items = ci['item_matrix'] - self._old_cursor = cursor - self._update_list() - - - def _update_list(self): - """ update the list of completion and hilight the currently selected completion """ - if len(self._items) > 100: - items = self._items[:100] - else : - items = self._items - items_m = items - - self._console_widget._clear_temporary_buffer() - strng = html_utils.html_tableify(items_m, select=None) - self._console_widget._fill_temporary_buffer(self._old_cursor, strng, html=True) + self.cancel_completion() + strng = text.columnize(items) + self._console_widget._fill_temporary_buffer(cursor, strng, html=False) diff --git a/IPython/frontend/qt/console/completion_widget.py b/IPython/frontend/qt/console/completion_widget.py index 8103f05..b258775 100644 --- a/IPython/frontend/qt/console/completion_widget.py +++ b/IPython/frontend/qt/console/completion_widget.py @@ -134,5 +134,5 @@ class CompletionWidget(QtGui.QListWidget): else: self.hide() - def _cancel_completion(self): + def cancel_completion(self): self.hide() diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py index 75bb12e..cd2a93c 100644 --- a/IPython/frontend/qt/console/console_widget.py +++ b/IPython/frontend/qt/console/console_widget.py @@ -843,7 +843,7 @@ class ConsoleWidget(LoggingConfigurable, QtGui.QWidget): def _cancel_completion(self): """ If text completion is progress, cancel it. """ - self._completion_widget._cancel_completion() + self._completion_widget.cancel_completion() def _clear_temporary_buffer(self): """ Clears the "temporary text" buffer, i.e. all the text following diff --git a/IPython/frontend/qt/console/qtconsoleapp.py b/IPython/frontend/qt/console/qtconsoleapp.py index e1eac4c..1735d9b 100644 --- a/IPython/frontend/qt/console/qtconsoleapp.py +++ b/IPython/frontend/qt/console/qtconsoleapp.py @@ -105,13 +105,6 @@ qt_flags = { "Disable rich text support."), } -# not quite sure on how this works -#qt_flags.update(boolean_flag( -# 'gui-completion', 'ConsoleWidget.gui_completion', -# "use a GUI widget for tab completion", -# "use plaintext output for completion" -#)) - # and app_flags from the Console Mixin qt_flags.update(app_flags) # add frontend flags to the full set diff --git a/IPython/utils/html_utils.py b/IPython/utils/html_utils.py deleted file mode 100644 index aaf2f2b..0000000 --- a/IPython/utils/html_utils.py +++ /dev/null @@ -1,124 +0,0 @@ -"""some html utilis""" -from IPython.core.display import HTML - - -def columnize_info(items, separator_width=1, displaywidth=80, empty=None): - """ Get info on a list of string to display it as a multicolumns list - - returns : - --------- - - a dict containing several parameters: - - 'item_matrix' : list of list with the innermost list representing a row - 'columns_number': number of columns - 'rows_number' : number of rown - 'columns_width' : a list indicating the maximum length of the element in each columns - - Parameters : - ------------ - separator_width : when trying to ajust the number of column, consider a separator size of this much caracters - displaywidth : try to fit the columns in this width - empty : if the number of items is different from nrows * ncols, fill with empty - - """ - # 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 {'item_matrix' :[[empty]], - 'columns_number':1, - 'rows_number':1, - 'columns_width':[0]} - elif size == 1: - return {'item_matrix' :[[items[0]]], - 'columns_number':1, - 'rows_number':1, - 'columns_width':[len(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 (items, [longest]) - - # Try every row count from 1 upwards - array_index = lambda nrows, row, col: nrows*col + row - nrows = 1 - for nrows in range(1, size): - ncols = (size + nrows - 1) // nrows - colwidths = [] - totwidth = -separator_width - 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 - len_x = item_len[i] - colwidth = max(colwidth, len_x) - colwidths.append(colwidth) - totwidth += colwidth + separator_width - 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. - reorderd_items = [] - for row in range(nrows): - texts = [] - for col in range(ncols): - i = row + nrows*col - if i >= size: - texts.append(empty) - 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]) - reorderd_items.append(texts) - - return {'item_matrix' :reorderd_items, - 'columns_number':ncols, - 'rows_number':nrows, - 'columns_width':colwidths} - - -def column_table(items, select=None) : - """ return a html table of the item with a select class on one""" - items_m = columnize_info(items)['item_matrix'] - return HTML(html_tableify(items_m, select=select)) - -def html_tableify(item_matrix, select=None) : - """ returnr a string for an html table""" - if not item_matrix : - return '' - html_cols = [] - tds = lambda text : u''+text+u'' - trs = lambda text : u''+text+u'' - tds_items = [map(tds, row) for row in item_matrix ] - if select : - row, col = select - try : - tds_items[row][col] = u''\ - +item_matrix[row][col]\ - +u'' - except IndexError : - pass - #select the right item - html_cols = map(trs, (u''.join(row) for row in tds_items)) - html = (u''+(u''.join(html_cols))+u'
') - css = u""" - - """ - return css+html diff --git a/IPython/utils/text.py b/IPython/utils/text.py index 85db442..0df0bec 100644 --- a/IPython/utils/text.py +++ b/IPython/utils/text.py @@ -690,7 +690,7 @@ def _get_or_default(mylist, i, default=None): return mylist[i] @skip_doctest -def compute_item_matrix(items, *args, **kwargs) : +def compute_item_matrix(items, empty=None, *args, **kwargs) : """Returns a nested list, and info to columnize items Parameters : @@ -698,6 +698,8 @@ def compute_item_matrix(items, *args, **kwargs) : items : list of strings to columize + empty : (default None) + default value to fill list if needed separator_size : int (default=2) How much caracters will be used as a separation between each columns. displaywidth : int (default=80)