##// END OF EJS Templates
Merge pull request #1851 from Carreau/newcomp_rebase...
Fernando Perez -
r7395:45d28c54 merge
parent child Browse files
Show More
@@ -0,0 +1,371 b''
1 """a navigable completer for the qtconsole"""
2 # coding : utf-8
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2012, IPython Development Team.$
5 #
6 # Distributed under the terms of the Modified BSD License.$
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
10
11 # System library imports
12 import IPython.utils.text as text
13
14 from IPython.external.qt import QtCore, QtGui
15
16 #--------------------------------------------------------------------------
17 # Return an HTML table with selected item in a special class
18 #--------------------------------------------------------------------------
19 def html_tableify(item_matrix, select=None, header=None , footer=None) :
20 """ returnr a string for an html table"""
21 if not item_matrix :
22 return ''
23 html_cols = []
24 tds = lambda text : u'<td>'+text+u' </td>'
25 trs = lambda text : u'<tr>'+text+u'</tr>'
26 tds_items = [map(tds, row) for row in item_matrix]
27 if select :
28 row, col = select
29 tds_items[row][col] = u'<td class="inverted">'\
30 +item_matrix[row][col]\
31 +u' </td>'
32 #select the right item
33 html_cols = map(trs, (u''.join(row) for row in tds_items))
34 head = ''
35 foot = ''
36 if header :
37 head = (u'<tr>'\
38 +''.join((u'<td>'+header+u'</td>')*len(item_matrix[0]))\
39 +'</tr>')
40
41 if footer :
42 foot = (u'<tr>'\
43 +''.join((u'<td>'+footer+u'</td>')*len(item_matrix[0]))\
44 +'</tr>')
45 html = (u'<table class="completion" style="white-space:pre">'+head+(u''.join(html_cols))+foot+u'</table>')
46 return html
47
48 class SlidingInterval(object):
49 """a bound interval that follows a cursor
50
51 internally used to scoll the completion view when the cursor
52 try to go beyond the edges, and show '...' when rows are hidden
53 """
54
55 _min = 0
56 _max = 1
57 _current = 0
58 def __init__(self, maximum=1, width=6, minimum=0, sticky_lenght=1):
59 """Create a new bounded interval
60
61 any value return by this will be bound between maximum and
62 minimum. usual width will be 'width', and sticky_length
63 set when the return interval should expand to max and min
64 """
65 self._min = minimum
66 self._max = maximum
67 self._start = 0
68 self._width = width
69 self._stop = self._start+self._width+1
70 self._sticky_lenght = sticky_lenght
71
72 @property
73 def current(self):
74 """current cursor position"""
75 return self._current
76
77 @current.setter
78 def current(self, value):
79 """set current cursor position"""
80 current = min(max(self._min, value), self._max)
81
82 self._current = current
83
84 if current > self._stop :
85 self._stop = current
86 self._start = current-self._width
87 elif current < self._start :
88 self._start = current
89 self._stop = current + self._width
90
91 if abs(self._start - self._min) <= self._sticky_lenght :
92 self._start = self._min
93
94 if abs(self._stop - self._max) <= self._sticky_lenght :
95 self._stop = self._max
96
97 @property
98 def start(self):
99 """begiiing of interval to show"""
100 return self._start
101
102 @property
103 def stop(self):
104 """end of interval to show"""
105 return self._stop
106
107 @property
108 def width(self):
109 return self._stop - self._start
110
111 @property
112 def nth(self):
113 return self.current - self.start
114
115 class CompletionHtml(QtGui.QWidget):
116 """ A widget for tab completion, navigable by arrow keys """
117
118 #--------------------------------------------------------------------------
119 # 'QObject' interface
120 #--------------------------------------------------------------------------
121
122 _items = ()
123 _index = (0, 0)
124 _consecutive_tab = 0
125 _size = (1, 1)
126 _old_cursor = None
127 _start_position = 0
128 _slice_start = 0
129 _slice_len = 4
130
131 def __init__(self, console_widget):
132 """ Create a completion widget that is attached to the specified Qt
133 text edit widget.
134 """
135 assert isinstance(console_widget._control, (QtGui.QTextEdit, QtGui.QPlainTextEdit))
136 super(CompletionHtml, self).__init__()
137
138 self._text_edit = console_widget._control
139 self._console_widget = console_widget
140 self._text_edit.installEventFilter(self)
141 self._sliding_interval = None
142 self._justified_items = None
143
144 # Ensure that the text edit keeps focus when widget is displayed.
145 self.setFocusProxy(self._text_edit)
146
147
148 def eventFilter(self, obj, event):
149 """ Reimplemented to handle keyboard input and to auto-hide when the
150 text edit loses focus.
151 """
152 if obj == self._text_edit:
153 etype = event.type()
154 if etype == QtCore.QEvent.KeyPress:
155 key = event.key()
156 if self._consecutive_tab == 0 and key in (QtCore.Qt.Key_Tab,):
157 return False
158 elif self._consecutive_tab == 1 and key in (QtCore.Qt.Key_Tab,):
159 # ok , called twice, we grab focus, and show the cursor
160 self._consecutive_tab = self._consecutive_tab+1
161 self._update_list()
162 return True
163 elif self._consecutive_tab == 2:
164 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
165 self._complete_current()
166 return True
167 if key in (QtCore.Qt.Key_Tab,):
168 self.select_right()
169 self._update_list()
170 return True
171 elif key in ( QtCore.Qt.Key_Down,):
172 self.select_down()
173 self._update_list()
174 return True
175 elif key in (QtCore.Qt.Key_Right,):
176 self.select_right()
177 self._update_list()
178 return True
179 elif key in ( QtCore.Qt.Key_Up,):
180 self.select_up()
181 self._update_list()
182 return True
183 elif key in ( QtCore.Qt.Key_Left,):
184 self.select_left()
185 self._update_list()
186 return True
187 elif key in ( QtCore.Qt.Key_Escape,):
188 self.cancel_completion()
189 return True
190 else :
191 self.cancel_completion()
192 else:
193 self.cancel_completion()
194
195 elif etype == QtCore.QEvent.FocusOut:
196 self.cancel_completion()
197
198 return super(CompletionHtml, self).eventFilter(obj, event)
199
200 #--------------------------------------------------------------------------
201 # 'CompletionHtml' interface
202 #--------------------------------------------------------------------------
203 def cancel_completion(self):
204 """Cancel the completion
205
206 should be called when the completer have to be dismissed
207
208 This reset internal variable, clearing the temporary buffer
209 of the console where the completion are shown.
210 """
211 self._consecutive_tab = 0
212 self._slice_start = 0
213 self._console_widget._clear_temporary_buffer()
214 self._index = (0, 0)
215 if(self._sliding_interval):
216 self._sliding_interval = None
217
218 #
219 # ... 2 4 4 4 4 4 4 4 4 4 4 4 4
220 # 2 2 4 4 4 4 4 4 4 4 4 4 4 4
221 #
222 #2 2 x x x x x x x x x x x 5 5
223 #6 6 x x x x x x x x x x x 5 5
224 #6 6 x x x x x x x x x x ? 5 5
225 #6 6 x x x x x x x x x x ? 1 1
226 #
227 #3 3 3 3 3 3 3 3 3 3 3 3 1 1 1 ...
228 #3 3 3 3 3 3 3 3 3 3 3 3 1 1 1 ...
229 def _select_index(self, row, col):
230 """Change the selection index, and make sure it stays in the right range
231
232 A little more complicated than just dooing modulo the number of row columns
233 to be sure to cycle through all element.
234
235 horizontaly, the element are maped like this :
236 to r <-- a b c d e f --> to g
237 to f <-- g h i j k l --> to m
238 to l <-- m n o p q r --> to a
239
240 and vertically
241 a d g j m p
242 b e h k n q
243 c f i l o r
244 """
245
246 nr, nc = self._size
247 nr = nr-1
248 nc = nc-1
249
250 # case 1
251 if (row > nr and col >= nc) or (row >= nr and col > nc):
252 self._select_index(0, 0)
253 # case 2
254 elif (row <= 0 and col < 0) or (row < 0 and col <= 0):
255 self._select_index(nr, nc)
256 # case 3
257 elif row > nr :
258 self._select_index(0, col+1)
259 # case 4
260 elif row < 0 :
261 self._select_index(nr, col-1)
262 # case 5
263 elif col > nc :
264 self._select_index(row+1, 0)
265 # case 6
266 elif col < 0 :
267 self._select_index(row-1, nc)
268 elif 0 <= row and row <= nr and 0 <= col and col <= nc :
269 self._index = (row, col)
270 else :
271 raise NotImplementedError("you'r trying to go where no completion\
272 have gone before : %d:%d (%d:%d)"%(row, col, nr, nc) )
273
274
275 @property
276 def _slice_end(self):
277 end = self._slice_start+self._slice_len
278 if end > len(self._items) :
279 return None
280 return end
281
282 def select_up(self):
283 """move cursor up"""
284 r, c = self._index
285 self._select_index(r-1, c)
286
287 def select_down(self):
288 """move cursor down"""
289 r, c = self._index
290 self._select_index(r+1, c)
291
292 def select_left(self):
293 """move cursor left"""
294 r, c = self._index
295 self._select_index(r, c-1)
296
297 def select_right(self):
298 """move cursor right"""
299 r, c = self._index
300 self._select_index(r, c+1)
301
302 def show_items(self, cursor, items):
303 """ Shows the completion widget with 'items' at the position specified
304 by 'cursor'.
305 """
306 if not items :
307 return
308 self._start_position = cursor.position()
309 self._consecutive_tab = 1
310 items_m, ci = text.compute_item_matrix(items, empty=' ')
311 self._sliding_interval = SlidingInterval(len(items_m)-1)
312
313 self._items = items_m
314 self._size = (ci['rows_numbers'], ci['columns_numbers'])
315 self._old_cursor = cursor
316 self._index = (0, 0)
317 sjoin = lambda x : [ y.ljust(w, ' ') for y, w in zip(x, ci['columns_width'])]
318 self._justified_items = map(sjoin, items_m)
319 self._update_list(hilight=False)
320
321
322
323
324 def _update_list(self, hilight=True):
325 """ update the list of completion and hilight the currently selected completion """
326 self._sliding_interval.current = self._index[0]
327 head = None
328 foot = None
329 if self._sliding_interval.start > 0 :
330 head = '...'
331
332 if self._sliding_interval.stop < self._sliding_interval._max:
333 foot = '...'
334 items_m = self._justified_items[\
335 self._sliding_interval.start:\
336 self._sliding_interval.stop+1\
337 ]
338
339 self._console_widget._clear_temporary_buffer()
340 if(hilight):
341 sel = (self._sliding_interval.nth, self._index[1])
342 else :
343 sel = None
344
345 strng = html_tableify(items_m, select=sel, header=head, footer=foot)
346 self._console_widget._fill_temporary_buffer(self._old_cursor, strng, html=True)
347
348 #--------------------------------------------------------------------------
349 # Protected interface
350 #--------------------------------------------------------------------------
351
352 def _complete_current(self):
353 """ Perform the completion with the currently selected item.
354 """
355 i = self._index
356 item = self._items[i[0]][i[1]]
357 item = item.strip()
358 if item :
359 self._current_text_cursor().insertText(item)
360 self.cancel_completion()
361
362 def _current_text_cursor(self):
363 """ Returns a cursor with text between the start position and the
364 current position selected.
365 """
366 cursor = self._text_edit.textCursor()
367 if cursor.position() >= self._start_position:
368 cursor.setPosition(self._start_position,
369 QtGui.QTextCursor.KeepAnchor)
370 return cursor
371
@@ -0,0 +1,62 b''
1 """a simple completer for the qtconsole"""
2 #-----------------------------------------------------------------------------
3 # Copyright (c) 2012, IPython Development Team.$
4 #
5 # Distributed under the terms of the Modified BSD License.$
6 #
7 # The full license is in the file COPYING.txt, distributed with this software.
8 #-------------------------------------------------------------------
9
10 # System library imports
11 from IPython.external.qt import QtCore, QtGui
12 import IPython.utils.text as text
13
14
15 class CompletionPlain(QtGui.QWidget):
16 """ A widget for tab completion, navigable by arrow keys """
17
18 #--------------------------------------------------------------------------
19 # 'QObject' interface
20 #--------------------------------------------------------------------------
21
22 def __init__(self, console_widget):
23 """ Create a completion widget that is attached to the specified Qt
24 text edit widget.
25 """
26 assert isinstance(console_widget._control, (QtGui.QTextEdit, QtGui.QPlainTextEdit))
27 super(CompletionPlain, self).__init__()
28
29 self._text_edit = console_widget._control
30 self._console_widget = console_widget
31
32 self._text_edit.installEventFilter(self)
33
34 def eventFilter(self, obj, event):
35 """ Reimplemented to handle keyboard input and to auto-hide when the
36 text edit loses focus.
37 """
38 if obj == self._text_edit:
39 etype = event.type()
40
41 if etype in( QtCore.QEvent.KeyPress, QtCore.QEvent.FocusOut ):
42 self.cancel_completion()
43
44 return super(CompletionPlain, self).eventFilter(obj, event)
45
46 #--------------------------------------------------------------------------
47 # 'CompletionPlain' interface
48 #--------------------------------------------------------------------------
49 def cancel_completion(self):
50 """Cancel the completion, reseting internal variable, clearing buffer """
51 self._console_widget._clear_temporary_buffer()
52
53
54 def show_items(self, cursor, items):
55 """ Shows the completion widget with 'items' at the position specified
56 by 'cursor'.
57 """
58 if not items :
59 return
60 self.cancel_completion()
61 strng = text.columnize(items)
62 self._console_widget._fill_temporary_buffer(cursor, strng, html=False)
@@ -10,10 +10,11 b' class CompletionWidget(QtGui.QListWidget):'
10 10 # 'QObject' interface
11 11 #--------------------------------------------------------------------------
12 12
13 def __init__(self, text_edit):
13 def __init__(self, console_widget):
14 14 """ Create a completion widget that is attached to the specified Qt
15 15 text edit widget.
16 16 """
17 text_edit = console_widget._control
17 18 assert isinstance(text_edit, (QtGui.QTextEdit, QtGui.QPlainTextEdit))
18 19 super(CompletionWidget, self).__init__()
19 20
@@ -132,3 +133,6 b' class CompletionWidget(QtGui.QListWidget):'
132 133 self.hide()
133 134 else:
134 135 self.hide()
136
137 def cancel_completion(self):
138 self.hide()
@@ -5,7 +5,6 b''
5 5 #-----------------------------------------------------------------------------
6 6
7 7 # Standard library imports
8 import os
9 8 from os.path import commonprefix
10 9 import re
11 10 import sys
@@ -23,6 +22,8 b' from IPython.utils.text import columnize'
23 22 from IPython.utils.traitlets import Bool, Enum, Integer, Unicode
24 23 from ansi_code_processor import QtAnsiCodeProcessor
25 24 from completion_widget import CompletionWidget
25 from completion_html import CompletionHtml
26 from completion_plain import CompletionPlain
26 27 from kill_ring import QtKillRing
27 28
28 29 #-----------------------------------------------------------------------------
@@ -65,10 +66,19 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
65 66 non-positive number disables text truncation (not recommended).
66 67 """
67 68 )
68 gui_completion = Bool(False, config=True,
69 help="""
70 Use a list widget instead of plain text output for tab completion.
71 """
69 gui_completion = Enum(['plain', 'droplist', 'ncurses'], config=True,
70 default_value = 'ncurses',
71 help="""
72 The type of completer to use. Valid values are:
73
74 'plain' : Show the availlable completion as a text list
75 Below the editting area.
76 'droplist': Show the completion in a drop down list navigable
77 by the arrow keys, and from which you can select
78 completion by pressing Return.
79 'ncurses' : Show the completion as a text list which is navigable by
80 `tab` and arrow keys.
81 """
72 82 )
73 83 # NOTE: this value can only be specified during initialization.
74 84 kind = Enum(['plain', 'rich'], default_value='plain', config=True,
@@ -137,12 +147,12 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
137 147 font_changed = QtCore.Signal(QtGui.QFont)
138 148
139 149 #------ Protected class variables ------------------------------------------
140
150
141 151 # control handles
142 152 _control = None
143 153 _page_control = None
144 154 _splitter = None
145
155
146 156 # When the control key is down, these keys are mapped.
147 157 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
148 158 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
@@ -161,6 +171,8 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
161 171 [ QtCore.Qt.Key_C, QtCore.Qt.Key_G, QtCore.Qt.Key_O,
162 172 QtCore.Qt.Key_V ])
163 173
174 _temp_buffer_filled = False
175
164 176 #---------------------------------------------------------------------------
165 177 # 'QObject' interface
166 178 #---------------------------------------------------------------------------
@@ -211,7 +223,13 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
211 223 # information for subclasses; they should be considered read-only.
212 224 self._append_before_prompt_pos = 0
213 225 self._ansi_processor = QtAnsiCodeProcessor()
214 self._completion_widget = CompletionWidget(self._control)
226 if self.gui_completion == 'ncurses':
227 self._completion_widget = CompletionHtml(self)
228 elif self.gui_completion == 'droplist':
229 self._completion_widget = CompletionWidget(self)
230 elif self.gui_completion == 'plain':
231 self._completion_widget = CompletionPlain(self)
232
215 233 self._continuation_prompt = '> '
216 234 self._continuation_prompt_html = None
217 235 self._executing = False
@@ -228,7 +246,6 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
228 246 self._reading = False
229 247 self._reading_callback = None
230 248 self._tab_width = 8
231 self._text_completing_pos = 0
232 249
233 250 # Set a monospaced font.
234 251 self.reset_font()
@@ -823,18 +840,17 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
823 840 """
824 841 self._append_custom(self._insert_plain_text, text, before_prompt)
825 842
826 def _cancel_text_completion(self):
843 def _cancel_completion(self):
827 844 """ If text completion is progress, cancel it.
828 845 """
829 if self._text_completing_pos:
830 self._clear_temporary_buffer()
831 self._text_completing_pos = 0
846 self._completion_widget.cancel_completion()
832 847
833 848 def _clear_temporary_buffer(self):
834 849 """ Clears the "temporary text" buffer, i.e. all the text following
835 850 the prompt region.
836 851 """
837 852 # Select and remove all text below the input buffer.
853 _temp_buffer_filled = False
838 854 cursor = self._get_prompt_cursor()
839 855 prompt = self._continuation_prompt.lstrip()
840 856 while cursor.movePosition(QtGui.QTextCursor.NextBlock):
@@ -862,7 +878,7 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
862 878 def _complete_with_items(self, cursor, items):
863 879 """ Performs completion with 'items' at the specified cursor location.
864 880 """
865 self._cancel_text_completion()
881 self._cancel_completion()
866 882
867 883 if len(items) == 1:
868 884 cursor.setPosition(self._control.textCursor().position(),
@@ -877,19 +893,26 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
877 893 cursor.insertText(prefix)
878 894 current_pos = cursor.position()
879 895
880 if self.gui_completion:
881 cursor.movePosition(QtGui.QTextCursor.Left, n=len(prefix))
882 self._completion_widget.show_items(cursor, items)
883 else:
884 cursor.beginEditBlock()
885 self._append_plain_text('\n')
886 self._page(self._format_as_columns(items))
887 cursor.endEditBlock()
896 cursor.movePosition(QtGui.QTextCursor.Left, n=len(prefix))
897 self._completion_widget.show_items(cursor, items)
898
899
900 def _fill_temporary_buffer(self, cursor, text, html=False):
901 """fill the area below the active editting zone with text"""
902
903 current_pos = self._control.textCursor().position()
904
905 cursor.beginEditBlock()
906 self._append_plain_text('\n')
907 self._page(text, html=html)
908 cursor.endEditBlock()
909
910 cursor.setPosition(current_pos)
911 self._control.moveCursor(QtGui.QTextCursor.End)
912 self._control.setTextCursor(cursor)
913
914 _temp_buffer_filled = True
888 915
889 cursor.setPosition(current_pos)
890 self._control.moveCursor(QtGui.QTextCursor.End)
891 self._control.setTextCursor(cursor)
892 self._text_completing_pos = current_pos
893 916
894 917 def _context_menu_make(self, pos):
895 918 """ Creates a context menu for the given QPoint (in widget coordinates).
@@ -951,7 +974,6 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
951 974 control.viewport().installEventFilter(self)
952 975
953 976 # Connect signals.
954 control.cursorPositionChanged.connect(self._cursor_position_changed)
955 977 control.customContextMenuRequested.connect(
956 978 self._custom_context_menu_requested)
957 979 control.copyAvailable.connect(self.copy_available)
@@ -1021,7 +1043,7 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
1021 1043 intercepted = True
1022 1044
1023 1045 # Special handling when tab completing in text mode.
1024 self._cancel_text_completion()
1046 self._cancel_completion()
1025 1047
1026 1048 if self._in_buffer(position):
1027 1049 # Special handling when a reading a line of raw input.
@@ -1634,8 +1656,9 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
1634 1656 def _keyboard_quit(self):
1635 1657 """ Cancels the current editing task ala Ctrl-G in Emacs.
1636 1658 """
1637 if self._text_completing_pos:
1638 self._cancel_text_completion()
1659 if self._temp_buffer_filled :
1660 self._cancel_completion()
1661 self._clear_temporary_buffer()
1639 1662 else:
1640 1663 self.input_buffer = ''
1641 1664
@@ -1853,24 +1876,6 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
1853 1876 if diff < 0 and document.blockCount() == document.maximumBlockCount():
1854 1877 scrollbar.setValue(scrollbar.value() + diff)
1855 1878
1856 def _cursor_position_changed(self):
1857 """ Clears the temporary buffer based on the cursor position.
1858 """
1859 if self._text_completing_pos:
1860 document = self._control.document()
1861 if self._text_completing_pos < document.characterCount():
1862 cursor = self._control.textCursor()
1863 pos = cursor.position()
1864 text_cursor = self._control.textCursor()
1865 text_cursor.setPosition(self._text_completing_pos)
1866 if pos < self._text_completing_pos or \
1867 cursor.blockNumber() > text_cursor.blockNumber():
1868 self._clear_temporary_buffer()
1869 self._text_completing_pos = 0
1870 else:
1871 self._clear_temporary_buffer()
1872 self._text_completing_pos = 0
1873
1874 1879 def _custom_context_menu_requested(self, pos):
1875 1880 """ Shows a context menu at the given QPoint (in widget coordinates).
1876 1881 """
@@ -104,11 +104,7 b' qt_flags = {'
104 104 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
105 105 "Disable rich text support."),
106 106 }
107 qt_flags.update(boolean_flag(
108 'gui-completion', 'ConsoleWidget.gui_completion',
109 "use a GUI widget for tab completion",
110 "use plaintext output for completion"
111 ))
107
112 108 # and app_flags from the Console Mixin
113 109 qt_flags.update(app_flags)
114 110 # add frontend flags to the full set
@@ -117,7 +113,6 b' flags.update(qt_flags)'
117 113 # start with copy of front&backend aliases list
118 114 aliases = dict(aliases)
119 115 qt_aliases = dict(
120
121 116 style = 'IPythonWidget.syntax_style',
122 117 stylesheet = 'IPythonQtConsoleApp.stylesheet',
123 118 colors = 'ZMQInteractiveShell.colors',
@@ -127,6 +122,7 b' qt_aliases = dict('
127 122 )
128 123 # and app_aliases from the Console Mixin
129 124 qt_aliases.update(app_aliases)
125 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
130 126 # add frontend aliases to the full set
131 127 aliases.update(qt_aliases)
132 128
@@ -22,6 +22,7 b" default_light_style_template = '''"
22 22 .in-prompt-number { font-weight: bold; }
23 23 .out-prompt { color: darkred; }
24 24 .out-prompt-number { font-weight: bold; }
25 .inverted { background-color: %(fgcolor)s ; color:%(bgcolor)s;}
25 26 '''
26 27 default_light_style_sheet = default_light_style_template%dict(
27 28 bgcolor='white', fgcolor='black', select="#ccc")
@@ -38,6 +39,7 b" default_dark_style_template = '''"
38 39 .in-prompt-number { color: lime; font-weight: bold; }
39 40 .out-prompt { color: red; }
40 41 .out-prompt-number { color: red; font-weight: bold; }
42 .inverted { background-color: %(fgcolor)s ; color:%(bgcolor)s;}
41 43 '''
42 44 default_dark_style_sheet = default_dark_style_template%dict(
43 45 bgcolor='black', fgcolor='white', select="#555")
@@ -50,6 +52,7 b" default_bw_style_sheet = '''"
50 52 selection-background-color: #cccccc}
51 53 .in-prompt-number { font-weight: bold; }
52 54 .out-prompt-number { font-weight: bold; }
55 .inverted { background-color: black ; color: white;}
53 56 '''
54 57 default_bw_syntax_style = 'bw'
55 58
@@ -14,6 +14,7 b''
14 14
15 15 import os
16 16 import math
17 import random
17 18
18 19 import nose.tools as nt
19 20
@@ -32,13 +33,37 b' def test_columnize():'
32 33 items = [l*size for l in 'abc']
33 34 out = text.columnize(items, displaywidth=80)
34 35 nt.assert_equals(out, 'aaaaa bbbbb ccccc\n')
35 out = text.columnize(items, displaywidth=10)
36 out = text.columnize(items, displaywidth=12)
36 37 nt.assert_equals(out, 'aaaaa ccccc\nbbbbb\n')
37
38 out = text.columnize(items, displaywidth=10)
39 nt.assert_equals(out, 'aaaaa\nbbbbb\nccccc\n')
40
41 def test_columnize_random():
42 """Test with random input to hopfully catch edge case """
43 for nitems in [random.randint(2,70) for i in range(2,20)]:
44 displaywidth = random.randint(20,200)
45 rand_len = [random.randint(2,displaywidth) for i in range(nitems)]
46 items = ['x'*l for l in rand_len]
47 out = text.columnize(items, displaywidth=displaywidth)
48 longer_line = max([len(x) for x in out.split('\n')])
49 longer_element = max(rand_len)
50 if longer_line > displaywidth:
51 print "Columnize displayed something lager than displaywidth : %s " % longer_line
52 print "longer element : %s " % longer_element
53 print "displaywidth : %s " % displaywidth
54 print "number of element : %s " % nitems
55 print "size of each element :\n %s" % rand_len
56 assert False
57
58 def test_columnize_medium():
59 """Test with inputs than shouldn't be wider tahn 80 """
60 size = 40
61 items = [l*size for l in 'abc']
62 out = text.columnize(items, displaywidth=80)
63 nt.assert_equals(out, '\n'.join(items+['']))
38 64
39 65 def test_columnize_long():
40 66 """Test columnize with inputs longer than the display window"""
41 text.columnize(['a'*81, 'b'*81], displaywidth=80)
42 67 size = 11
43 68 items = [l*size for l in 'abc']
44 69 out = text.columnize(items, displaywidth=size-1)
@@ -24,7 +24,7 b' import textwrap'
24 24 from string import Formatter
25 25
26 26 from IPython.external.path import path
27 from IPython.testing.skipdoctest import skip_doctest_py3
27 from IPython.testing.skipdoctest import skip_doctest_py3, skip_doctest
28 28 from IPython.utils import py3compat
29 29 from IPython.utils.io import nlprint
30 30 from IPython.utils.data import flatten
@@ -660,6 +660,91 b' class DollarFormatter(FullEvalFormatter):'
660 660 # Re-yield the {foo} style pattern
661 661 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
662 662
663 #-----------------------------------------------------------------------------
664 # Utils to columnize a list of string
665 #-----------------------------------------------------------------------------
666 def _chunks(l, n):
667 """Yield successive n-sized chunks from l."""
668 for i in xrange(0, len(l), n):
669 yield l[i:i+n]
670
671 def _find_optimal(rlist , separator_size=2 , displaywidth=80):
672 """Calculate optimal info to columnize a list of string"""
673 for nrow in range(1, len(rlist)+1) :
674 chk = map(max,_chunks(rlist, nrow))
675 sumlength = sum(chk)
676 ncols = len(chk)
677 if sumlength+separator_size*(ncols-1) <= displaywidth :
678 break;
679 return {'columns_numbers' : ncols,
680 'optimal_separator_width':(displaywidth - sumlength)/(ncols-1) if (ncols -1) else 0,
681 'rows_numbers' : nrow,
682 'columns_width' : chk
683 }
684
685 def _get_or_default(mylist, i, default=None):
686 """return list item number, or default if don't exist"""
687 if i >= len(mylist):
688 return default
689 else :
690 return mylist[i]
691
692 @skip_doctest
693 def compute_item_matrix(items, empty=None, *args, **kwargs) :
694 """Returns a nested list, and info to columnize items
695
696 Parameters :
697 ------------
698
699 items :
700 list of strings to columize
701 empty : (default None)
702 default value to fill list if needed
703 separator_size : int (default=2)
704 How much caracters will be used as a separation between each columns.
705 displaywidth : int (default=80)
706 The width of the area onto wich the columns should enter
707
708 Returns :
709 ---------
710
711 Returns a tuple of (strings_matrix, dict_info)
712
713 strings_matrix :
714
715 nested list of string, the outer most list contains as many list as
716 rows, the innermost lists have each as many element as colums. If the
717 total number of elements in `items` does not equal the product of
718 rows*columns, the last element of some lists are filled with `None`.
719
720 dict_info :
721 some info to make columnize easier:
722
723 columns_numbers : number of columns
724 rows_numbers : number of rows
725 columns_width : list of with of each columns
726 optimal_separator_width : best separator width between columns
727
728 Exemple :
729 ---------
730
731 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
732 ...: compute_item_matrix(l,displaywidth=12)
733 Out[1]:
734 ([['aaa', 'f', 'k'],
735 ['b', 'g', 'l'],
736 ['cc', 'h', None],
737 ['d', 'i', None],
738 ['eeeee', 'j', None]],
739 {'columns_numbers': 3,
740 'columns_width': [5, 1, 1],
741 'optimal_separator_width': 2,
742 'rows_numbers': 5})
743
744 """
745 info = _find_optimal(map(len, items), *args, **kwargs)
746 nrow, ncol = info['rows_numbers'], info['columns_numbers']
747 return ([[ _get_or_default(items, c*nrow+i, default=empty) for c in range(ncol) ] for i in range(nrow) ], info)
663 748
664 749 def columnize(items, separator=' ', displaywidth=80):
665 750 """ Transform a list of strings into a single string with columns.
@@ -679,58 +764,9 b" def columnize(items, separator=' ', displaywidth=80):"
679 764 -------
680 765 The formatted string.
681 766 """
682 # Note: this code is adapted from columnize 0.3.2.
683 # See http://code.google.com/p/pycolumnize/
684
685 # Some degenerate cases.
686 size = len(items)
687 if size == 0:
767 if not items :
688 768 return '\n'
689 elif size == 1:
690 return '%s\n' % items[0]
691
692 # Special case: if any item is longer than the maximum width, there's no
693 # point in triggering the logic below...
694 item_len = map(len, items) # save these, we can reuse them below
695 longest = max(item_len)
696 if longest >= displaywidth:
697 return '\n'.join(items+[''])
698
699 # Try every row count from 1 upwards
700 array_index = lambda nrows, row, col: nrows*col + row
701 for nrows in range(1, size):
702 ncols = (size + nrows - 1) // nrows
703 colwidths = []
704 totwidth = -len(separator)
705 for col in range(ncols):
706 # Get max column width for this column
707 colwidth = 0
708 for row in range(nrows):
709 i = array_index(nrows, row, col)
710 if i >= size: break
711 x, len_x = items[i], item_len[i]
712 colwidth = max(colwidth, len_x)
713 colwidths.append(colwidth)
714 totwidth += colwidth + len(separator)
715 if totwidth > displaywidth:
716 break
717 if totwidth <= displaywidth:
718 break
719
720 # The smallest number of rows computed and the max widths for each
721 # column has been obtained. Now we just have to format each of the rows.
722 string = ''
723 for row in range(nrows):
724 texts = []
725 for col in range(ncols):
726 i = row + nrows*col
727 if i >= size:
728 texts.append('')
729 else:
730 texts.append(items[i])
731 while texts and not texts[-1]:
732 del texts[-1]
733 for col in range(len(texts)):
734 texts[col] = texts[col].ljust(colwidths[col])
735 string += '%s\n' % separator.join(texts)
736 return string
769 matrix, info = compute_item_matrix(items, separator_size=len(separator), displaywidth=displaywidth)
770 fmatrix = [filter(None, x) for x in matrix]
771 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['columns_width'])])
772 return '\n'.join(map(sjoin, fmatrix))+'\n'
General Comments 0
You need to be logged in to leave comments. Login now