##// END OF EJS Templates
Better tab-completion when the autocomp menu is displayed.
Gael Varoquaux -
Show More
@@ -1,407 +1,408 b''
1 1 # encoding: utf-8
2 2 """
3 3 A Wx widget to act as a console and input commands.
4 4
5 5 This widget deals with prompts and provides an edit buffer
6 6 restricted to after the last prompt.
7 7 """
8 8
9 9 __docformat__ = "restructuredtext en"
10 10
11 11 #-------------------------------------------------------------------------------
12 12 # Copyright (C) 2008 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is
15 15 # in the file COPYING, distributed as part of this software.
16 16 #-------------------------------------------------------------------------------
17 17
18 18 #-------------------------------------------------------------------------------
19 19 # Imports
20 20 #-------------------------------------------------------------------------------
21 21
22 22 import wx
23 23 import wx.stc as stc
24 24
25 25 from wx.py import editwindow
26 26 import sys
27 27 LINESEP = '\n'
28 28 if sys.platform == 'win32':
29 29 LINESEP = '\n\r'
30 30
31 31 import re
32 32
33 33 # FIXME: Need to provide an API for non user-generated display on the
34 34 # screen: this should not be editable by the user.
35 35
36 36 _DEFAULT_SIZE = 10
37 37
38 38 _DEFAULT_STYLE = {
39 39 'stdout' : 'fore:#0000FF',
40 40 'stderr' : 'fore:#007f00',
41 41 'trace' : 'fore:#FF0000',
42 42
43 43 'default' : 'size:%d' % _DEFAULT_SIZE,
44 44 'bracegood' : 'fore:#00AA00,back:#000000,bold',
45 45 'bracebad' : 'fore:#FF0000,back:#000000,bold',
46 46
47 47 # properties for the various Python lexer styles
48 48 'comment' : 'fore:#007F00',
49 49 'number' : 'fore:#007F7F',
50 50 'string' : 'fore:#7F007F,italic',
51 51 'char' : 'fore:#7F007F,italic',
52 52 'keyword' : 'fore:#00007F,bold',
53 53 'triple' : 'fore:#7F0000',
54 54 'tripledouble' : 'fore:#7F0000',
55 55 'class' : 'fore:#0000FF,bold,underline',
56 56 'def' : 'fore:#007F7F,bold',
57 57 'operator' : 'bold'
58 58 }
59 59
60 60 # new style numbers
61 61 _STDOUT_STYLE = 15
62 62 _STDERR_STYLE = 16
63 63 _TRACE_STYLE = 17
64 64
65 65
66 66 # system colors
67 67 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
68 68
69 69 #-------------------------------------------------------------------------------
70 70 # The console widget class
71 71 #-------------------------------------------------------------------------------
72 72 class ConsoleWidget(editwindow.EditWindow):
73 73 """ Specialized styled text control view for console-like workflow.
74 74
75 75 This widget is mainly interested in dealing with the prompt and
76 76 keeping the cursor inside the editing line.
77 77 """
78 78
79 79 # This is where the title captured from the ANSI escape sequences are
80 80 # stored.
81 81 title = 'Console'
82 82
83 83 # The buffer being edited.
84 84 def _set_input_buffer(self, string):
85 85 self.SetSelection(self.current_prompt_pos, self.GetLength())
86 86 self.ReplaceSelection(string)
87 87 self.GotoPos(self.GetLength())
88 88
89 89 def _get_input_buffer(self):
90 90 """ Returns the text in current edit buffer.
91 91 """
92 92 input_buffer = self.GetTextRange(self.current_prompt_pos,
93 93 self.GetLength())
94 94 input_buffer = input_buffer.replace(LINESEP, '\n')
95 95 return input_buffer
96 96
97 97 input_buffer = property(_get_input_buffer, _set_input_buffer)
98 98
99 99 style = _DEFAULT_STYLE.copy()
100 100
101 101 # Translation table from ANSI escape sequences to color. Override
102 102 # this to specify your colors.
103 103 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
104 104 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
105 105 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
106 106 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
107 107 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
108 108 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
109 109 '1;34': [12, 'LIGHT BLUE'], '1;35':
110 110 [13, 'MEDIUM VIOLET RED'],
111 111 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
112 112
113 113 # The color of the carret (call _apply_style() after setting)
114 114 carret_color = 'BLACK'
115 115
116 116 #--------------------------------------------------------------------------
117 117 # Public API
118 118 #--------------------------------------------------------------------------
119 119
120 120 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
121 121 size=wx.DefaultSize, style=0, ):
122 122 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
123 123 self._configure_scintilla()
124 124
125 125 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
126 126 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
127 127
128 128
129 129 def write(self, text, refresh=True):
130 130 """ Write given text to buffer, while translating the ansi escape
131 131 sequences.
132 132 """
133 133 # XXX: do not put print statements to sys.stdout/sys.stderr in
134 134 # this method, the print statements will call this method, as
135 135 # you will end up with an infinit loop
136 136 title = self.title_pat.split(text)
137 137 if len(title)>1:
138 138 self.title = title[-2]
139 139
140 140 text = self.title_pat.sub('', text)
141 141 segments = self.color_pat.split(text)
142 142 segment = segments.pop(0)
143 143 self.GotoPos(self.GetLength())
144 144 self.StartStyling(self.GetLength(), 0xFF)
145 145 try:
146 146 self.AppendText(segment)
147 147 except UnicodeDecodeError:
148 148 # XXX: Do I really want to skip the exception?
149 149 pass
150 150
151 151 if segments:
152 152 for ansi_tag, text in zip(segments[::2], segments[1::2]):
153 153 self.StartStyling(self.GetLength(), 0xFF)
154 154 try:
155 155 self.AppendText(text)
156 156 except UnicodeDecodeError:
157 157 # XXX: Do I really want to skip the exception?
158 158 pass
159 159
160 160 if ansi_tag not in self.ANSI_STYLES:
161 161 style = 0
162 162 else:
163 163 style = self.ANSI_STYLES[ansi_tag][0]
164 164
165 165 self.SetStyling(len(text), style)
166 166
167 167 self.GotoPos(self.GetLength())
168 168 if refresh:
169 169 wx.Yield()
170 170
171 171
172 172 def new_prompt(self, prompt):
173 173 """ Prints a prompt at start of line, and move the start of the
174 174 current block there.
175 175
176 176 The prompt can be given with ascii escape sequences.
177 177 """
178 178 self.write(prompt)
179 179 # now we update our cursor giving end of prompt
180 180 self.current_prompt_pos = self.GetLength()
181 181 self.current_prompt_line = self.GetCurrentLine()
182 182 wx.Yield()
183 183 self.EnsureCaretVisible()
184 184
185 185
186 186 def scroll_to_bottom(self):
187 187 maxrange = self.GetScrollRange(wx.VERTICAL)
188 188 self.ScrollLines(maxrange)
189 189
190 190
191 191 def pop_completion(self, possibilities, offset=0):
192 192 """ Pops up an autocompletion menu. Offset is the offset
193 193 in characters of the position at which the menu should
194 194 appear, relativ to the cursor.
195 195 """
196 196 self.AutoCompSetIgnoreCase(False)
197 197 self.AutoCompSetAutoHide(False)
198 198 self.AutoCompSetMaxHeight(len(possibilities))
199 199 self.AutoCompShow(offset, " ".join(possibilities))
200 200
201 201
202 202 def get_line_width(self):
203 203 """ Return the width of the line in characters.
204 204 """
205 205 return self.GetSize()[0]/self.GetCharWidth()
206 206
207 207
208 208 #--------------------------------------------------------------------------
209 209 # Private API
210 210 #--------------------------------------------------------------------------
211 211
212 212 def _apply_style(self):
213 213 """ Applies the colors for the different text elements and the
214 214 carret.
215 215 """
216 216 self.SetCaretForeground(self.carret_color)
217 217
218 218 #self.StyleClearAll()
219 219 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
220 220 "fore:#FF0000,back:#0000FF,bold")
221 221 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
222 222 "fore:#000000,back:#FF0000,bold")
223 223
224 224 for style in self.ANSI_STYLES.values():
225 225 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
226 226
227 227
228 228 def _configure_scintilla(self):
229 229 self.SetEOLMode(stc.STC_EOL_LF)
230 230
231 231 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
232 232 # the widget
233 233 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
234 234 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
235 235 # Also allow Ctrl Shift "=" for poor non US keyboard users.
236 236 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
237 237 stc.STC_CMD_ZOOMIN)
238 238
239 239 # Keys: we need to clear some of the keys the that don't play
240 240 # well with a console.
241 241 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
242 242 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
243 243 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
244 244
245
246 245 self.SetEOLMode(stc.STC_EOL_CRLF)
247 246 self.SetWrapMode(stc.STC_WRAP_CHAR)
248 247 self.SetWrapMode(stc.STC_WRAP_WORD)
249 248 self.SetBufferedDraw(True)
250 249 self.SetUseAntiAliasing(True)
251 250 self.SetLayoutCache(stc.STC_CACHE_PAGE)
252 251 self.SetUndoCollection(False)
253 252 self.SetUseTabs(True)
254 253 self.SetIndent(4)
255 254 self.SetTabWidth(4)
256 255
257 256 self.EnsureCaretVisible()
258 257 # we don't want scintilla's autocompletion to choose
259 258 # automaticaly out of a single choice list, as we pop it up
260 259 # automaticaly
261 260 self.AutoCompSetChooseSingle(False)
262 261 self.AutoCompSetMaxHeight(10)
262 # XXX: this doesn't seem to have an effect.
263 self.AutoCompSetFillUps('\n')
263 264
264 265 self.SetMargins(3, 3) #text is moved away from border with 3px
265 266 # Suppressing Scintilla margins
266 267 self.SetMarginWidth(0, 0)
267 268 self.SetMarginWidth(1, 0)
268 269 self.SetMarginWidth(2, 0)
269 270
270 271 self._apply_style()
271 272
272 273 # Xterm escape sequences
273 274 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
274 275 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
275 276
276 277 #self.SetEdgeMode(stc.STC_EDGE_LINE)
277 278 #self.SetEdgeColumn(80)
278 279
279 280 # styles
280 281 p = self.style
281 282 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
282 283 self.StyleClearAll()
283 284 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
284 285 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
285 286 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
286 287
287 288 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
288 289 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
289 290 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
290 291 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
291 292 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
292 293 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
293 294 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
294 295 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
295 296 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
296 297 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
297 298 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
298 299 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
299 300 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
300 301 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
301 302
302 303
303 304 def _on_key_down(self, event, skip=True):
304 305 """ Key press callback used for correcting behavior for
305 306 console-like interfaces: the cursor is constraint to be after
306 307 the last prompt.
307 308
308 309 Return True if event as been catched.
309 310 """
310 311 catched = True
311 312 # Intercept some specific keys.
312 313 if event.KeyCode == ord('L') and event.ControlDown() :
313 314 self.scroll_to_bottom()
314 315 elif event.KeyCode == ord('K') and event.ControlDown() :
315 316 self.input_buffer = ''
316 317 elif event.KeyCode == wx.WXK_PAGEUP:
317 318 self.ScrollPages(-1)
318 319 elif event.KeyCode == wx.WXK_PAGEDOWN:
319 320 self.ScrollPages(1)
320 321 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
321 322 self.ScrollLines(-1)
322 323 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
323 324 self.ScrollLines(1)
324 325 else:
325 326 catched = False
326 327
327 328 if self.AutoCompActive():
328 329 event.Skip()
329 330 else:
330 331 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
331 332 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
332 333 catched = True
333 334 self.CallTipCancel()
334 335 self.write('\n', refresh=False)
335 336 # Under windows scintilla seems to be doing funny stuff to the
336 337 # line returns here, but the getter for input_buffer filters
337 338 # this out.
338 339 if sys.platform == 'win32':
339 340 self.input_buffer = self.input_buffer
340 341 self._on_enter()
341 342
342 343 elif event.KeyCode == wx.WXK_HOME:
343 344 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
344 345 self.GotoPos(self.current_prompt_pos)
345 346 catched = True
346 347
347 348 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
348 349 # FIXME: This behavior is not ideal: if the selection
349 350 # is already started, it will jump.
350 351 self.SetSelectionStart(self.current_prompt_pos)
351 352 self.SetSelectionEnd(self.GetCurrentPos())
352 353 catched = True
353 354
354 355 elif event.KeyCode == wx.WXK_UP:
355 356 if self.GetCurrentLine() > self.current_prompt_line:
356 357 if self.GetCurrentLine() == self.current_prompt_line + 1 \
357 358 and self.GetColumn(self.GetCurrentPos()) < \
358 359 self.GetColumn(self.current_prompt_pos):
359 360 self.GotoPos(self.current_prompt_pos)
360 361 else:
361 362 event.Skip()
362 363 catched = True
363 364
364 365 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
365 366 if self.GetCurrentPos() > self.current_prompt_pos:
366 367 event.Skip()
367 368 catched = True
368 369
369 370 if skip and not catched:
370 371 # Put the cursor back in the edit region
371 372 if self.GetCurrentPos() < self.current_prompt_pos:
372 373 self.GotoPos(self.current_prompt_pos)
373 374 else:
374 375 event.Skip()
375 376
376 377 return catched
377 378
378 379
379 380 def _on_key_up(self, event, skip=True):
380 381 """ If cursor is outside the editing region, put it back.
381 382 """
382 383 event.Skip()
383 384 if self.GetCurrentPos() < self.current_prompt_pos:
384 385 self.GotoPos(self.current_prompt_pos)
385 386
386 387
387 388
388 389 if __name__ == '__main__':
389 390 # Some simple code to test the console widget.
390 391 class MainWindow(wx.Frame):
391 392 def __init__(self, parent, id, title):
392 393 wx.Frame.__init__(self, parent, id, title, size=(300,250))
393 394 self._sizer = wx.BoxSizer(wx.VERTICAL)
394 395 self.console_widget = ConsoleWidget(self)
395 396 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
396 397 self.SetSizer(self._sizer)
397 398 self.SetAutoLayout(1)
398 399 self.Show(True)
399 400
400 401 app = wx.PySimpleApp()
401 402 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
402 403 w.SetSize((780, 460))
403 404 w.Show()
404 405
405 406 app.MainLoop()
406 407
407 408
@@ -1,493 +1,495 b''
1 1 # encoding: utf-8 -*- test-case-name:
2 2 # FIXME: Need to add tests.
3 3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
4 4
5 5 """Classes to provide a Wx frontend to the
6 6 IPython.kernel.core.interpreter.
7 7
8 8 This class inherits from ConsoleWidget, that provides a console-like
9 9 widget to provide a text-rendering widget suitable for a terminal.
10 10 """
11 11
12 12 __docformat__ = "restructuredtext en"
13 13
14 14 #-------------------------------------------------------------------------------
15 15 # Copyright (C) 2008 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-------------------------------------------------------------------------------
20 20
21 21 #-------------------------------------------------------------------------------
22 22 # Imports
23 23 #-------------------------------------------------------------------------------
24 24
25 25 # Major library imports
26 26 import re
27 27 import __builtin__
28 28 from time import sleep
29 29 import sys
30 30 from threading import Lock
31 31 import string
32 32
33 33 import wx
34 34 from wx import stc
35 35
36 36 # Ipython-specific imports.
37 37 from IPython.frontend._process import PipedProcess
38 38 from console_widget import ConsoleWidget
39 39 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
40 40
41 41 #-------------------------------------------------------------------------------
42 42 # Constants
43 43 #-------------------------------------------------------------------------------
44 44
45 45 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
46 46 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
47 47 _ERROR_BG = '#FFF1F1' # Nice red
48 48
49 49 _COMPLETE_BUFFER_MARKER = 31
50 50 _ERROR_MARKER = 30
51 51 _INPUT_MARKER = 29
52 52
53 53 prompt_in1 = \
54 54 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
55 55
56 56 prompt_out = \
57 57 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
58 58
59 59 #-------------------------------------------------------------------------------
60 60 # Classes to implement the Wx frontend
61 61 #-------------------------------------------------------------------------------
62 62 class WxController(ConsoleWidget, PrefilterFrontEnd):
63 63 """Classes to provide a Wx frontend to the
64 64 IPython.kernel.core.interpreter.
65 65
66 66 This class inherits from ConsoleWidget, that provides a console-like
67 67 widget to provide a text-rendering widget suitable for a terminal.
68 68 """
69 69
70 70 output_prompt_template = string.Template(prompt_out)
71 71
72 72 input_prompt_template = string.Template(prompt_in1)
73 73
74 74 # Print debug info on what is happening to the console.
75 75 debug = False
76 76
77 77 # The title of the terminal, as captured through the ANSI escape
78 78 # sequences.
79 79 def _set_title(self, title):
80 80 return self.Parent.SetTitle(title)
81 81
82 82 def _get_title(self):
83 83 return self.Parent.GetTitle()
84 84
85 85 title = property(_get_title, _set_title)
86 86
87 87
88 88 # The buffer being edited.
89 89 # We are duplicating the definition here because of multiple
90 90 # inheritence
91 91 def _set_input_buffer(self, string):
92 92 ConsoleWidget._set_input_buffer(self, string)
93 93 self._colorize_input_buffer()
94 94
95 95 def _get_input_buffer(self):
96 96 """ Returns the text in current edit buffer.
97 97 """
98 98 return ConsoleWidget._get_input_buffer(self)
99 99
100 100 input_buffer = property(_get_input_buffer, _set_input_buffer)
101 101
102 102
103 103 #--------------------------------------------------------------------------
104 104 # Private Attributes
105 105 #--------------------------------------------------------------------------
106 106
107 107 # A flag governing the behavior of the input. Can be:
108 108 #
109 109 # 'readline' for readline-like behavior with a prompt
110 110 # and an edit buffer.
111 111 # 'raw_input' similar to readline, but triggered by a raw-input
112 112 # call. Can be used by subclasses to act differently.
113 113 # 'subprocess' for sending the raw input directly to a
114 114 # subprocess.
115 115 # 'buffering' for buffering of the input, that will be used
116 116 # when the input state switches back to another state.
117 117 _input_state = 'readline'
118 118
119 119 # Attribute to store reference to the pipes of a subprocess, if we
120 120 # are running any.
121 121 _running_process = False
122 122
123 123 # A queue for writing fast streams to the screen without flooding the
124 124 # event loop
125 125 _out_buffer = []
126 126
127 127 # A lock to lock the _out_buffer to make sure we don't empty it
128 128 # while it is being swapped
129 129 _out_buffer_lock = Lock()
130 130
131 131 _markers = dict()
132 132
133 133 #--------------------------------------------------------------------------
134 134 # Public API
135 135 #--------------------------------------------------------------------------
136 136
137 137 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
138 138 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
139 139 *args, **kwds):
140 140 """ Create Shell instance.
141 141 """
142 142 ConsoleWidget.__init__(self, parent, id, pos, size, style)
143 143 PrefilterFrontEnd.__init__(self, **kwds)
144 144
145 145 # Marker for complete buffer.
146 146 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
147 147 background=_COMPLETE_BUFFER_BG)
148 148 # Marker for current input buffer.
149 149 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
150 150 background=_INPUT_BUFFER_BG)
151 151 # Marker for tracebacks.
152 152 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
153 153 background=_ERROR_BG)
154 154
155 155 # A time for flushing the write buffer
156 156 BUFFER_FLUSH_TIMER_ID = 100
157 157 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
158 158 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
159 159
160 160 if 'debug' in kwds:
161 161 self.debug = kwds['debug']
162 162 kwds.pop('debug')
163 163
164 164 # Inject self in namespace, for debug
165 165 if self.debug:
166 166 self.shell.user_ns['self'] = self
167 167
168 168
169 169 def raw_input(self, prompt):
170 170 """ A replacement from python's raw_input.
171 171 """
172 172 self.new_prompt(prompt)
173 173 self._input_state = 'raw_input'
174 174 if hasattr(self, '_cursor'):
175 175 del self._cursor
176 176 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
177 177 self.waiting = True
178 178 self.__old_on_enter = self._on_enter
179 179 def my_on_enter():
180 180 self.waiting = False
181 181 self._on_enter = my_on_enter
182 182 # XXX: Busy waiting, ugly.
183 183 while self.waiting:
184 184 wx.Yield()
185 185 sleep(0.1)
186 186 self._on_enter = self.__old_on_enter
187 187 self._input_state = 'buffering'
188 188 self._cursor = wx.BusyCursor()
189 189 return self.input_buffer.rstrip('\n')
190 190
191 191
192 192 def system_call(self, command_string):
193 193 self._input_state = 'subprocess'
194 194 self._running_process = PipedProcess(command_string,
195 195 out_callback=self.buffered_write,
196 196 end_callback = self._end_system_call)
197 197 self._running_process.start()
198 198 # XXX: another one of these polling loops to have a blocking
199 199 # call
200 200 wx.Yield()
201 201 while self._running_process:
202 202 wx.Yield()
203 203 sleep(0.1)
204 204 # Be sure to flush the buffer.
205 205 self._buffer_flush(event=None)
206 206
207 207
208 208 def do_calltip(self):
209 209 """ Analyse current and displays useful calltip for it.
210 210 """
211 211 if self.debug:
212 212 print >>sys.__stdout__, "do_calltip"
213 213 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
214 214 symbol = self.input_buffer
215 215 symbol_string = separators.split(symbol)[-1]
216 216 base_symbol_string = symbol_string.split('.')[0]
217 217 if base_symbol_string in self.shell.user_ns:
218 218 symbol = self.shell.user_ns[base_symbol_string]
219 219 elif base_symbol_string in self.shell.user_global_ns:
220 220 symbol = self.shell.user_global_ns[base_symbol_string]
221 221 elif base_symbol_string in __builtin__.__dict__:
222 222 symbol = __builtin__.__dict__[base_symbol_string]
223 223 else:
224 224 return False
225 225 try:
226 226 for name in symbol_string.split('.')[1:] + ['__doc__']:
227 227 symbol = getattr(symbol, name)
228 228 self.AutoCompCancel()
229 229 wx.Yield()
230 230 self.CallTipShow(self.GetCurrentPos(), symbol)
231 231 except:
232 232 # The retrieve symbol couldn't be converted to a string
233 233 pass
234 234
235 235
236 236 def _popup_completion(self, create=False):
237 237 """ Updates the popup completion menu if it exists. If create is
238 238 true, open the menu.
239 239 """
240 240 if self.debug:
241 241 print >>sys.__stdout__, "_popup_completion",
242 242 line = self.input_buffer
243 243 if (self.AutoCompActive() and not line[-1] == '.') \
244 244 or create==True:
245 245 suggestion, completions = self.complete(line)
246 246 offset=0
247 247 if completions:
248 248 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
249 249 residual = complete_sep.split(line)[-1]
250 250 offset = len(residual)
251 251 self.pop_completion(completions, offset=offset)
252 252 if self.debug:
253 253 print >>sys.__stdout__, completions
254 254
255 255
256 256 def buffered_write(self, text):
257 257 """ A write method for streams, that caches the stream in order
258 258 to avoid flooding the event loop.
259 259
260 260 This can be called outside of the main loop, in separate
261 261 threads.
262 262 """
263 263 self._out_buffer_lock.acquire()
264 264 self._out_buffer.append(text)
265 265 self._out_buffer_lock.release()
266 266 if not self._buffer_flush_timer.IsRunning():
267 267 wx.CallAfter(self._buffer_flush_timer.Start, 100) # milliseconds
268 268
269 269
270 270 #--------------------------------------------------------------------------
271 271 # LineFrontEnd interface
272 272 #--------------------------------------------------------------------------
273 273
274 274 def execute(self, python_string, raw_string=None):
275 275 self._input_state = 'buffering'
276 276 self.CallTipCancel()
277 277 self._cursor = wx.BusyCursor()
278 278 if raw_string is None:
279 279 raw_string = python_string
280 280 end_line = self.current_prompt_line \
281 281 + max(1, len(raw_string.split('\n'))-1)
282 282 for i in range(self.current_prompt_line, end_line):
283 283 if i in self._markers:
284 284 self.MarkerDeleteHandle(self._markers[i])
285 285 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
286 286 # Update the display:
287 287 wx.Yield()
288 288 self.GotoPos(self.GetLength())
289 289 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
290 290
291 291 def save_output_hooks(self):
292 292 self.__old_raw_input = __builtin__.raw_input
293 293 PrefilterFrontEnd.save_output_hooks(self)
294 294
295 295 def capture_output(self):
296 296 __builtin__.raw_input = self.raw_input
297 297 PrefilterFrontEnd.capture_output(self)
298 298
299 299
300 300 def release_output(self):
301 301 __builtin__.raw_input = self.__old_raw_input
302 302 PrefilterFrontEnd.release_output(self)
303 303
304 304
305 305 def after_execute(self):
306 306 PrefilterFrontEnd.after_execute(self)
307 307 # Clear the wait cursor
308 308 if hasattr(self, '_cursor'):
309 309 del self._cursor
310 310 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
311 311
312 312
313 313 def show_traceback(self):
314 314 start_line = self.GetCurrentLine()
315 315 PrefilterFrontEnd.show_traceback(self)
316 316 wx.Yield()
317 317 for i in range(start_line, self.GetCurrentLine()):
318 318 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
319 319
320 320
321 321 #--------------------------------------------------------------------------
322 322 # ConsoleWidget interface
323 323 #--------------------------------------------------------------------------
324 324
325 325 def new_prompt(self, prompt):
326 326 """ Display a new prompt, and start a new input buffer.
327 327 """
328 328 self._input_state = 'readline'
329 329 ConsoleWidget.new_prompt(self, prompt)
330 330 i = self.current_prompt_line
331 331 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
332 332
333 333
334 334 def write(self, *args, **kwargs):
335 335 # Avoid multiple inheritence, be explicit about which
336 336 # parent method class gets called
337 337 ConsoleWidget.write(self, *args, **kwargs)
338 338
339 339
340 340 def _on_key_down(self, event, skip=True):
341 341 """ Capture the character events, let the parent
342 342 widget handle them, and put our logic afterward.
343 343 """
344 344 # FIXME: This method needs to be broken down in smaller ones.
345 345 current_line_number = self.GetCurrentLine()
346 346 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
347 347 # Capture Control-C
348 348 if self._input_state == 'subprocess':
349 349 if self.debug:
350 350 print >>sys.__stderr__, 'Killing running process'
351 351 self._running_process.process.kill()
352 352 elif self._input_state == 'buffering':
353 353 if self.debug:
354 354 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
355 355 raise KeyboardInterrupt
356 356 # XXX: We need to make really sure we
357 357 # get back to a prompt.
358 358 elif self._input_state == 'subprocess' and (
359 359 ( event.KeyCode<256 and
360 360 not event.ControlDown() )
361 361 or
362 362 ( event.KeyCode in (ord('d'), ord('D')) and
363 363 event.ControlDown())):
364 364 # We are running a process, we redirect keys.
365 365 ConsoleWidget._on_key_down(self, event, skip=skip)
366 366 char = chr(event.KeyCode)
367 367 # Deal with some inconsistency in wx keycodes:
368 368 if char == '\r':
369 369 char = '\n'
370 370 elif not event.ShiftDown():
371 371 char = char.lower()
372 372 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
373 373 char = '\04'
374 374 self._running_process.process.stdin.write(char)
375 375 self._running_process.process.stdin.flush()
376 376 elif event.KeyCode in (ord('('), 57):
377 377 # Calltips
378 378 event.Skip()
379 379 self.do_calltip()
380 elif self.AutoCompActive():
380 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
381 381 event.Skip()
382 382 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
383 383 wx.CallAfter(self._popup_completion, create=True)
384 384 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
385 wx.WXK_RIGHT):
385 wx.WXK_RIGHT, wx.WXK_ESCAPE):
386 386 wx.CallAfter(self._popup_completion)
387 387 else:
388 388 # Up history
389 389 if event.KeyCode == wx.WXK_UP and (
390 390 ( current_line_number == self.current_prompt_line and
391 391 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
392 392 or event.ControlDown() ):
393 393 new_buffer = self.get_history_previous(
394 394 self.input_buffer)
395 395 if new_buffer is not None:
396 396 self.input_buffer = new_buffer
397 397 if self.GetCurrentLine() > self.current_prompt_line:
398 398 # Go to first line, for seemless history up.
399 399 self.GotoPos(self.current_prompt_pos)
400 400 # Down history
401 401 elif event.KeyCode == wx.WXK_DOWN and (
402 402 ( current_line_number == self.LineCount -1 and
403 403 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
404 404 or event.ControlDown() ):
405 405 new_buffer = self.get_history_next()
406 406 if new_buffer is not None:
407 407 self.input_buffer = new_buffer
408 408 # Tab-completion
409 409 elif event.KeyCode == ord('\t'):
410 410 last_line = self.input_buffer.split('\n')[-1]
411 411 if not re.match(r'^\s*$', last_line):
412 412 self.complete_current_input()
413 if self.AutoCompActive():
414 wx.CallAfter(self._popup_completion, create=True)
413 415 else:
414 416 event.Skip()
415 417 else:
416 418 ConsoleWidget._on_key_down(self, event, skip=skip)
417 419
418 420
419 421 def _on_key_up(self, event, skip=True):
420 422 """ Called when any key is released.
421 423 """
422 424 if event.KeyCode in (59, ord('.')):
423 425 # Intercepting '.'
424 426 event.Skip()
425 427 self._popup_completion(create=True)
426 428 else:
427 429 ConsoleWidget._on_key_up(self, event, skip=skip)
428 430
429 431
430 432 def _on_enter(self):
431 433 """ Called on return key down, in readline input_state.
432 434 """
433 435 if self.debug:
434 436 print >>sys.__stdout__, repr(self.input_buffer)
435 437 PrefilterFrontEnd._on_enter(self)
436 438
437 439
438 440 #--------------------------------------------------------------------------
439 441 # Private API
440 442 #--------------------------------------------------------------------------
441 443
442 444 def _end_system_call(self):
443 445 """ Called at the end of a system call.
444 446 """
445 447 self._input_state = 'buffering'
446 448 self._running_process = False
447 449
448 450
449 451 def _buffer_flush(self, event):
450 452 """ Called by the timer to flush the write buffer.
451 453
452 454 This is always called in the mainloop, by the wx timer.
453 455 """
454 456 self._out_buffer_lock.acquire()
455 457 _out_buffer = self._out_buffer
456 458 self._out_buffer = []
457 459 self._out_buffer_lock.release()
458 460 self.write(''.join(_out_buffer), refresh=False)
459 461 self._buffer_flush_timer.Stop()
460 462
461 463 def _colorize_input_buffer(self):
462 464 """ Keep the input buffer lines at a bright color.
463 465 """
464 466 if not self._input_state in ('readline', 'raw_input'):
465 467 return
466 468 end_line = self.GetCurrentLine()
467 469 if not sys.platform == 'win32':
468 470 end_line += 1
469 471 for i in range(self.current_prompt_line, end_line):
470 472 if i in self._markers:
471 473 self.MarkerDeleteHandle(self._markers[i])
472 474 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
473 475
474 476
475 477 if __name__ == '__main__':
476 478 class MainWindow(wx.Frame):
477 479 def __init__(self, parent, id, title):
478 480 wx.Frame.__init__(self, parent, id, title, size=(300,250))
479 481 self._sizer = wx.BoxSizer(wx.VERTICAL)
480 482 self.shell = WxController(self)
481 483 self._sizer.Add(self.shell, 1, wx.EXPAND)
482 484 self.SetSizer(self._sizer)
483 485 self.SetAutoLayout(1)
484 486 self.Show(True)
485 487
486 488 app = wx.PySimpleApp()
487 489 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
488 490 frame.shell.SetFocus()
489 491 frame.SetSize((680, 460))
490 492 self = frame.shell
491 493
492 494 app.MainLoop()
493 495
General Comments 0
You need to be logged in to leave comments. Login now