##// 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 # 'QObject' interface
10 # 'QObject' interface
11 #--------------------------------------------------------------------------
11 #--------------------------------------------------------------------------
12
12
13 def __init__(self, text_edit):
13 def __init__(self, console_widget):
14 """ Create a completion widget that is attached to the specified Qt
14 """ Create a completion widget that is attached to the specified Qt
15 text edit widget.
15 text edit widget.
16 """
16 """
17 text_edit = console_widget._control
17 assert isinstance(text_edit, (QtGui.QTextEdit, QtGui.QPlainTextEdit))
18 assert isinstance(text_edit, (QtGui.QTextEdit, QtGui.QPlainTextEdit))
18 super(CompletionWidget, self).__init__()
19 super(CompletionWidget, self).__init__()
19
20
@@ -132,3 +133,6 b' class CompletionWidget(QtGui.QListWidget):'
132 self.hide()
133 self.hide()
133 else:
134 else:
134 self.hide()
135 self.hide()
136
137 def cancel_completion(self):
138 self.hide()
@@ -5,7 +5,6 b''
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6
6
7 # Standard library imports
7 # Standard library imports
8 import os
9 from os.path import commonprefix
8 from os.path import commonprefix
10 import re
9 import re
11 import sys
10 import sys
@@ -23,6 +22,8 b' from IPython.utils.text import columnize'
23 from IPython.utils.traitlets import Bool, Enum, Integer, Unicode
22 from IPython.utils.traitlets import Bool, Enum, Integer, Unicode
24 from ansi_code_processor import QtAnsiCodeProcessor
23 from ansi_code_processor import QtAnsiCodeProcessor
25 from completion_widget import CompletionWidget
24 from completion_widget import CompletionWidget
25 from completion_html import CompletionHtml
26 from completion_plain import CompletionPlain
26 from kill_ring import QtKillRing
27 from kill_ring import QtKillRing
27
28
28 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
@@ -65,10 +66,19 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
65 non-positive number disables text truncation (not recommended).
66 non-positive number disables text truncation (not recommended).
66 """
67 """
67 )
68 )
68 gui_completion = Bool(False, config=True,
69 gui_completion = Enum(['plain', 'droplist', 'ncurses'], config=True,
69 help="""
70 default_value = 'ncurses',
70 Use a list widget instead of plain text output for tab completion.
71 help="""
71 """
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 # NOTE: this value can only be specified during initialization.
83 # NOTE: this value can only be specified during initialization.
74 kind = Enum(['plain', 'rich'], default_value='plain', config=True,
84 kind = Enum(['plain', 'rich'], default_value='plain', config=True,
@@ -137,12 +147,12 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
137 font_changed = QtCore.Signal(QtGui.QFont)
147 font_changed = QtCore.Signal(QtGui.QFont)
138
148
139 #------ Protected class variables ------------------------------------------
149 #------ Protected class variables ------------------------------------------
140
150
141 # control handles
151 # control handles
142 _control = None
152 _control = None
143 _page_control = None
153 _page_control = None
144 _splitter = None
154 _splitter = None
145
155
146 # When the control key is down, these keys are mapped.
156 # When the control key is down, these keys are mapped.
147 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
157 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
148 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
158 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
@@ -161,6 +171,8 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
161 [ QtCore.Qt.Key_C, QtCore.Qt.Key_G, QtCore.Qt.Key_O,
171 [ QtCore.Qt.Key_C, QtCore.Qt.Key_G, QtCore.Qt.Key_O,
162 QtCore.Qt.Key_V ])
172 QtCore.Qt.Key_V ])
163
173
174 _temp_buffer_filled = False
175
164 #---------------------------------------------------------------------------
176 #---------------------------------------------------------------------------
165 # 'QObject' interface
177 # 'QObject' interface
166 #---------------------------------------------------------------------------
178 #---------------------------------------------------------------------------
@@ -211,7 +223,13 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
211 # information for subclasses; they should be considered read-only.
223 # information for subclasses; they should be considered read-only.
212 self._append_before_prompt_pos = 0
224 self._append_before_prompt_pos = 0
213 self._ansi_processor = QtAnsiCodeProcessor()
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 self._continuation_prompt = '> '
233 self._continuation_prompt = '> '
216 self._continuation_prompt_html = None
234 self._continuation_prompt_html = None
217 self._executing = False
235 self._executing = False
@@ -228,7 +246,6 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
228 self._reading = False
246 self._reading = False
229 self._reading_callback = None
247 self._reading_callback = None
230 self._tab_width = 8
248 self._tab_width = 8
231 self._text_completing_pos = 0
232
249
233 # Set a monospaced font.
250 # Set a monospaced font.
234 self.reset_font()
251 self.reset_font()
@@ -823,18 +840,17 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
823 """
840 """
824 self._append_custom(self._insert_plain_text, text, before_prompt)
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 """ If text completion is progress, cancel it.
844 """ If text completion is progress, cancel it.
828 """
845 """
829 if self._text_completing_pos:
846 self._completion_widget.cancel_completion()
830 self._clear_temporary_buffer()
831 self._text_completing_pos = 0
832
847
833 def _clear_temporary_buffer(self):
848 def _clear_temporary_buffer(self):
834 """ Clears the "temporary text" buffer, i.e. all the text following
849 """ Clears the "temporary text" buffer, i.e. all the text following
835 the prompt region.
850 the prompt region.
836 """
851 """
837 # Select and remove all text below the input buffer.
852 # Select and remove all text below the input buffer.
853 _temp_buffer_filled = False
838 cursor = self._get_prompt_cursor()
854 cursor = self._get_prompt_cursor()
839 prompt = self._continuation_prompt.lstrip()
855 prompt = self._continuation_prompt.lstrip()
840 while cursor.movePosition(QtGui.QTextCursor.NextBlock):
856 while cursor.movePosition(QtGui.QTextCursor.NextBlock):
@@ -862,7 +878,7 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
862 def _complete_with_items(self, cursor, items):
878 def _complete_with_items(self, cursor, items):
863 """ Performs completion with 'items' at the specified cursor location.
879 """ Performs completion with 'items' at the specified cursor location.
864 """
880 """
865 self._cancel_text_completion()
881 self._cancel_completion()
866
882
867 if len(items) == 1:
883 if len(items) == 1:
868 cursor.setPosition(self._control.textCursor().position(),
884 cursor.setPosition(self._control.textCursor().position(),
@@ -877,19 +893,26 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
877 cursor.insertText(prefix)
893 cursor.insertText(prefix)
878 current_pos = cursor.position()
894 current_pos = cursor.position()
879
895
880 if self.gui_completion:
896 cursor.movePosition(QtGui.QTextCursor.Left, n=len(prefix))
881 cursor.movePosition(QtGui.QTextCursor.Left, n=len(prefix))
897 self._completion_widget.show_items(cursor, items)
882 self._completion_widget.show_items(cursor, items)
898
883 else:
899
884 cursor.beginEditBlock()
900 def _fill_temporary_buffer(self, cursor, text, html=False):
885 self._append_plain_text('\n')
901 """fill the area below the active editting zone with text"""
886 self._page(self._format_as_columns(items))
902
887 cursor.endEditBlock()
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 def _context_menu_make(self, pos):
917 def _context_menu_make(self, pos):
895 """ Creates a context menu for the given QPoint (in widget coordinates).
918 """ Creates a context menu for the given QPoint (in widget coordinates).
@@ -951,7 +974,6 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
951 control.viewport().installEventFilter(self)
974 control.viewport().installEventFilter(self)
952
975
953 # Connect signals.
976 # Connect signals.
954 control.cursorPositionChanged.connect(self._cursor_position_changed)
955 control.customContextMenuRequested.connect(
977 control.customContextMenuRequested.connect(
956 self._custom_context_menu_requested)
978 self._custom_context_menu_requested)
957 control.copyAvailable.connect(self.copy_available)
979 control.copyAvailable.connect(self.copy_available)
@@ -1021,7 +1043,7 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
1021 intercepted = True
1043 intercepted = True
1022
1044
1023 # Special handling when tab completing in text mode.
1045 # Special handling when tab completing in text mode.
1024 self._cancel_text_completion()
1046 self._cancel_completion()
1025
1047
1026 if self._in_buffer(position):
1048 if self._in_buffer(position):
1027 # Special handling when a reading a line of raw input.
1049 # Special handling when a reading a line of raw input.
@@ -1634,8 +1656,9 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
1634 def _keyboard_quit(self):
1656 def _keyboard_quit(self):
1635 """ Cancels the current editing task ala Ctrl-G in Emacs.
1657 """ Cancels the current editing task ala Ctrl-G in Emacs.
1636 """
1658 """
1637 if self._text_completing_pos:
1659 if self._temp_buffer_filled :
1638 self._cancel_text_completion()
1660 self._cancel_completion()
1661 self._clear_temporary_buffer()
1639 else:
1662 else:
1640 self.input_buffer = ''
1663 self.input_buffer = ''
1641
1664
@@ -1853,24 +1876,6 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
1853 if diff < 0 and document.blockCount() == document.maximumBlockCount():
1876 if diff < 0 and document.blockCount() == document.maximumBlockCount():
1854 scrollbar.setValue(scrollbar.value() + diff)
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 def _custom_context_menu_requested(self, pos):
1879 def _custom_context_menu_requested(self, pos):
1875 """ Shows a context menu at the given QPoint (in widget coordinates).
1880 """ Shows a context menu at the given QPoint (in widget coordinates).
1876 """
1881 """
@@ -104,11 +104,7 b' qt_flags = {'
104 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
104 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
105 "Disable rich text support."),
105 "Disable rich text support."),
106 }
106 }
107 qt_flags.update(boolean_flag(
107
108 'gui-completion', 'ConsoleWidget.gui_completion',
109 "use a GUI widget for tab completion",
110 "use plaintext output for completion"
111 ))
112 # and app_flags from the Console Mixin
108 # and app_flags from the Console Mixin
113 qt_flags.update(app_flags)
109 qt_flags.update(app_flags)
114 # add frontend flags to the full set
110 # add frontend flags to the full set
@@ -117,7 +113,6 b' flags.update(qt_flags)'
117 # start with copy of front&backend aliases list
113 # start with copy of front&backend aliases list
118 aliases = dict(aliases)
114 aliases = dict(aliases)
119 qt_aliases = dict(
115 qt_aliases = dict(
120
121 style = 'IPythonWidget.syntax_style',
116 style = 'IPythonWidget.syntax_style',
122 stylesheet = 'IPythonQtConsoleApp.stylesheet',
117 stylesheet = 'IPythonQtConsoleApp.stylesheet',
123 colors = 'ZMQInteractiveShell.colors',
118 colors = 'ZMQInteractiveShell.colors',
@@ -127,6 +122,7 b' qt_aliases = dict('
127 )
122 )
128 # and app_aliases from the Console Mixin
123 # and app_aliases from the Console Mixin
129 qt_aliases.update(app_aliases)
124 qt_aliases.update(app_aliases)
125 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
130 # add frontend aliases to the full set
126 # add frontend aliases to the full set
131 aliases.update(qt_aliases)
127 aliases.update(qt_aliases)
132
128
@@ -22,6 +22,7 b" default_light_style_template = '''"
22 .in-prompt-number { font-weight: bold; }
22 .in-prompt-number { font-weight: bold; }
23 .out-prompt { color: darkred; }
23 .out-prompt { color: darkred; }
24 .out-prompt-number { font-weight: bold; }
24 .out-prompt-number { font-weight: bold; }
25 .inverted { background-color: %(fgcolor)s ; color:%(bgcolor)s;}
25 '''
26 '''
26 default_light_style_sheet = default_light_style_template%dict(
27 default_light_style_sheet = default_light_style_template%dict(
27 bgcolor='white', fgcolor='black', select="#ccc")
28 bgcolor='white', fgcolor='black', select="#ccc")
@@ -38,6 +39,7 b" default_dark_style_template = '''"
38 .in-prompt-number { color: lime; font-weight: bold; }
39 .in-prompt-number { color: lime; font-weight: bold; }
39 .out-prompt { color: red; }
40 .out-prompt { color: red; }
40 .out-prompt-number { color: red; font-weight: bold; }
41 .out-prompt-number { color: red; font-weight: bold; }
42 .inverted { background-color: %(fgcolor)s ; color:%(bgcolor)s;}
41 '''
43 '''
42 default_dark_style_sheet = default_dark_style_template%dict(
44 default_dark_style_sheet = default_dark_style_template%dict(
43 bgcolor='black', fgcolor='white', select="#555")
45 bgcolor='black', fgcolor='white', select="#555")
@@ -50,6 +52,7 b" default_bw_style_sheet = '''"
50 selection-background-color: #cccccc}
52 selection-background-color: #cccccc}
51 .in-prompt-number { font-weight: bold; }
53 .in-prompt-number { font-weight: bold; }
52 .out-prompt-number { font-weight: bold; }
54 .out-prompt-number { font-weight: bold; }
55 .inverted { background-color: black ; color: white;}
53 '''
56 '''
54 default_bw_syntax_style = 'bw'
57 default_bw_syntax_style = 'bw'
55
58
@@ -14,6 +14,7 b''
14
14
15 import os
15 import os
16 import math
16 import math
17 import random
17
18
18 import nose.tools as nt
19 import nose.tools as nt
19
20
@@ -32,13 +33,37 b' def test_columnize():'
32 items = [l*size for l in 'abc']
33 items = [l*size for l in 'abc']
33 out = text.columnize(items, displaywidth=80)
34 out = text.columnize(items, displaywidth=80)
34 nt.assert_equals(out, 'aaaaa bbbbb ccccc\n')
35 nt.assert_equals(out, 'aaaaa bbbbb ccccc\n')
35 out = text.columnize(items, displaywidth=10)
36 out = text.columnize(items, displaywidth=12)
36 nt.assert_equals(out, 'aaaaa ccccc\nbbbbb\n')
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 def test_columnize_long():
65 def test_columnize_long():
40 """Test columnize with inputs longer than the display window"""
66 """Test columnize with inputs longer than the display window"""
41 text.columnize(['a'*81, 'b'*81], displaywidth=80)
42 size = 11
67 size = 11
43 items = [l*size for l in 'abc']
68 items = [l*size for l in 'abc']
44 out = text.columnize(items, displaywidth=size-1)
69 out = text.columnize(items, displaywidth=size-1)
@@ -24,7 +24,7 b' import textwrap'
24 from string import Formatter
24 from string import Formatter
25
25
26 from IPython.external.path import path
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 from IPython.utils import py3compat
28 from IPython.utils import py3compat
29 from IPython.utils.io import nlprint
29 from IPython.utils.io import nlprint
30 from IPython.utils.data import flatten
30 from IPython.utils.data import flatten
@@ -660,6 +660,91 b' class DollarFormatter(FullEvalFormatter):'
660 # Re-yield the {foo} style pattern
660 # Re-yield the {foo} style pattern
661 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
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 def columnize(items, separator=' ', displaywidth=80):
749 def columnize(items, separator=' ', displaywidth=80):
665 """ Transform a list of strings into a single string with columns.
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 The formatted string.
765 The formatted string.
681 """
766 """
682 # Note: this code is adapted from columnize 0.3.2.
767 if not items :
683 # See http://code.google.com/p/pycolumnize/
684
685 # Some degenerate cases.
686 size = len(items)
687 if size == 0:
688 return '\n'
768 return '\n'
689 elif size == 1:
769 matrix, info = compute_item_matrix(items, separator_size=len(separator), displaywidth=displaywidth)
690 return '%s\n' % items[0]
770 fmatrix = [filter(None, x) for x in matrix]
691
771 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['columns_width'])])
692 # Special case: if any item is longer than the maximum width, there's no
772 return '\n'.join(map(sjoin, fmatrix))+'\n'
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
General Comments 0
You need to be logged in to leave comments. Login now